Skip to content

Commit bbcb41e

Browse files
authored
feat(@netlify/vite-plugin-react-router): add edge support (#562)
This adds opt-in support for deploying React Router 7 apps to Netlify Edge Functions (Deno runtime) via the `edge: boolean` plugin option. Previously, the plugin only supported Netlify Functions (Node.js runtime). - Add `edge?: boolean` option to Vite plugin factory - When `edge` is enabled: - Configure Vite SSR build with `target: 'webworker'` and other Deno-compatible build config - Generate an edge function in `.netlify/v1/edge-functions/`, instead of a function `.netlify/v1/functions/` - Add `excludedPaths?: string[]` option to allow users to exclude paths from the React Router handler. Required when using `edge: true` and the project also has its own Netlify Functions with a configured `path`, otherwise only the RR handler runs on all dynamic paths.
1 parent 88b44ae commit bbcb41e

File tree

82 files changed

+2831
-1723
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+2831
-1723
lines changed

.github/workflows/e2e.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ jobs:
2525

2626
- run: corepack enable
2727
- name: Install Deno
28-
uses: denoland/setup-deno@v1
28+
uses: denoland/setup-deno@v2
2929
with:
3030
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17
31-
deno-version: v1.37.0
31+
deno-version: v2.2.4
3232
- run: pnpm install
3333

3434
- name: Install Playwright Browsers

.github/workflows/publint.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Publint
2+
on:
3+
push:
4+
branches: [main]
5+
pull_request:
6+
types: [opened, synchronize, reopened]
7+
branches:
8+
- '**'
9+
- '!release-please--**'
10+
merge_group:
11+
jobs:
12+
publint:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: read
16+
steps:
17+
- run: git config --global core.symlinks true
18+
- uses: actions/checkout@v5
19+
with:
20+
fetch-depth: 0
21+
- run: corepack enable pnpm
22+
- name: Node.js
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: lts/*
26+
cache: 'pnpm'
27+
- name: Install Deno
28+
uses: denoland/setup-deno@v2
29+
with:
30+
# Should satisfy the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17
31+
deno-version: v2.2.4
32+
- name: Install dependencies
33+
run: pnpm install
34+
- name: Build
35+
run: pnpm run build:packages
36+
- name: Publint
37+
run: pnpm run --filter '@netlify/*' publint

.github/workflows/release-please.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ jobs:
2828
registry-url: 'https://registry.npmjs.org'
2929
if: ${{ steps.release.outputs.releases_created }}
3030
- name: Install Deno
31-
uses: denoland/setup-deno@v1
31+
uses: denoland/setup-deno@v2
3232
with:
3333
# Should satisfy the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17
34-
deno-version: v1
34+
deno-version: v2.2.4
3535
if: ${{ steps.release.outputs.releases_created }}
3636
- run: corepack enable
3737
- name: Install dependencies

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ jobs:
2222
check-latest: true
2323
- run: corepack enable
2424
- name: Install Deno
25-
uses: denoland/setup-deno@v1
25+
uses: denoland/setup-deno@v2
2626
with:
2727
# Should satisfy the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17
28-
deno-version: v1
28+
deno-version: v2.2.4
2929
- name: Install
3030
run: pnpm install
3131
- name: Build

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@
4747
"fast-glob": "^3.3.2",
4848
"husky": "^9.0.11",
4949
"lint-staged": "^15.0.0",
50-
"netlify-cli": "^20.1.1",
50+
"netlify-cli": "^23.9.5",
5151
"npm-run-all2": "^6.0.0",
5252
"p-limit": "^5.0.0",
5353
"prettier": "^3.0.0",
54+
"publint": "^0.3.15",
5455
"typescript": "^5.0.0",
5556
"vitest": "^3.0.0"
5657
},

packages/remix-adapter/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"scripts": {
4242
"prepack": "pnpm run build",
4343
"build": "tsup-node src/index.ts src/vite/plugin.ts --format esm,cjs --dts --target node16 --clean",
44-
"build:watch": "pnpm run build --watch"
44+
"build:watch": "pnpm run build --watch",
45+
"publint": "publint --strict"
4546
},
4647
"repository": {
4748
"type": "git",

packages/remix-edge-adapter/README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,31 @@ However, if you are using **Remix Vite**, you can instead deploy your existing s
1616

1717
```js
1818
// vite.config.js
19-
import { vitePlugin as remix } from "@remix-run/dev";
20-
import { netlifyPlugin } from "@netlify/remix-edge-adapter/plugin";
19+
import { vitePlugin as remix } from '@remix-run/dev'
20+
import { netlifyPlugin } from '@netlify/remix-edge-adapter/plugin'
2121

2222
export default defineConfig({
23-
plugins: [remix(), netlifyPlugin(),
24-
});
23+
plugins: [remix(), netlifyPlugin()],
24+
})
25+
```
26+
27+
If you have your own Netlify Functions (typically in `netlify/functions`) for which you've configured a `path`, you must
28+
exclude those paths to avoid conflicts with the generated Remix SSR handler, which would otherwise run on all dynamic
29+
paths:
30+
31+
```js
32+
// vite.config.js
33+
import { vitePlugin as remix } from '@remix-run/dev'
34+
import { netlifyPlugin } from '@netlify/remix-edge-adapter/plugin'
35+
36+
export default defineConfig({
37+
plugins: [
38+
remix(),
39+
netlifyPlugin({
40+
excludedPaths: ['/ping', '/api/*', '/webhooks/*'],
41+
}),
42+
],
43+
})
2544
```
2645

2746
3. Add an `app/entry.jsx` (.tsx if using TypeScript) with these contents:

packages/remix-edge-adapter/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@
4343
"build": "pnpm run build:src && pnpm run build:types",
4444
"build:src": "tsup-node src/index.ts src/vite/plugin.ts --format esm,cjs --dts --target node16 --clean",
4545
"build:types": "deno types > deno.d.ts",
46-
"build:watch": "pnpm run build:src --watch"
46+
"build:watch": "pnpm run build:src --watch",
47+
"publint": "publint --strict"
4748
},
4849
"repository": {
4950
"type": "git",

packages/remix-edge-adapter/src/vite/plugin.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export default createRequestHandler({
8585

8686
// This is written to the edge functions directory. It just re-exports
8787
// the compiled entrypoint, along with the Netlify function config.
88-
function generateEdgeFunction(handlerPath: string, exclude: Array<string> = []) {
88+
function generateEdgeFunction(handlerPath: string, excludedPath: Array<string>) {
8989
return /* js */ `
9090
export { default } from "${handlerPath}";
9191
@@ -94,7 +94,7 @@ function generateEdgeFunction(handlerPath: string, exclude: Array<string> = [])
9494
generator: "${name}@${version}",
9595
cache: "manual",
9696
path: "/*",
97-
excludedPath: ${JSON.stringify(exclude)},
97+
excludedPath: ${JSON.stringify(excludedPath)},
9898
};`
9999
}
100100

