Skip to content

Commit 8d2c9fd

Browse files
authored
fix: populate value for Netlify React Router context in dev (#550)
Follow-up to #546. Since the `netlifyRouterContext.set()` call was occurring in the server entry point built for production but not in dev, and no default value was set for the context at construction time, using middleware in dev would throw. There's no ergonomic way to set up a `.set()` call dynamically on a context in dev, as far as I can tell. This leaves the default value set at construction time as our only option. To make this work, we can use the global `Netlify.context`. However, this is only accessible during a request-response lifecycle, not in module scope at module init time. So this commit simply changes `netlifyRouterContext` to a proxy so that we can lazily evaluate the context properties from `Netlify.context` during a request lifecycle. To be clear, the Netlify router context will only work in local dev when using either the Netlify CLI or `@netlify/vite-plugin`.
1 parent 28f2644 commit 8d2c9fd

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

packages/vite-plugin-react-router/README.md

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,37 @@
33
The React Router Adapter for Netlify allows you to deploy your [React Router](https://reactrouter.com) app to
44
[Netlify Functions](https://docs.netlify.com/functions/overview/).
55

6-
To deploy a React Router 7+ site to Netlify, install this package:
7-
86
## How to use
97

8+
To deploy a React Router 7+ site to Netlify, install this package:
9+
1010
```sh
1111
npm install --save-dev @netlify/vite-plugin-react-router
1212
```
1313

14+
It's also recommended (but not required) to use the
15+
[Netlify Vite plugin](https://www.npmjs.com/package/@netlify/vite-plugin), which provides full Netlify platform
16+
emulation directly in your local dev server:
17+
18+
```sh
19+
npm install --save-dev @netlify/vite-plugin
20+
```
21+
1422
and include the Netlify plugin in your `vite.config.ts`:
1523

1624
```typescript
1725
import { reactRouter } from '@react-router/dev/vite'
1826
import { defineConfig } from 'vite'
1927
import tsconfigPaths from 'vite-tsconfig-paths'
20-
import netlifyPlugin from '@netlify/vite-plugin-react-router' // <- add this
28+
import netlifyReactRouter from '@netlify/vite-plugin-react-router' // <- add this
29+
import netlify from '@netlify/vite-plugin' // <- add this (optional)
2130
2231
export default defineConfig({
2332
plugins: [
2433
reactRouter(),
2534
tsconfigPaths(),
26-
netlifyPlugin(), // <- add this
35+
netlifyReactRouter(), // <- add this
36+
netlify(), // <- add this (optional)
2737
],
2838
})
2939
```
@@ -76,6 +86,12 @@ export default function Example() {
7686
}
7787
```
7888

89+
> [!IMPORTANT]
90+
>
91+
> Note that in local development, `netlifyRouterContext` requires Netlify platform emulation, which is provided
92+
> seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI - up to
93+
> you).
94+
7995
### Middleware context
8096

8197
React Router introduced a stable middleware feature in 7.9.0.
@@ -103,3 +119,9 @@ export default function Home() {
103119
return <h1>Hello world</h1>
104120
}
105121
```
122+
123+
> [!IMPORTANT]
124+
>
125+
> Note that in local development, `netlifyRouterContext` requires Netlify platform emulation, which is provided
126+
> seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI - up to
127+
> you).

packages/vite-plugin-react-router/src/server.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,36 @@ export type RequestHandler = (request: Request, context: NetlifyContext) => Prom
4141
*
4242
* @example context.get(netlifyRouterContext).geo?.country?.name
4343
*/
44-
export const netlifyRouterContext = createContext<NetlifyContext>()
44+
export const netlifyRouterContext =
45+
// We must use a singleton because Remix contexts rely on referential equality.
46+
// We can't hook into the request lifecycle in dev mode, so we use a Proxy to always read from the
47+
// current `Netlify.context` value, which is always contextual to the in-flight request.
48+
createContext<Partial<NetlifyContext>>(
49+
new Proxy(
50+
// Can't reference `Netlify.context` here because it isn't set outside of a request lifecycle
51+
{},
52+
{
53+
get(_target, prop, receiver) {
54+
return Reflect.get(Netlify.context ?? {}, prop, receiver)
55+
},
56+
set(_target, prop, value, receiver) {
57+
return Reflect.set(Netlify.context ?? {}, prop, value, receiver)
58+
},
59+
has(_target, prop) {
60+
return Reflect.has(Netlify.context ?? {}, prop)
61+
},
62+
deleteProperty(_target, prop) {
63+
return Reflect.deleteProperty(Netlify.context ?? {}, prop)
64+
},
65+
ownKeys(_target) {
66+
return Reflect.ownKeys(Netlify.context ?? {})
67+
},
68+
getOwnPropertyDescriptor(_target, prop) {
69+
return Reflect.getOwnPropertyDescriptor(Netlify.context ?? {}, prop)
70+
},
71+
},
72+
),
73+
)
4574

4675
/**
4776
* Given a build and a callback to get the base loader context, this returns

0 commit comments

Comments
 (0)