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