Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -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"
36 changes: 20 additions & 16 deletions app/env.server.ts
Original file line number Diff line number Diff line change
@@ -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<typeof envSchema>
let env: APP_ENV
type ServerEnv = z.infer<typeof envSchema>
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)

Expand All @@ -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<typeof getClientEnv>
type ClientEnvVars = ReturnType<typeof getClientEnv>

declare global {
interface Window {
env: CLIENT_ENV
}
namespace NodeJS {
interface ProcessEnv extends APP_ENV {}
env: ClientEnvVars
}
}
4 changes: 2 additions & 2 deletions app/routes/resource.locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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({
Expand Down
3 changes: 1 addition & 2 deletions app/routes/robots[.]txt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([
{
Expand Down
11 changes: 5 additions & 6 deletions app/server/context.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
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,
isProductionDeployment: env.APP_ENV === "production",
env,
clientEnv,
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,
}
Expand Down