@@ -125,7 +125,21 @@ const getEdgeFunctionHandlerModuleId = async (root: string, isHydrogenSite: bool
125125
return findUserEdgeFunctionHandlerFile(root)
126126
}
127127

128-
export function netlifyPlugin(): Plugin {
128+
export interface NetlifyPluginOptions {
129+
/**
130+
* Paths to exclude from being handled by the Remix handler.
131+
*
132+
* @IMPORTANT If you have your own Netlify Functions running on custom `path`s, you
133+
* must exclude those paths here to avoid conflicts.
134+
*
135+
* @type {string[]}
136+
* @default []
137+
*/
138+
excludedPaths?: string[]
139+
}
140+
141+
export function netlifyPlugin(options: NetlifyPluginOptions = {}): Plugin {
142+
const additionalExcludedPaths = options.excludedPaths ?? []
129143
let resolvedConfig: ResolvedConfig
130144
let currentCommand: string
131145
let isSsr: boolean | undefined
@@ -264,23 +278,25 @@ export function netlifyPlugin(): Plugin {
264278
async writeBundle() {
265279
// Write the server entrypoint to the Netlify functions directory
266280
if (currentCommand === 'build' && isSsr) {
267-
const exclude: Array<string> = ['/.netlify/*']
281+
const excludedPath: Array<string> = ['/.netlify/*']
268282
try {
269283
// Get the client files so we can skip them in the edge function
270284
const clientDirectory = join(resolvedConfig.build.outDir, '..', 'client')
271285
const entries = await readdir(clientDirectory, { withFileTypes: true })
272286
for (const entry of entries) {
273287
// With directories we don't bother to recurse into it and just skip the whole thing.
274288
if (entry.isDirectory()) {
275-
exclude.push(`/${entry.name}/*`)
289+
excludedPath.push(`/${entry.name}/*`)
276290
} else if (entry.isFile()) {
277-
exclude.push(`/${entry.name}`)
291+
excludedPath.push(`/${entry.name}`)
278292
}
279293
}
280294
} catch {
281295
// Ignore if it doesn't exist
282296
}
283297

298+
excludedPath.push(...additionalExcludedPaths)
299+
284300
const edgeFunctionsDirectory = join(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR)
285301

286302
await mkdir(edgeFunctionsDirectory, { recursive: true })
@@ -290,7 +306,7 @@ export function netlifyPlugin(): Plugin {
290306

291307
await writeFile(
292308
join(edgeFunctionsDirectory, EDGE_FUNCTION_FILENAME),
293-
generateEdgeFunction(relativeHandlerPath, exclude),
309+
generateEdgeFunction(relativeHandlerPath, excludedPath),
294310
)
295311
}
296312
},

packages/remix-runtime/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"scripts": {
2222
"prepack": "pnpm run build",
2323
"build": "tsup-node src/index.ts --format esm,cjs --dts --target node16 --clean",
24-
"build:watch": "pnpm run build --watch"
24+
"build:watch": "pnpm run build --watch",
25+
"publint": "publint --strict"
2526
},
2627
"repository": {
2728
"type": "git",

0 commit comments

Comments
 (0)