@netlify/vite-plugin-react-router 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.2](https://github.com/netlify/remix-compute/compare/vite-plugin-react-router-v2.1.1...vite-plugin-react-router-v2.1.2) (2025-11-08)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **vite-plugin-react-router:** fix custom `build.assetsDir` edge case ([#578](https://github.com/netlify/remix-compute/issues/578)) ([7316d95](https://github.com/netlify/remix-compute/commit/7316d95ea087e19dffe9414749c891642096c4cd))
9
+
10
+ ## [2.1.1](https://github.com/netlify/remix-compute/compare/vite-plugin-react-router-v2.1.0...vite-plugin-react-router-v2.1.1) (2025-11-06)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **@netlify/vite-plugin-react-router:** fix local dev with `edge: true` ([#572](https://github.com/netlify/remix-compute/issues/572)) ([dfb27c1](https://github.com/netlify/remix-compute/commit/dfb27c1cb52d253063b2c19dd52b05fb5ec8f4ce))
16
+
3
17
  ## [2.1.0](https://github.com/netlify/remix-compute/compare/vite-plugin-react-router-v2.0.1...vite-plugin-react-router-v2.1.0) (2025-11-05)
4
18
 
5
19
 
package/README.md CHANGED
@@ -58,59 +58,23 @@ export default defineConfig({
58
58
  })
59
59
  ```
60
60
 
61
- Second, you **must** provide an `app/entry.server.tsx` (or `.jsx`) file that uses web-standard APIs compatible with the
62
- Deno runtime. Create a file with the following content:
63
-
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.
61
+ Second, you **must** provide an `app/entry.server.tsx` (or `.jsx`) file. Create a file with the following content:
69
62
 
70
63
  ```tsx
71
- import type { AppLoadContext, EntryContext } from 'react-router'
72
- import { ServerRouter } from 'react-router'
73
- import { isbot } from 'isbot'
74
- import { renderToReadableStream } from 'react-dom/server'
75
-
76
- export default async function handleRequest(
77
- request: Request,
78
- responseStatusCode: number,
79
- responseHeaders: Headers,
80
- routerContext: EntryContext,
81
- _loadContext: AppLoadContext,
82
- ) {
83
- let shellRendered = false
84
- const userAgent = request.headers.get('user-agent')
85
-
86
- const body = await renderToReadableStream(<ServerRouter context={routerContext} url={request.url} />, {
87
- onError(error: unknown) {
88
- responseStatusCode = 500
89
- // Log streaming rendering errors from inside the shell. Don't log
90
- // errors encountered during initial shell rendering since they'll
91
- // reject and get logged in handleDocumentRequest.
92
- if (shellRendered) {
93
- console.error(error)
94
- }
95
- },
96
- })
97
- shellRendered = true
98
-
99
- // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
100
- // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
101
- if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
102
- await body.allReady
103
- }
104
-
105
- responseHeaders.set('Content-Type', 'text/html')
106
- return new Response(body, {
107
- headers: responseHeaders,
108
- status: responseStatusCode,
109
- })
110
- }
64
+ export { default } from 'virtual:netlify-server-entry'
111
65
  ```
112
66
 
113
- You may need to `npm install isbot` if you do not have this dependency.
67
+ > [!TIP]
68
+ >
69
+ > If you prefer to avoid a `@ts-ignore` here, add this to `vite-env.d.ts` in your project root (or anywhere you prefer):
70
+ >
71
+ > ```typescript
72
+ > declare module 'virtual:netlify-server-entry' {
73
+ > import type { ServerEntryModule } from 'react-router'
74
+ > const entry: ServerEntryModule
75
+ > export default entry
76
+ > }
77
+ > ```
114
78
 
115
79
  Finally, if you have your own Netlify Functions (typically in `netlify/functions`) for which you've configured a `path`,
116
80
  you must exclude those paths to avoid conflicts with the generated React Router SSR handler:
@@ -0,0 +1,20 @@
1
+ import { EntryContext, AppLoadContext } from 'react-router';
2
+
3
+ /**
4
+ * Edge-compatible server entry using Web Streams instead of Node.js Streams.
5
+ * @see {@link https://reactrouter.com/api/framework-conventions/entry.server.tsx}
6
+ *
7
+ * This file was copied as-is from the React Router repository.
8
+ * @see {@link
9
+ * https://github.com/remix-run/react-router/blob/cb9a090316003988ff367bb2f2d1ef5bd03bd3af/integration/helpers/vite-plugin-cloudflare-template/app/entry.server.tsx}
10
+ *
11
+ *
12
+ * @example Export this from your `app/entry.server.tsx` when using `edge: true`:
13
+ *
14
+ * ```tsx
15
+ * export { default } from 'virtual:netlify-server-entry'
16
+ * ```
17
+ */
18
+ declare function handleRequest(request: Request, responseStatusCode: number, responseHeaders: Headers, routerContext: EntryContext, _loadContext: AppLoadContext): Promise<Response>;
19
+
20
+ export { handleRequest as default };
@@ -0,0 +1,29 @@
1
+ // src/entry.server.edge.tsx
2
+ import { ServerRouter } from "react-router";
3
+ import { isbot } from "isbot";
4
+ import { renderToReadableStream } from "react-dom/server";
5
+ import { jsx } from "react/jsx-runtime";
6
+ async function handleRequest(request, responseStatusCode, responseHeaders, routerContext, _loadContext) {
7
+ let shellRendered = false;
8
+ const userAgent = request.headers.get("user-agent");
9
+ const body = await renderToReadableStream(/* @__PURE__ */ jsx(ServerRouter, { context: routerContext, url: request.url }), {
10
+ onError(error) {
11
+ responseStatusCode = 500;
12
+ if (shellRendered) {
13
+ console.error(error);
14
+ }
15
+ }
16
+ });
17
+ shellRendered = true;
18
+ if (userAgent && isbot(userAgent) || routerContext.isSpaMode) {
19
+ await body.allReady;
20
+ }
21
+ responseHeaders.set("Content-Type", "text/html");
22
+ return new Response(body, {
23
+ headers: responseHeaders,
24
+ status: responseStatusCode
25
+ });
26
+ }
27
+ export {
28
+ handleRequest as default
29
+ };
package/dist/index.js CHANGED
@@ -109,10 +109,11 @@ function createRequestHandler({
109
109
  var import_promises = require("fs/promises");
110
110
  var import_node_path = require("path");
111
111
  var import_posix = require("path/posix");
112
+ var import_tinyglobby = require("tinyglobby");
112
113
 
113
114
  // package.json
114
115
  var name = "@netlify/vite-plugin-react-router";
115
- var version = "2.1.0";
116
+ var version = "2.1.2";
116
117
 
117
118
  // src/plugin.ts
118
119
  var NETLIFY_FUNCTIONS_DIR = ".netlify/v1/functions";
@@ -121,6 +122,7 @@ var FUNCTION_FILENAME = "react-router-server.mjs";
121
122
  var FUNCTION_HANDLER_CHUNK = "server";
122
123
  var FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
123
124
  var RESOLVED_FUNCTION_HANDLER_MODULE_ID = `\0${FUNCTION_HANDLER_MODULE_ID}`;
125
+ var SERVER_ENTRY_MODULE_ID = "virtual:netlify-server-entry";
124
126
  var toPosixPath = (path) => path.split(import_node_path.sep).join(import_posix.sep);
125
127
  var FUNCTION_HANDLER = (
126
128
  /* js */
@@ -179,9 +181,11 @@ function netlifyPlugin(options = {}) {
179
181
  const additionalExcludedPaths = options.excludedPaths ?? [];
180
182
  let resolvedConfig;
181
183
  let isProductionSsrBuild = false;
184
+ let currentCommand;
182
185
  return {
183
186
  name: "vite-plugin-netlify-react-router",
184
187
  config(config, { command, isSsrBuild }) {
188
+ currentCommand = command;
185
189
  isProductionSsrBuild = isSsrBuild === true && command === "build";
186
190
  if (isProductionSsrBuild) {
187
191
  config.build ??= {};
@@ -212,10 +216,20 @@ function netlifyPlugin(options = {}) {
212
216
  }
213
217
  }
214
218
  },
215
- async resolveId(source) {
219
+ async resolveId(source, importer, options2) {
216
220
  if (source === FUNCTION_HANDLER_MODULE_ID) {
217
221
  return RESOLVED_FUNCTION_HANDLER_MODULE_ID;
218
222
  }
223
+ if (source === SERVER_ENTRY_MODULE_ID && edge) {
224
+ if (currentCommand === "serve") {
225
+ const reactRouterDev = await this.resolve("@react-router/dev/config", importer, options2);
226
+ if (!reactRouterDev) {
227
+ throw new Error("The @react-router/dev package is required for local development. Please install it.");
228
+ }
229
+ return (0, import_node_path.resolve)((0, import_node_path.dirname)(reactRouterDev.id), "config/defaults/entry.server.node.tsx");
230
+ }
231
+ return this.resolve("@netlify/vite-plugin-react-router/entry.server.edge", importer, options2);
232
+ }
219
233
  },
220
234
  // See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
221
235
  load(id) {
@@ -232,12 +246,14 @@ function netlifyPlugin(options = {}) {
232
246
  const handlerPath = (0, import_node_path.join)(resolvedConfig.build.outDir, `${FUNCTION_HANDLER_CHUNK}.js`);
233
247
  if (edge) {
234
248
  const clientDir = (0, import_node_path.join)(resolvedConfig.build.outDir, "..", "client");
235
- const entries = await (0, import_promises.readdir)(clientDir, { withFileTypes: true });
236
- const excludedPath = [
237
- "/.netlify/*",
238
- ...entries.map((entry) => entry.isDirectory() ? `/${entry.name}/*` : `/${entry.name}`),
239
- ...additionalExcludedPaths
240
- ];
249
+ const clientFiles = await (0, import_tinyglobby.glob)("**/*", {
250
+ cwd: clientDir,
251
+ // We can't exclude entire directories because there could be `foo/bar.baz` in the
252
+ // client dir and a `/foo` route handled by the server function.
253
+ onlyFiles: true,
254
+ dot: true
255
+ });
256
+ const excludedPath = ["/.netlify/*", ...clientFiles.map((file) => `/${file}`), ...additionalExcludedPaths];
241
257
  const edgeFunctionsDir = (0, import_node_path.join)(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
242
258
  await (0, import_promises.mkdir)(edgeFunctionsDir, { recursive: true });
243
259
  const relativeHandlerPath = toPosixPath((0, import_node_path.relative)(edgeFunctionsDir, handlerPath));
package/dist/index.mjs CHANGED
@@ -5,13 +5,14 @@ import {
5
5
  import "./chunk-J5PMA2AP.mjs";
6
6
 
7
7
  // src/plugin.ts
8
- import { mkdir, writeFile, readdir } from "node:fs/promises";
9
- import { join, relative, sep } from "node:path";
8
+ import { mkdir, writeFile } from "node:fs/promises";
9
+ import { dirname, join, relative, resolve, sep } from "node:path";
10
10
  import { sep as posixSep } from "node:path/posix";
11
+ import { glob } from "tinyglobby";
11
12
 
12
13
  // package.json
13
14
  var name = "@netlify/vite-plugin-react-router";
14
- var version = "2.1.0";
15
+ var version = "2.1.2";
15
16
 
16
17
  // src/plugin.ts
17
18
  var NETLIFY_FUNCTIONS_DIR = ".netlify/v1/functions";
@@ -20,6 +21,7 @@ var FUNCTION_FILENAME = "react-router-server.mjs";
20
21
  var FUNCTION_HANDLER_CHUNK = "server";
21
22
  var FUNCTION_HANDLER_MODULE_ID = "virtual:netlify-server";
22
23
  var RESOLVED_FUNCTION_HANDLER_MODULE_ID = `\0${FUNCTION_HANDLER_MODULE_ID}`;
24
+ var SERVER_ENTRY_MODULE_ID = "virtual:netlify-server-entry";
23
25
  var toPosixPath = (path) => path.split(sep).join(posixSep);
24
26
  var FUNCTION_HANDLER = (
25
27
  /* js */
@@ -78,9 +80,11 @@ function netlifyPlugin(options = {}) {
78
80
  const additionalExcludedPaths = options.excludedPaths ?? [];
79
81
  let resolvedConfig;
80
82
  let isProductionSsrBuild = false;
83
+ let currentCommand;
81
84
  return {
82
85
  name: "vite-plugin-netlify-react-router",
83
86
  config(config, { command, isSsrBuild }) {
87
+ currentCommand = command;
84
88
  isProductionSsrBuild = isSsrBuild === true && command === "build";
85
89
  if (isProductionSsrBuild) {
86
90
  config.build ??= {};
@@ -111,10 +115,20 @@ function netlifyPlugin(options = {}) {
111
115
  }
112
116
  }
113
117
  },
114
- async resolveId(source) {
118
+ async resolveId(source, importer, options2) {
115
119
  if (source === FUNCTION_HANDLER_MODULE_ID) {
116
120
  return RESOLVED_FUNCTION_HANDLER_MODULE_ID;
117
121
  }
122
+ if (source === SERVER_ENTRY_MODULE_ID && edge) {
123
+ if (currentCommand === "serve") {
124
+ const reactRouterDev = await this.resolve("@react-router/dev/config", importer, options2);
125
+ if (!reactRouterDev) {
126
+ throw new Error("The @react-router/dev package is required for local development. Please install it.");
127
+ }
128
+ return resolve(dirname(reactRouterDev.id), "config/defaults/entry.server.node.tsx");
129
+ }
130
+ return this.resolve("@netlify/vite-plugin-react-router/entry.server.edge", importer, options2);
131
+ }
118
132
  },
119
133
  // See https://vitejs.dev/guide/api-plugin#virtual-modules-convention.
120
134
  load(id) {
@@ -131,12 +145,14 @@ function netlifyPlugin(options = {}) {
131
145
  const handlerPath = join(resolvedConfig.build.outDir, `${FUNCTION_HANDLER_CHUNK}.js`);
132
146
  if (edge) {
133
147
  const clientDir = join(resolvedConfig.build.outDir, "..", "client");
134
- const entries = await readdir(clientDir, { withFileTypes: true });
135
- const excludedPath = [
136
- "/.netlify/*",
137
- ...entries.map((entry) => entry.isDirectory() ? `/${entry.name}/*` : `/${entry.name}`),
138
- ...additionalExcludedPaths
139
- ];
148
+ const clientFiles = await glob("**/*", {
149
+ cwd: clientDir,
150
+ // We can't exclude entire directories because there could be `foo/bar.baz` in the
151
+ // client dir and a `/foo` route handled by the server function.
152
+ onlyFiles: true,
153
+ dot: true
154
+ });
155
+ const excludedPath = ["/.netlify/*", ...clientFiles.map((file) => `/${file}`), ...additionalExcludedPaths];
140
156
  const edgeFunctionsDir = join(resolvedConfig.root, NETLIFY_EDGE_FUNCTIONS_DIR);
141
157
  await mkdir(edgeFunctionsDir, { recursive: true });
142
158
  const relativeHandlerPath = toPosixPath(relative(edgeFunctionsDir, handlerPath));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/vite-plugin-react-router",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "React Router 7+ Vite plugin for Netlify",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
@@ -24,6 +24,10 @@
24
24
  "./edge": {
25
25
  "types": "./dist/edge.d.mts",
26
26
  "default": "./dist/edge.mjs"
27
+ },
28
+ "./entry.server.edge": {
29
+ "types": "./dist/entry.server.edge.d.mts",
30
+ "default": "./dist/entry.server.edge.mjs"
27
31
  }
28
32
  },
29
33
  "files": [
@@ -48,11 +52,12 @@
48
52
  },
49
53
  "homepage": "https://github.com/netlify/remix-compute#readme",
50
54
  "dependencies": {
51
- "isbot": "^5.0.0"
55
+ "@netlify/edge-functions": "^3.0.2",
56
+ "@netlify/functions": "^5.1.0",
57
+ "isbot": "^5.1.25",
58
+ "tinyglobby": "^0.2.10"
52
59
  },
53
60
  "devDependencies": {
54
- "@netlify/edge-functions": "^2.11.0",
55
- "@netlify/functions": "^3.1.10",
56
61
  "@types/react": "^18.0.27",
57
62
  "@types/react-dom": "^18.0.10",
58
63
  "react": "^18.2.0",