diff --git a/packages/vite-plugin-react-router/README.md b/packages/vite-plugin-react-router/README.md index 07405ab1..ec9792eb 100644 --- a/packages/vite-plugin-react-router/README.md +++ b/packages/vite-plugin-react-router/README.md @@ -3,27 +3,37 @@ The React Router Adapter for Netlify allows you to deploy your [React Router](https://reactrouter.com) app to [Netlify Functions](https://docs.netlify.com/functions/overview/). -To deploy a React Router 7+ site to Netlify, install this package: - ## How to use +To deploy a React Router 7+ site to Netlify, install this package: + ```sh npm install --save-dev @netlify/vite-plugin-react-router ``` +It's also recommended (but not required) to use the +[Netlify Vite plugin](https://www.npmjs.com/package/@netlify/vite-plugin), which provides full Netlify platform +emulation directly in your local dev server: + +```sh +npm install --save-dev @netlify/vite-plugin +``` + and include the Netlify plugin in your `vite.config.ts`: ```typescript import { reactRouter } from '@react-router/dev/vite' import { defineConfig } from 'vite' import tsconfigPaths from 'vite-tsconfig-paths' -import netlifyPlugin from '@netlify/vite-plugin-react-router' // <- add this +import netlifyReactRouter from '@netlify/vite-plugin-react-router' // <- add this +import netlify from '@netlify/vite-plugin' // <- add this (optional) export default defineConfig({ plugins: [ reactRouter(), tsconfigPaths(), - netlifyPlugin(), // <- add this + netlifyReactRouter(), // <- add this + netlify(), // <- add this (optional) ], }) ``` @@ -76,6 +86,12 @@ export default function Example() { } ``` +> [!IMPORTANT] +> +> Note that in local development, `netlifyRouterContext` requires Netlify platform emulation, which is provided +> seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI - up to +> you). + ### Middleware context React Router introduced a stable middleware feature in 7.9.0. @@ -103,3 +119,9 @@ export default function Home() { return

Hello world

} ``` + +> [!IMPORTANT] +> +> Note that in local development, `netlifyRouterContext` requires Netlify platform emulation, which is provided +> seamlessly by [`@netlify/vite-plugin`](https://www.npmjs.com/package/@netlify/vite-plugin) (or Netlify CLI - up to +> you). diff --git a/packages/vite-plugin-react-router/src/server.ts b/packages/vite-plugin-react-router/src/server.ts index e61daf85..668f6252 100644 --- a/packages/vite-plugin-react-router/src/server.ts +++ b/packages/vite-plugin-react-router/src/server.ts @@ -41,7 +41,36 @@ export type RequestHandler = (request: Request, context: NetlifyContext) => Prom * * @example context.get(netlifyRouterContext).geo?.country?.name */ -export const netlifyRouterContext = createContext() +export const netlifyRouterContext = + // We must use a singleton because Remix contexts rely on referential equality. + // We can't hook into the request lifecycle in dev mode, so we use a Proxy to always read from the + // current `Netlify.context` value, which is always contextual to the in-flight request. + createContext>( + new Proxy( + // Can't reference `Netlify.context` here because it isn't set outside of a request lifecycle + {}, + { + get(_target, prop, receiver) { + return Reflect.get(Netlify.context ?? {}, prop, receiver) + }, + set(_target, prop, value, receiver) { + return Reflect.set(Netlify.context ?? {}, prop, value, receiver) + }, + has(_target, prop) { + return Reflect.has(Netlify.context ?? {}, prop) + }, + deleteProperty(_target, prop) { + return Reflect.deleteProperty(Netlify.context ?? {}, prop) + }, + ownKeys(_target) { + return Reflect.ownKeys(Netlify.context ?? {}) + }, + getOwnPropertyDescriptor(_target, prop) { + return Reflect.getOwnPropertyDescriptor(Netlify.context ?? {}, prop) + }, + }, + ), + ) /** * Given a build and a callback to get the base loader context, this returns