Skip to content

Commit a585f4e

Browse files
authored
Lazy init env (#38)
# Description - Lazily initialize and process environment variables - Set default values for `NODE_ENV` and `APP_ENV` for local development ## Type of change Please mark relevant options with an `x` in the brackets. - [x] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update - [ ] Algorithm update - updates algorithm documentation/questions/answers etc. - [ ] Other (please describe): # How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration - [ ] Integration tests - [x] Unit tests - [x] Manual tests - [ ] No tests required
1 parent 161bcf2 commit a585f4e

File tree

5 files changed

+32
-27
lines changed

5 files changed

+32
-27
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# Add your env variables here
2-
APP_DEPLOYMENT_ENV="staging"
2+
3+
# The environment of the app. This is different from NODE_ENV, and will be used for deployments.
4+
# Default: development
5+
#APP_ENV="staging"

app/env.server.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { z } from "zod"
22

33
const envSchema = z.object({
4-
NODE_ENV: z.enum(["development", "production", "test"]),
5-
APP_DEPLOYMENT_ENV: z.enum(["staging", "production"]),
4+
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
5+
APP_ENV: z.enum(["development", "staging", "production"]).default("development"),
66
})
77

8-
type APP_ENV = z.infer<typeof envSchema>
9-
let env: APP_ENV
8+
type ServerEnv = z.infer<typeof envSchema>
9+
let env: ServerEnv
10+
1011
/**
11-
* Helper method used for initializing .env vars in your entry.server.ts file. It uses
12-
* zod to validate your .env and throws if it's not valid.
12+
* Initializes and parses given environment variables using zod
1313
* @returns Initialized env vars
1414
*/
15-
export const initEnv = () => {
15+
export function initEnv() {
1616
// biome-ignore lint/nursery/noProcessEnv: This should be the only place to use process.env directly
1717
const envData = envSchema.safeParse(process.env)
1818

@@ -23,35 +23,39 @@ export const initEnv = () => {
2323
}
2424

2525
env = envData.data
26+
Object.freeze(env)
2627

2728
// Do not log the message when running tests
2829
if (env.NODE_ENV !== "test") {
2930
// biome-ignore lint/suspicious/noConsole: We want this to be logged
3031
console.log("✅ Environment variables loaded successfully")
3132
}
32-
return envData.data
33+
return env
34+
}
35+
36+
export function getServerEnv() {
37+
if (env) return env
38+
return initEnv()
3339
}
3440

3541
/**
36-
* Helper method for you to return client facing .env vars, only return vars that are needed on the client.
42+
* Helper function which returns a subset of the environment vars which are safe expose to the client.
43+
* Dont expose any secrets or sensitive data here.
3744
* Otherwise you would expose your server vars to the client if you returned them from here as this is
3845
* directly sent in the root to the client and set on the window.env
3946
* @returns Subset of the whole process.env to be passed to the client and used there
4047
*/
41-
export const getClientEnv = () => {
42-
const serverEnv = env
48+
export function getClientEnv() {
49+
const serverEnv = getServerEnv()
4350
return {
4451
NODE_ENV: serverEnv.NODE_ENV,
4552
}
4653
}
4754

48-
type CLIENT_ENV = ReturnType<typeof getClientEnv>
55+
type ClientEnvVars = ReturnType<typeof getClientEnv>
4956

5057
declare global {
5158
interface Window {
52-
env: CLIENT_ENV
53-
}
54-
namespace NodeJS {
55-
interface ProcessEnv extends APP_ENV {}
59+
env: ClientEnvVars
5660
}
5761
}

app/routes/resource.locales.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { resources } from "~/localization/resource"
44
import type { Route } from "./+types/resource.locales"
55

66
export async function loader({ request, context }: Route.LoaderArgs) {
7-
const { env } = context
7+
const { isProductionDeployment } = context
88
const url = new URL(request.url)
99

1010
const lng = z
@@ -24,7 +24,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
2424
const headers = new Headers()
2525

2626
// On production, we want to add cache headers to the response
27-
if (env.APP_DEPLOYMENT_ENV === "production") {
27+
if (isProductionDeployment) {
2828
headers.set(
2929
"Cache-Control",
3030
cacheHeader({

app/routes/robots[.]txt.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { createDomain } from "~/utils/http"
44
import type { Route } from "./+types/robots[.]txt"
55

66
export async function loader({ request, context }: Route.LoaderArgs) {
7-
const { env } = context
8-
const isProductionDeployment = env.APP_DEPLOYMENT_ENV === "production"
7+
const { isProductionDeployment } = context
98
const domain = createDomain(request)
109
const robotsTxt = generateRobotsTxt([
1110
{

app/server/context.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
import type { Context } from "hono"
22
import { i18next } from "remix-hono/i18next"
3-
import { getClientEnv, initEnv } from "~/env.server"
4-
5-
// Setup the .env vars
6-
const env = initEnv()
3+
import { getClientEnv, getServerEnv } from "~/env.server"
74

85
export const getLoadContext = async (c: Context) => {
96
// get the locale from the context
107
const locale = i18next.getLocale(c)
118
// get t function for the default namespace
129
const t = await i18next.getFixedT(c)
10+
// get the server environment
11+
const env = getServerEnv()
1312

14-
const clientEnv = getClientEnv()
1513
return {
1614
lang: locale,
1715
t,
16+
isProductionDeployment: env.APP_ENV === "production",
1817
env,
19-
clientEnv,
18+
clientEnv: getClientEnv(),
2019
// We do not add this to AppLoadContext type because it's not needed in the loaders, but it's used above to handle requests
2120
body: c.body,
2221
}

0 commit comments

Comments
 (0)