Skip to content

Commit e4362d1

Browse files
committed
fix: populate value for Netlify RR context in dev
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 function `getNetlifyRouterContext()` so that we can lazily construct the context with a default value of `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 fb65ce3 commit e4362d1

File tree

5 files changed

+42
-17
lines changed

5 files changed

+42
-17
lines changed

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

Lines changed: 26 additions & 8 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
```
@@ -61,13 +71,13 @@ type-safe `RouterContextProvider`. Note that this requires requires v2.0.0+ of `
6171
For example:
6272

6373
```tsx
64-
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
74+
import { getNetlifyRouterContext } from '@netlify/vite-plugin-react-router'
6575
import { useLoaderData } from 'react-router'
6676
import type { Route } from './+types/example'
6777

6878
export async function loader({ context }: Route.LoaderArgs) {
6979
return {
70-
country: context.get(netlifyRouterContext).geo?.country?.name ?? 'an unknown country',
80+
country: context.get(getNetlifyRouterContext()).geo?.country?.name ?? 'an unknown country',
7181
}
7282
}
7383
export default function Example() {
@@ -76,6 +86,10 @@ export default function Example() {
7686
}
7787
```
7888

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

8195
React Router introduced a stable middleware feature in 7.9.0.
@@ -88,12 +102,12 @@ To access the [Netlify context](https://docs.netlify.com/build/functions/api/#ne
88102
specifically, you must import our `RouterContextProvider` instance:
89103

90104
```tsx
91-
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
105+
import { getNetlifyRouterContext } from '@netlify/vite-plugin-react-router'
92106

93107
import type { Route } from './+types/home'
94108

95109
const logMiddleware: Route.MiddlewareFunction = async ({ request, context }) => {
96-
const country = context.get(netlifyRouterContext).geo?.country?.name ?? 'unknown'
110+
const country = context.get(getNetlifyRouterContext()).geo?.country?.name ?? 'unknown'
97111
console.log(`Handling ${request.method} request to ${request.url} from ${country}`)
98112
}
99113

@@ -103,3 +117,7 @@ export default function Home() {
103117
return <h1>Hello world</h1>
104118
}
105119
```
120+
121+
> [!IMPORTANT] Note that in local development, `getNetlifyRouterContext` requires Netlify platform emulation, which is
122+
> provided seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI -
123+
> up to you).
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export type { GetLoadContextFunction, RequestHandler } from './server'
2-
export { createRequestHandler, netlifyRouterContext } from './server'
2+
export { createRequestHandler, getNetlifyRouterContext } from './server'
33

44
export { netlifyPlugin as default } from './plugin'

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AppLoadContext, ServerBuild } from 'react-router'
1+
import type { AppLoadContext, RouterContext, ServerBuild } from 'react-router'
22
import {
33
createContext,
44
RouterContextProvider,
@@ -35,13 +35,20 @@ export type GetLoadContextFunction_V8 = (
3535

3636
export type RequestHandler = (request: Request, context: NetlifyContext) => Promise<Response>
3737

38+
let netlifyRouterContext: RouterContext<Partial<NetlifyContext>>
3839
/**
3940
* An instance of `ReactContextProvider` providing access to
4041
* [Netlify request context]{@link https://docs.netlify.com/build/functions/api/#netlify-specific-context-object}
4142
*
42-
* @example context.get(netlifyRouterContext).geo?.country?.name
43+
* @example context.get(getNetlifyRouterContext()).geo?.country?.name
4344
*/
44-
export const netlifyRouterContext = createContext<NetlifyContext>()
45+
export const getNetlifyRouterContext = () => {
46+
// We must use a singleton because Remix contexts rely on referential equality
47+
// We defer initialization until first use so that we can initialize it with a default value
48+
// within a request lifecycle, where the `Netlify` global will be set. This is just for dev.
49+
netlifyRouterContext ??= createContext<Partial<NetlifyContext>>(Netlify.context ?? {})
50+
return netlifyRouterContext
51+
}
4552

4653
/**
4754
* Given a build and a callback to get the base loader context, this returns
@@ -66,7 +73,7 @@ export function createRequestHandler({
6673
try {
6774
const getDefaultReactRouterContext = () => {
6875
const ctx = new RouterContextProvider()
69-
ctx.set(netlifyRouterContext, netlifyContext)
76+
ctx.set(getNetlifyRouterContext(), netlifyContext)
7077

7178
// Provide backwards compatibility with previous plain object context
7279
// See https://reactrouter.com/how-to/middleware#migration-from-apploadcontext.

tests/e2e/fixtures/react-router-v8-middleware/app/routes/context.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
1+
import { getNetlifyRouterContext } from '@netlify/vite-plugin-react-router'
22
import { useLoaderData } from 'react-router'
33

44
import type { Route } from './+types/context'
55

66
export async function loader({ context }: Route.LoaderArgs) {
77
return {
8-
siteName: context.get(netlifyRouterContext).site.name,
8+
siteName: context.get(getNetlifyRouterContext()).site.name,
99
}
1010
}
1111
export default function About() {

tests/e2e/fixtures/react-router-v8-middleware/app/routes/middleware.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { netlifyRouterContext } from '@netlify/vite-plugin-react-router'
1+
import { getNetlifyRouterContext } from '@netlify/vite-plugin-react-router'
22

33
import type { Route } from './+types/middleware'
44

55
const logMiddleware: Route.MiddlewareFunction = async ({ request, context }, next) => {
6-
const siteName = context.get(netlifyRouterContext).site.name
6+
const siteName = context.get(getNetlifyRouterContext()).site.name
77
console.log(`Handling ${request.method} request to ${request.url} on ${siteName}`)
88
const response = await next()
99
response.headers.set('x-test-site-name', siteName)

0 commit comments

Comments
 (0)