From 919ea3788c15d1373ef3dcf57cabcf54bdf7f64d Mon Sep 17 00:00:00 2001
From: RFCreate <107062289+RFCreate@users.noreply.github.com>
Date: Mon, 13 Oct 2025 08:41:39 -0600
Subject: [PATCH 1/3] Signed CLA by adding RFCreate to list
---
contributors.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/contributors.yml b/contributors.yml
index b1cefda8b7..d66ecb9e2b 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -330,6 +330,7 @@
- remorses
- renyu-io
- reyronald
+- RFCreate
- richardscarrott
- rifaidev
- rimian
From 566b02874d55141e610132427d096b60bf5d0081 Mon Sep 17 00:00:00 2001
From: RFCreate <107062289+RFCreate@users.noreply.github.com>
Date: Fri, 21 Nov 2025 18:39:10 -0600
Subject: [PATCH 2/3] fix(vite): loadEnv before reading routes
---
.changeset/pretty-dryers-drive.md | 5 +++++
packages/react-router-dev/vite/plugin.ts | 12 ++++++------
packages/react-router-dev/vite/rsc/plugin.ts | 12 ++++++------
3 files changed, 17 insertions(+), 12 deletions(-)
create mode 100644 .changeset/pretty-dryers-drive.md
diff --git a/.changeset/pretty-dryers-drive.md b/.changeset/pretty-dryers-drive.md
new file mode 100644
index 0000000000..e92e1c1bc6
--- /dev/null
+++ b/.changeset/pretty-dryers-drive.md
@@ -0,0 +1,5 @@
+---
+"@react-router/dev": major
+---
+
+Load vite env variables before reading routes.ts
diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts
index 81cd7e0bb3..9847e9192b 100644
--- a/packages/react-router-dev/vite/plugin.ts
+++ b/packages/react-router-dev/vite/plugin.ts
@@ -1279,20 +1279,20 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
});
}
- reactRouterConfigLoader = await createConfigLoader({
+ await loadDotenv({
rootDirectory,
+ viteUserConfig,
mode,
- watch: viteCommand === "serve",
});
- await updatePluginContext();
-
- await loadDotenv({
+ reactRouterConfigLoader = await createConfigLoader({
rootDirectory,
- viteUserConfig,
mode,
+ watch: viteCommand === "serve",
});
+ await updatePluginContext();
+
let environments = await getEnvironmentsOptions(ctx, viteCommand, {
viteUserConfig,
});
diff --git a/packages/react-router-dev/vite/rsc/plugin.ts b/packages/react-router-dev/vite/rsc/plugin.ts
index 73510358dc..c0333f683f 100644
--- a/packages/react-router-dev/vite/rsc/plugin.ts
+++ b/packages/react-router-dev/vite/rsc/plugin.ts
@@ -60,6 +60,12 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
const rootDirectory = getRootDirectory(viteUserConfig);
const watch = command === "serve";
+ await loadDotenv({
+ rootDirectory,
+ viteUserConfig,
+ mode,
+ });
+
configLoader = await createConfigLoader({
rootDirectory,
mode,
@@ -104,12 +110,6 @@ export function reactRouterRSCVitePlugin(): Vite.PluginOption[] {
);
}
- await loadDotenv({
- rootDirectory,
- viteUserConfig,
- mode,
- });
-
const vite = await import("vite");
logger = vite.createLogger(viteUserConfig.logLevel, {
prefix: "[react-router]",
From e60d82bc46f369311c6dbf3610ee6408654874c9 Mon Sep 17 00:00:00 2001
From: RFCreate <107062289+RFCreate@users.noreply.github.com>
Date: Fri, 21 Nov 2025 18:36:20 -0600
Subject: [PATCH 3/3] test: write case where VITE_* variable is used in
routes.ts
---
.../helpers/vite-6-template/package.json | 2 +-
integration/vite-dotenv-test.ts | 157 +++++++++---------
pnpm-lock.yaml | 132 ++++++++++++++-
3 files changed, 201 insertions(+), 90 deletions(-)
diff --git a/integration/helpers/vite-6-template/package.json b/integration/helpers/vite-6-template/package.json
index 14cb1cb07f..71fee2d06f 100644
--- a/integration/helpers/vite-6-template/package.json
+++ b/integration/helpers/vite-6-template/package.json
@@ -31,7 +31,7 @@
"@types/react-dom": "^18.2.7",
"eslint": "^8.38.0",
"typescript": "^5.1.6",
- "vite": "^6.1.0",
+ "vite": "^6.3.0",
"vite-env-only": "^3.0.1",
"vite-tsconfig-paths": "^4.2.1"
},
diff --git a/integration/vite-dotenv-test.ts b/integration/vite-dotenv-test.ts
index c9faf7ad54..90961997aa 100644
--- a/integration/vite-dotenv-test.ts
+++ b/integration/vite-dotenv-test.ts
@@ -10,7 +10,7 @@ import {
} from "./helpers/vite.js";
const templateNames = [
- "vite-5-template",
+ "vite-6-template",
"rsc-vite-framework",
] as const satisfies TemplateName[];
@@ -30,6 +30,24 @@ let getFiles = async ({
"server.mjs": EXPRESS_SERVER({ port, templateName }),
[envPath]: `
ENV_VAR_FROM_DOTENV_FILE=Content from ${envPath} file
+ VITE_PORTAL=testing
+ `,
+ "app/routes.ts": `
+ import { type RouteConfig, route } from "@react-router/dev/routes";
+ import { flatRoutes } from "@react-router/fs-routes";
+ const routes = [];
+ if (import.meta.env.VITE_PORTAL === "testing") {
+ routes.push(route("testing", "testing.tsx"));
+ }
+ export default [
+ ...await flatRoutes(),
+ ...routes,
+ ] satisfies RouteConfig;
+ `,
+ "app/testing.tsx": String.raw`
+ export default function TestingRoute() {
+ return
Testing Route
+ }
`,
"app/routes/dotenv.tsx": String.raw`
import { useState, useEffect } from "react";
@@ -62,91 +80,68 @@ let getFiles = async ({
};
};
-test.describe("Vite .env", () => {
- for (const templateName of templateNames) {
- test.describe(`template: ${templateName}`, () => {
- test.describe("defaults", async () => {
- let port: number;
- let cwd: string;
- let stop: () => void;
-
- test.beforeAll(async () => {
- port = await getPort();
- cwd = await createProject(
- await getFiles({ port, templateName }),
- templateName,
- );
- stop = await customDev({ cwd, port });
- });
- test.afterAll(() => stop());
-
- test("express", async ({ page }) => {
- let pageErrors: unknown[] = [];
- page.on("pageerror", (error) => pageErrors.push(error));
-
- await page.goto(`http://localhost:${port}/dotenv`, {
- waitUntil: "networkidle",
+for (const envDir of [undefined, "custom-env-dir"]) {
+ let envPath = `${envDir ? `${envDir}/` : ""}.env`;
+ test.describe(`Vite ${envPath}`, () => {
+ for (const templateName of templateNames) {
+ test.describe(`template: ${templateName}`, () => {
+ test.describe("defaults", async () => {
+ let port: number;
+ let cwd: string;
+ let stop: () => void;
+
+ test.beforeAll(async () => {
+ port = await getPort();
+ cwd = await createProject(
+ await getFiles({ envDir, port, templateName }),
+ templateName,
+ );
+ stop = await customDev({ cwd, port });
+ });
+ test.afterAll(() => stop());
+
+ test("express", async ({ page }) => {
+ let pageErrors: unknown[] = [];
+ page.on("pageerror", (error) => pageErrors.push(error));
+
+ await page.goto(`http://localhost:${port}/dotenv`, {
+ waitUntil: "networkidle",
+ });
+ expect(pageErrors).toEqual([]);
+
+ let loaderContent = page.locator(
+ "[data-dotenv-route-loader-content]",
+ );
+ await expect(loaderContent).toHaveText(
+ `Content from ${envPath} file`,
+ );
+
+ let clientContent = page.locator(
+ "[data-dotenv-route-client-content]",
+ );
+ await expect(clientContent).toHaveText(
+ "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing",
+ );
+
+ expect(pageErrors).toEqual([]);
});
- expect(pageErrors).toEqual([]);
-
- let loaderContent = page.locator(
- "[data-dotenv-route-loader-content]",
- );
- await expect(loaderContent).toHaveText("Content from .env file");
- let clientContent = page.locator(
- "[data-dotenv-route-client-content]",
- );
- await expect(clientContent).toHaveText(
- "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing",
- );
+ test("routes.ts has VITE_* env var", async ({ page }) => {
+ let pageErrors: unknown[] = [];
+ page.on("pageerror", (error) => pageErrors.push(error));
- expect(pageErrors).toEqual([]);
- });
- });
-
- test.describe("custom env dir", async () => {
- let port: number;
- let cwd: string;
- let stop: () => void;
-
- test.beforeAll(async () => {
- const envDir = "custom-env-dir";
- port = await getPort();
- cwd = await createProject(
- await getFiles({ envDir, port, templateName }),
- templateName,
- );
- stop = await customDev({ cwd, port });
- });
- test.afterAll(() => stop());
+ await page.goto(`http://localhost:${port}/testing`, {
+ waitUntil: "networkidle",
+ });
+ expect(pageErrors).toEqual([]);
- test("express", async ({ page }) => {
- let pageErrors: unknown[] = [];
- page.on("pageerror", (error) => pageErrors.push(error));
+ let testingDiv = page.locator("[data-testing-route]");
+ await expect(testingDiv).toHaveText("Testing Route");
- await page.goto(`http://localhost:${port}/dotenv`, {
- waitUntil: "networkidle",
+ expect(pageErrors).toEqual([]);
});
- expect(pageErrors).toEqual([]);
-
- let loaderContent = page.locator(
- "[data-dotenv-route-loader-content]",
- );
- await expect(loaderContent).toHaveText(
- "Content from custom-env-dir/.env file",
- );
-
- let clientContent = page.locator(
- "[data-dotenv-route-client-content]",
- );
- await expect(clientContent).toHaveText(
- "process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing",
- );
-
- expect(pageErrors).toEqual([]);
});
});
- });
- }
-});
+ }
+ });
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6abe21b3be..5d9f5d7a97 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -616,7 +616,7 @@ importers:
version: 1.17.4(babel-plugin-macros@3.1.0)
'@vanilla-extract/vite-plugin':
specifier: ^5.1.1
- version: 5.1.1(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))(yaml@2.8.0)
+ version: 5.1.1(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))(yaml@2.8.0)
express:
specifier: ^4.19.2
version: 4.21.2
@@ -658,14 +658,14 @@ importers:
specifier: ^5.1.6
version: 5.4.5
vite:
- specifier: ^6.1.0
- version: 6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
+ specifier: ^6.3.0
+ version: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
vite-env-only:
specifier: ^3.0.1
- version: 3.0.1(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
+ version: 3.0.1(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
vite-tsconfig-paths:
specifier: ^4.2.1
- version: 4.3.2(typescript@5.4.5)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
+ version: 4.3.2(typescript@5.4.5)(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))
integration/helpers/vite-7-beta-template:
dependencies:
@@ -9627,6 +9627,46 @@ packages:
yaml:
optional: true
+ vite@6.4.1:
+ resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
vite@7.0.0-beta.0:
resolution: {integrity: sha512-aW1su/Hrr35rHWviV72W3wXb5HfJOK21XGHHAthotTaE04zldBzjkUJUmk6EY6zFDrW5lk2Jw4ygZrJREkB+MA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -13484,7 +13524,7 @@ snapshots:
dependencies:
'@vanilla-extract/css': 1.17.4(babel-plugin-macros@3.1.0)
'@vanilla-extract/integration': 8.0.4(babel-plugin-macros@3.1.0)
- vite: 6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
+ vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
vite-node: 3.2.4(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
@@ -13596,6 +13636,26 @@ snapshots:
- tsx
- yaml
+ '@vanilla-extract/vite-plugin@5.1.1(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))(yaml@2.8.0)':
+ dependencies:
+ '@vanilla-extract/compiler': 0.3.1(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
+ '@vanilla-extract/integration': 8.0.4(babel-plugin-macros@3.1.0)
+ vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
'@vanilla-extract/vite-plugin@5.1.1(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(vite@7.0.0-beta.0(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0))(yaml@2.8.0)':
dependencies:
'@vanilla-extract/compiler': 0.3.1(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
@@ -19275,6 +19335,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ vite-env-only@3.0.1(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)):
+ dependencies:
+ '@babel/core': 7.27.7
+ '@babel/generator': 7.27.5
+ '@babel/parser': 7.27.7
+ '@babel/traverse': 7.27.7
+ '@babel/types': 7.27.7
+ babel-dead-code-elimination: 1.0.10
+ micromatch: 4.0.5
+ vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
+ transitivePeerDependencies:
+ - supports-color
+
vite-env-only@3.0.1(vite@7.0.0-beta.0(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)):
dependencies:
'@babel/core': 7.27.7
@@ -19294,7 +19367,7 @@ snapshots:
debug: 4.4.1
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 6.2.5(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
+ vite: 6.4.1(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -19315,7 +19388,7 @@ snapshots:
debug: 4.4.1
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 6.2.5(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
+ vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -19385,6 +19458,17 @@ snapshots:
- supports-color
- typescript
+ vite-tsconfig-paths@4.3.2(typescript@5.4.5)(vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)):
+ dependencies:
+ debug: 4.4.1
+ globrex: 0.1.2
+ tsconfck: 3.0.3(typescript@5.4.5)
+ optionalDependencies:
+ vite: 6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
vite-tsconfig-paths@4.3.2(typescript@5.4.5)(vite@7.0.0-beta.0(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)):
dependencies:
debug: 4.4.1
@@ -19433,6 +19517,38 @@ snapshots:
tsx: 4.19.3
yaml: 2.8.0
+ vite@6.4.1(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0):
+ dependencies:
+ esbuild: 0.25.0
+ fdir: 6.4.6(picomatch@4.0.2)
+ picomatch: 4.0.2
+ postcss: 8.5.3
+ rollup: 4.43.0
+ tinyglobby: 0.2.14
+ optionalDependencies:
+ '@types/node': 20.11.30
+ fsevents: 2.3.3
+ jiti: 2.4.2
+ lightningcss: 1.30.1
+ tsx: 4.19.3
+ yaml: 2.8.0
+
+ vite@6.4.1(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0):
+ dependencies:
+ esbuild: 0.25.0
+ fdir: 6.4.6(picomatch@4.0.2)
+ picomatch: 4.0.2
+ postcss: 8.5.3
+ rollup: 4.43.0
+ tinyglobby: 0.2.14
+ optionalDependencies:
+ '@types/node': 22.14.0
+ fsevents: 2.3.3
+ jiti: 2.4.2
+ lightningcss: 1.30.1
+ tsx: 4.19.3
+ yaml: 2.8.0
+
vite@7.0.0-beta.0(@types/node@22.14.0)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0):
dependencies:
esbuild: 0.25.0