Skip to content

Commit 2f24fb1

Browse files
committed
fix: add mechanism to support user NFs
1 parent 0286e3c commit 2f24fb1

File tree

4 files changed

+48
-14
lines changed

4 files changed

+48
-14
lines changed

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export default defineConfig({
6161
Second, you **must** provide an `app/entry.server.tsx` (or `.jsx`) file that uses web-standard APIs compatible with the
6262
Deno runtime. Create a file with the following content:
6363

64+
> [!IMPORTANT]
65+
>
66+
> This file uses `renderToReadableStream` (Web Streams API) instead of `renderToPipeableStream` (Node.js API), which is
67+
> required for the Deno runtime. You may customize your server entry file, but see below for important edge runtime
68+
> constraints.
69+
6470
```tsx
6571
import type { AppLoadContext, EntryContext } from 'react-router'
6672
import { ServerRouter } from 'react-router'
@@ -106,10 +112,22 @@ export default async function handleRequest(
106112

107113
You may need to `npm install isbot` if you do not have this dependency.
108114

109-
> [!IMPORTANT]
110-
>
111-
> This file uses `renderToReadableStream` (Web Streams API) instead of `renderToPipeableStream` (Node.js API), which is
112-
> required for the Deno runtime. You may customize your server entry file, but see below for
115+
Finally, if you have your own Netlify Functions (typically in `netlify/functions`) for which you've configured a `path`,
116+
you must exclude those paths to avoid conflicts with the generated React Router SSR handler:
117+
118+
```typescript
119+
export default defineConfig({
120+
plugins: [
121+
reactRouter(),
122+
tsconfigPaths(),
123+
netlifyReactRouter({
124+
edge: true,
125+
excludedPaths: ['/ping', '/api/*', '/webhooks/*'],
126+
}),
127+
netlify(),
128+
],
129+
})
130+
```
113131

114132
#### Moving back from Edge Functions to Functions
115133

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ export interface NetlifyPluginOptions {
1010
* @default false
1111
*/
1212
edge?: boolean
13+
/**
14+
* Paths to exclude from being handled by the React Router handler.
15+
*
16+
* @IMPORTANT If you have opted in to edge rendering with `edge: true` and you have your own Netlify
17+
* Functions running on custom `path`s, you must exclude those paths here to avoid conflicts.
18+
*
19+
* @type {URLPattern[]}
20+
* @default []
21+
*/
22+
excludedPaths?: string[]
1323
}
1424

1525
// https://docs.netlify.com/frameworks-api/#netlify-v1-functions
@@ -48,22 +58,23 @@ export default createRequestHandler({
4858

4959
// This is written to the functions directory. It just re-exports
5060
// the compiled entrypoint, along with Netlify function config.
51-
function generateNetlifyFunction(handlerPath: string) {
61+
function generateNetlifyFunction(handlerPath: string, excludedPath: Array<string>) {
5262
return /* js */ `
5363
export { default } from "${handlerPath}";
5464
5565
export const config = {
5666
name: "React Router server handler",
5767
generator: "${name}@${version}",
5868
path: "/*",
69+
excludedPath: ${JSON.stringify(excludedPath)},
5970
preferStatic: true,
6071
};
6172
`
6273
}
6374

6475
// This is written to the edge functions directory. It just re-exports
6576
// the compiled entrypoint, along with Netlify edge function config.
66-
function generateEdgeFunction(handlerPath: string, excludePath: Array<string> = []) {
77+
function generateEdgeFunction(handlerPath: string, excludedPath: Array<string>) {
6778
return /* js */ `
6879
export { default } from "${handlerPath}";
6980
@@ -72,13 +83,14 @@ function generateEdgeFunction(handlerPath: string, excludePath: Array<string> =
7283
generator: "${name}@${version}",
7384
cache: "manual",
7485
path: "/*",
75-
excludedPath: ${JSON.stringify(excludePath)},
86+
excludedPath: ${JSON.stringify(excludedPath)},
7687
};
7788
`
7889
}
7990

8091
export function netlifyPlugin(options: NetlifyPluginOptions = {}): Plugin {
81-
const { edge = false } = options
92+
const edge = options.edge ?? false
93+
const additionalExcludedPaths = options.excludedPaths ?? []
8294
let resolvedConfig: ResolvedConfig
8395
let isProductionSsrBuild = false
8496
return {
@@ -147,9 +159,10 @@ export function netlifyPlugin(options: NetlifyPluginOptions = {}): Plugin {
147159
// not configurable, so the client out dir is always at ../client from the server out dir.
148160
const clientDir = join(resolvedConfig.build.outDir, '..', 'client')
149161
const entries = await readdir(clientDir, { withFileTypes: true })
150-
const excludePath = [
162+
const excludedPath = [
151163
'/.netlify/*',
152164
...entries.map((entry) => (entry.isDirectory() ? `/${entry.name}/*` : `/${entry.name}`)),
165+
...additionalExcludedPaths,
153166
]
154167

155168
// Write the server entry point to the Netlify Edge Functions directory
@@ -158,14 +171,18 @@ export function netlifyPlugin(options: NetlifyPluginOptions = {}): Plugin {
158171
const relativeHandlerPath = toPosixPath(relative(edgeFunctionsDir, handlerPath))
159172
await writeFile(
160173
join(edgeFunctionsDir, FUNCTION_FILENAME),
161-
generateEdgeFunction(relativeHandlerPath, excludePath),
174+
generateEdgeFunction(relativeHandlerPath, excludedPath),
162175
)
163176
} else {
164177
// Write the server entry point to the Netlify Functions directory
165178
const functionsDir = join(resolvedConfig.root, NETLIFY_FUNCTIONS_DIR)
166179
await mkdir(functionsDir, { recursive: true })
167180
const relativeHandlerPath = toPosixPath(relative(functionsDir, handlerPath))
168-
await writeFile(join(functionsDir, FUNCTION_FILENAME), generateNetlifyFunction(relativeHandlerPath))
181+
const excludedPath = ['/.netlify/*', ...additionalExcludedPaths]
182+
await writeFile(
183+
join(functionsDir, FUNCTION_FILENAME),
184+
generateNetlifyFunction(relativeHandlerPath, excludedPath),
185+
)
169186
}
170187
}
171188
},

tests/e2e/fixtures/react-router-edge-site/vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ export default defineConfig({
1111
plugins: [tailwindcss, autoprefixer],
1212
},
1313
},
14-
plugins: [reactRouter(), netlifyPlugin({ edge: true }), tsconfigPaths()],
14+
plugins: [reactRouter(), netlifyPlugin({ edge: true, excludedPaths: ['/please-blorble'] }), tsconfigPaths()],
1515
})

tests/e2e/react-router-user-journeys.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,7 @@ test.describe('React Router user journeys', () => {
232232
},
233233
)
234234

235-
// FIXME(serhalp) ah yes, hrm.
236-
test.skip('serves a response from a user-defined Netlify Function on a custom path', async ({
235+
test('serves a response from a user-defined Netlify Function on a custom path', async ({
237236
page,
238237
reactRouterEdgeSite,
239238
}) => {

0 commit comments

Comments
 (0)