11import type { AppLoadContext , ServerBuild } from 'react-router'
2- import { createRequestHandler as createReactRouterRequestHandler } from 'react-router'
2+ import {
3+ createContext ,
4+ RouterContextProvider ,
5+ createRequestHandler as createReactRouterRequestHandler ,
6+ } from 'react-router'
37import type { Context as NetlifyContext } from '@netlify/functions'
48
5- type LoadContext = AppLoadContext & NetlifyContext
9+ // Augment the user's `AppLoadContext` to include Netlify context fields
10+ // This is the recommended approach: https://reactrouter.com/upgrading/remix#9-update-types-for-apploadcontext.
11+ declare module 'react-router' {
12+ interface AppLoadContext extends NetlifyContext { }
13+ }
614
715/**
8- * A function that returns the value to use as `context` in route `loader` and
9- * `action` functions.
16+ * A function that returns the value to use as `context` in route `loader` and `action` functions.
17+ *
18+ * You can think of this as an escape hatch that allows you to pass environment/platform-specific
19+ * values through to your loader/action.
1020 *
11- * You can think of this as an escape hatch that allows you to pass
12- * environment/platform-specific values through to your loader/action.
21+ * NOTE: v7.9.0 introduced a breaking change when the user opts in to `future.v8_middleware`. This
22+ * requires returning an instance of `RouterContextProvider` instead of a plain object. We have a
23+ * peer dependency on >=7.9.0 so we can safely *import* these, but we cannot assume the user has
24+ * opted in to the flag.
1325 */
14- export type GetLoadContextFunction = ( request : Request , context : NetlifyContext ) => Promise < LoadContext > | LoadContext
26+ export type GetLoadContextFunction = GetLoadContextFunction_V7 | GetLoadContextFunction_V8
27+ export type GetLoadContextFunction_V7 = (
28+ request : Request ,
29+ context : NetlifyContext ,
30+ ) => Promise < AppLoadContext > | AppLoadContext
31+ export type GetLoadContextFunction_V8 = (
32+ request : Request ,
33+ context : NetlifyContext ,
34+ ) => Promise < RouterContextProvider > | RouterContextProvider
1535
16- export type RequestHandler = ( request : Request , context : LoadContext ) => Promise < Response | void >
36+ export type RequestHandler = ( request : Request , context : NetlifyContext ) => Promise < Response >
37+
38+ /**
39+ * An instance of `ReactContextProvider` providing access to
40+ * [Netlify request context]{@link https://docs.netlify.com/build/functions/api/#netlify-specific-context-object}
41+ *
42+ * @example context.get(netlifyRouterContext).geo?.country?.name
43+ */
44+ export const netlifyRouterContext = createContext < NetlifyContext > ( )
1745
1846/**
1947 * Given a build and a callback to get the base loader context, this returns
@@ -32,13 +60,26 @@ export function createRequestHandler({
3260} ) : RequestHandler {
3361 const reactRouterHandler = createReactRouterRequestHandler ( build , mode )
3462
35- return async ( request : Request , netlifyContext : NetlifyContext ) : Promise < Response | void > => {
63+ return async ( request : Request , netlifyContext : NetlifyContext ) : Promise < Response > => {
3664 const start = Date . now ( )
3765 console . log ( `[${ request . method } ] ${ request . url } ` )
3866 try {
39- const mergedLoadContext = ( await getLoadContext ?.( request , netlifyContext ) ) || netlifyContext
67+ const getDefaultReactRouterContext = ( ) => {
68+ const ctx = new RouterContextProvider ( )
69+ ctx . set ( netlifyRouterContext , netlifyContext )
70+
71+ // Provide backwards compatibility with previous plain object context
72+ // See https://reactrouter.com/how-to/middleware#migration-from-apploadcontext.
73+ Object . assign ( ctx , netlifyContext )
74+
75+ return ctx
76+ }
77+ const reactRouterContext = ( await getLoadContext ?.( request , netlifyContext ) ) ?? getDefaultReactRouterContext ( )
4078
41- const response = await reactRouterHandler ( request , mergedLoadContext )
79+ // @ts -expect-error -- I don't think there's any way to type this properly. We're passing a
80+ // union of the two types here, but this function accepts a conditional type based on the
81+ // presence of the `future.v8_middleware` flag in the user's config, which we don't have access to.
82+ const response = await reactRouterHandler ( request , reactRouterContext )
4283
4384 // A useful header for debugging
4485 response . headers . set ( 'x-nf-runtime' , 'Node' )
0 commit comments