diff --git a/.changeset/afraid-buses-pay.md b/.changeset/afraid-buses-pay.md
new file mode 100644
index 0000000000..7571cfbdb2
--- /dev/null
+++ b/.changeset/afraid-buses-pay.md
@@ -0,0 +1,5 @@
+---
+"@react-router/dev": patch
+---
+
+Handle custom `envDir` in Vite config
diff --git a/integration/helpers/vite.ts b/integration/helpers/vite.ts
index 62b6359904..10e04ebd50 100644
--- a/integration/helpers/vite.ts
+++ b/integration/helpers/vite.ts
@@ -58,8 +58,14 @@ export const reactRouterConfig = ({
`;
};
+type ViteConfigArgs = {
+ port: number;
+ fsAllow?: string[];
+ envDir?: string;
+};
+
export const viteConfig = {
- server: async (args: { port: number; fsAllow?: string[] }) => {
+ server: async (args: ViteConfigArgs) => {
let { port, fsAllow } = args;
let hmrPort = await getPort();
let text = dedent`
@@ -72,7 +78,7 @@ export const viteConfig = {
`;
return text;
},
- basic: async (args: { port: number; fsAllow?: string[] }) => {
+ basic: async (args: ViteConfigArgs) => {
return dedent`
import { reactRouter } from "@react-router/dev/vite";
import { envOnlyMacros } from "vite-env-only";
@@ -80,6 +86,7 @@ export const viteConfig = {
export default {
${await viteConfig.server(args)}
+ envDir: ${args.envDir ? `"${args.envDir}"` : "undefined"},
plugins: [
reactRouter(),
envOnlyMacros(),
diff --git a/integration/vite-dotenv-test.ts b/integration/vite-dotenv-test.ts
index dd27b43520..d96fe1df1a 100644
--- a/integration/vite-dotenv-test.ts
+++ b/integration/vite-dotenv-test.ts
@@ -8,73 +8,113 @@ import {
viteConfig,
} from "./helpers/vite.js";
-let files = {
- ".env": `
- ENV_VAR_FROM_DOTENV_FILE=Content from .env file
- `,
- "app/routes/dotenv.tsx": String.raw`
- import { useState, useEffect } from "react";
- import { useLoaderData } from "react-router";
-
- export const loader = () => {
- return {
- loaderContent: process.env.ENV_VAR_FROM_DOTENV_FILE,
- }
- }
+let getFiles = async ({ envDir, port }: { envDir?: string; port: number }) => {
+ let envPath = `${envDir ? `${envDir}/` : ""}.env`;
- export default function DotenvRoute() {
- const { loaderContent } = useLoaderData();
+ return {
+ "vite.config.js": await viteConfig.basic({ port, envDir }),
+ "server.mjs": EXPRESS_SERVER({ port }),
+ [envPath]: `
+ ENV_VAR_FROM_DOTENV_FILE=Content from ${envPath} file
+ `,
+ "app/routes/dotenv.tsx": String.raw`
+ import { useState, useEffect } from "react";
+ import { useLoaderData } from "react-router";
- const [clientContent, setClientContent] = useState('');
- useEffect(() => {
- try {
- setClientContent("process.env.ENV_VAR_FROM_DOTENV_FILE shouldn't be available on the client, found: " + process.env.ENV_VAR_FROM_DOTENV_FILE);
- } catch (err) {
- setClientContent("process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing");
+ export const loader = () => {
+ return {
+ loaderContent: process.env.ENV_VAR_FROM_DOTENV_FILE,
}
- }, []);
-
- return <>
-
{loaderContent}
- {clientContent}
- >
- }
- `,
+ }
+
+ export default function DotenvRoute() {
+ const { loaderContent } = useLoaderData();
+
+ const [clientContent, setClientContent] = useState('');
+ useEffect(() => {
+ try {
+ setClientContent("process.env.ENV_VAR_FROM_DOTENV_FILE shouldn't be available on the client, found: " + process.env.ENV_VAR_FROM_DOTENV_FILE);
+ } catch (err) {
+ setClientContent("process.env.ENV_VAR_FROM_DOTENV_FILE not available on the client, which is a good thing");
+ }
+ }, []);
+
+ return <>
+ {loaderContent}
+ {clientContent}
+ >
+ }
+ `,
+ };
};
-test.describe(async () => {
- let port: number;
- let cwd: string;
- let stop: () => void;
-
- test.beforeAll(async () => {
- port = await getPort();
- cwd = await createProject({
- "vite.config.js": await viteConfig.basic({ port }),
- "server.mjs": EXPRESS_SERVER({ port }),
- ...files,
+test.describe("Vite .env", () => {
+ 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 }));
+ 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 .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([]);
});
- stop = await customDev({ cwd, port });
});
- test.afterAll(() => stop());
- test("Vite / Load context / express", async ({ page }) => {
- let pageErrors: unknown[] = [];
- page.on("pageerror", (error) => pageErrors.push(error));
+ test.describe("custom env dir", async () => {
+ let port: number;
+ let cwd: string;
+ let stop: () => void;
- await page.goto(`http://localhost:${port}/dotenv`, {
- waitUntil: "networkidle",
+ test.beforeAll(async () => {
+ const envDir = "custom-env-dir";
+ port = await getPort();
+ cwd = await createProject(await getFiles({ envDir, port }));
+ stop = await customDev({ cwd, port });
});
- expect(pageErrors).toEqual([]);
+ test.afterAll(() => stop());
+
+ test("express", async ({ page }) => {
+ let pageErrors: unknown[] = [];
+ page.on("pageerror", (error) => pageErrors.push(error));
- let loaderContent = page.locator("[data-dotenv-route-loader-content]");
- await expect(loaderContent).toHaveText("Content from .env file");
+ await page.goto(`http://localhost:${port}/dotenv`, {
+ waitUntil: "networkidle",
+ });
+ expect(pageErrors).toEqual([]);
- 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"
- );
+ let loaderContent = page.locator("[data-dotenv-route-loader-content]");
+ await expect(loaderContent).toHaveText(
+ "Content from custom-env-dir/.env file"
+ );
- expect(pageErrors).toEqual([]);
+ 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/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts
index a936096288..3ca46360d9 100644
--- a/packages/react-router-dev/vite/plugin.ts
+++ b/packages/react-router-dev/vite/plugin.ts
@@ -955,7 +955,7 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => {
process.env,
vite.loadEnv(
viteConfigEnv.mode,
- ctx.rootDirectory,
+ viteUserConfig.envDir ?? ctx.rootDirectory,
// We override default prefix of "VITE_" with a blank string since
// we're targeting the server, so we want to load all environment
// variables, not just those explicitly marked for the client