From c73966cef4f368d22b3068952722063e48fc63c9 Mon Sep 17 00:00:00 2001 From: Thomas Fritz Date: Mon, 24 Mar 2025 16:04:23 +0100 Subject: [PATCH 1/2] lazy init of env --- .env.example | 5 ++++- app/env.server.ts | 36 +++++++++++++++++++--------------- app/routes/resource.locales.ts | 4 ++-- app/routes/robots[.]txt.ts | 3 +-- app/server/context.ts | 12 +++++------- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/.env.example b/.env.example index 7764bacf6..fe917fe69 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,5 @@ # Add your env variables here -APP_DEPLOYMENT_ENV="staging" + +# The environment of the app. This is different from NODE_ENV, and will be used for deployments. +# Default: development +#APP_ENV="staging" diff --git a/app/env.server.ts b/app/env.server.ts index f88d9a298..e777723a3 100644 --- a/app/env.server.ts +++ b/app/env.server.ts @@ -1,18 +1,18 @@ import { z } from "zod" const envSchema = z.object({ - NODE_ENV: z.enum(["development", "production", "test"]), - APP_DEPLOYMENT_ENV: z.enum(["staging", "production"]), + NODE_ENV: z.enum(["development", "production", "test"]).default("development"), + APP_ENV: z.enum(["development", "staging", "production"]).default("development"), }) -type APP_ENV = z.infer -let env: APP_ENV +type ServerEnv = z.infer +let env: ServerEnv + /** - * Helper method used for initializing .env vars in your entry.server.ts file. It uses - * zod to validate your .env and throws if it's not valid. + * Initializes and parses given environment variables using zod * @returns Initialized env vars */ -export const initEnv = () => { +export function initEnv() { // biome-ignore lint/nursery/noProcessEnv: This should be the only place to use process.env directly const envData = envSchema.safeParse(process.env) @@ -23,35 +23,39 @@ export const initEnv = () => { } env = envData.data + Object.freeze(env) // Do not log the message when running tests if (env.NODE_ENV !== "test") { // biome-ignore lint/suspicious/noConsole: We want this to be logged console.log("✅ Environment variables loaded successfully") } - return envData.data + return env +} + +export function getServerEnv() { + if (env) return env + return initEnv() } /** - * Helper method for you to return client facing .env vars, only return vars that are needed on the client. + * Helper function which returns a subset of the environment vars which are safe expose to the client. + * Dont expose any secrets or sensitive data here. * Otherwise you would expose your server vars to the client if you returned them from here as this is * directly sent in the root to the client and set on the window.env * @returns Subset of the whole process.env to be passed to the client and used there */ -export const getClientEnv = () => { - const serverEnv = env +export function getClientEnv() { + const serverEnv = getServerEnv() return { NODE_ENV: serverEnv.NODE_ENV, } } -type CLIENT_ENV = ReturnType +type ClientEnvVars = ReturnType declare global { interface Window { - env: CLIENT_ENV - } - namespace NodeJS { - interface ProcessEnv extends APP_ENV {} + env: ClientEnvVars } } diff --git a/app/routes/resource.locales.ts b/app/routes/resource.locales.ts index 1a05c377f..8c889ad7e 100644 --- a/app/routes/resource.locales.ts +++ b/app/routes/resource.locales.ts @@ -4,7 +4,7 @@ import { resources } from "~/localization/resource" import type { Route } from "./+types/resource.locales" export async function loader({ request, context }: Route.LoaderArgs) { - const { env } = context + const { isProductionDeployment } = context const url = new URL(request.url) const lng = z @@ -24,7 +24,7 @@ export async function loader({ request, context }: Route.LoaderArgs) { const headers = new Headers() // On production, we want to add cache headers to the response - if (env.APP_DEPLOYMENT_ENV === "production") { + if (isProductionDeployment) { headers.set( "Cache-Control", cacheHeader({ diff --git a/app/routes/robots[.]txt.ts b/app/routes/robots[.]txt.ts index b78c674dc..548d1e948 100644 --- a/app/routes/robots[.]txt.ts +++ b/app/routes/robots[.]txt.ts @@ -4,8 +4,7 @@ import { createDomain } from "~/utils/http" import type { Route } from "./+types/robots[.]txt" export async function loader({ request, context }: Route.LoaderArgs) { - const { env } = context - const isProductionDeployment = env.APP_DEPLOYMENT_ENV === "production" + const { isProductionDeployment } = context const domain = createDomain(request) const robotsTxt = generateRobotsTxt([ { diff --git a/app/server/context.ts b/app/server/context.ts index e8ae48cf9..e2825bc1b 100644 --- a/app/server/context.ts +++ b/app/server/context.ts @@ -1,22 +1,20 @@ import type { Context } from "hono" import { i18next } from "remix-hono/i18next" -import { getClientEnv, initEnv } from "~/env.server" - -// Setup the .env vars -const env = initEnv() +import { getClientEnv, getServerEnv } from "~/env.server" export const getLoadContext = async (c: Context) => { // get the locale from the context const locale = i18next.getLocale(c) // get t function for the default namespace const t = await i18next.getFixedT(c) + // get the server environment + const env = getServerEnv() - const clientEnv = getClientEnv() return { lang: locale, t, - env, - clientEnv, + isProductionDeployment: env.APP_ENV === "production", + clientEnv: getClientEnv(), // We do not add this to AppLoadContext type because it's not needed in the loaders, but it's used above to handle requests body: c.body, } From 5910cf8fc46395dcd7aa66ddcb50b594b0412e28 Mon Sep 17 00:00:00 2001 From: Thomas Fritz Date: Mon, 24 Mar 2025 16:06:29 +0100 Subject: [PATCH 2/2] added env --- app/server/context.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/server/context.ts b/app/server/context.ts index e2825bc1b..ddf43b2b8 100644 --- a/app/server/context.ts +++ b/app/server/context.ts @@ -14,6 +14,7 @@ export const getLoadContext = async (c: Context) => { lang: locale, t, isProductionDeployment: env.APP_ENV === "production", + env, clientEnv: getClientEnv(), // We do not add this to AppLoadContext type because it's not needed in the loaders, but it's used above to handle requests body: c.body,