Comment avoir des variables d'environnement typées avec Zod

Apprends à valider et typer tes variables d'environnement avec Zod pour sécuriser et simplifier l’authentification dans React Router 7.

4 min read
Déverrouillez votre potentiel

avec React Router 7

Vous en avez marre de...

❌ perdre du temps à chercher des informations éparpillées
❌ ne pas avoir de retour sur votre progression
Assistant IA spécialisé

Posez vos questions 24/7 à notre IA experte en React Router 7

Quiz interactifs

Validez vos acquis avec des quiz personnalisés et un feedback instantané

9 modules
45 leçons
Accès à vie
299.00
-50%

Pourquoi typer tes variables d’environnement ?

Une app full-stack a besoin de secrets : URL de BDD, clés SMTP, token Stripe... Si l’une de ces valeurs manque ou contient une coquille, ta mise en prod crashe au premier appel réseau - au pire tu exposes tes credentials côté client. La solution ? Valider et typer dès le démarrage grâce à Zod.

Objectifs de la leçon :

  1. Valider process.env au boot et provoquer un crash explicite en cas d’oubli.
  2. Profiter de l’autocomplétion TypeScript sur process.env.
  3. Séparer les variables privées de celles qu’on peut transmettre au navigateur.
  4. Exposer un hook useEnv() pour lire les variables « safe » partout côté client.

Définir un schéma Zod pour le serveur

On place toute la logique dans app/server/env.server.ts.

app/server/env.server.ts
1
import { z } from "zod";
2
3
const envServerSchema = z.object({
4
DATABASE_URL: z.string().min(4),
5
SMTP_HOST: z.string().min(4),
6
SMTP_PORT: z.coerce.number(),
7
SMTP_USER: z.string().min(4),
8
SMTP_PASSWORD:z.string().min(4),
9
FRONTEND_URL: z.string().url(),
10
});
11
12
export const serverEnv = envServerSchema.parse(process.env);
  1. parse(process.env) déclenche une erreur détaillée si une clé est absente ou trop courte.
  2. z.coerce.number() transforme "465" en 465, pratique pour les valeurs numériques reçues en string.

Déclaration de type globale

Pour bénéficier d’un auto-complétion sur process.env, on étend le type fourni par Node :

app/server/env.server.ts
1
declare global {
2
namespace NodeJS {
3
interface ProcessEnv extends z.infer<typeof envServerSchema> {}
4
}
5
}

Tu n’écriras plus jamais process.env.SMTP_HOST ?? "" : TypeScript sait que la valeur est présente et de type string.


Ne jamais envoyer un secret au navigateur

On ne veut pas voir DATABASE_URL ou SMTP_PASSWORD dans les DevTools ! Seule la variable FRONTEND_URL est inoffensive. On la transmet dans le loader root :

app/root.tsx
1
export async function loader({ request }: LoaderFunctionArgs) {
2
const user = await getOptionalUser({ request });
3
4
return data({
5
user,
6
env: {
7
FRONTEND_URL: serverEnv.FRONTEND_URL, // 🔐 une seule clé autorisée
8
},
9
});
10
}

Un hook useEnv() ultra simple

app/root.tsx
1
import { useRouteLoaderData } from "react-router";
2
3
export function useEnv() {
4
const data = useRouteLoaderData<typeof loader>("root");
5
return data?.env ?? null; // Renvoie { FRONTEND_URL } ou null
6
}

Utilisation :

app/routes/login.tsx
1
const { FRONTEND_URL } = useEnv() ?? {};

Aucune dépendance contextuelle, pas de state local : React Router fournit déjà la donnée hydratée depuis le SSR.


Typescript + Zod = fin des mauvaises surprises

ts prisma/seed.ts
1
- const url = process.env.DATABSE_URL // 🙀 typo silencieuse
2
+ const url = serverEnv.DATABASE_URL // 🛑 erreur de compile

Un slug mal orthographié est détecté à la compilation, pas en production.


Crash early plutôt que production cassée

Ajoute volontairement une clé manquante dans .env, puis lance :

Terminal
1
npm run dev
1
❌ [env.server] Invalid environment variables:
2
DATABASE_URL is required

L’app refuse de démarrer – tu corriges avant le déploiement. C’est bien moins coûteux qu’un Cannot connect to "undefined" reçu par tes utilisateurs !


Exemple complet

1
--- app/server/env.server.ts
2
import { z } from "zod";
3
4
const envServerSchema = z.object({
5
DATABASE_URL: z.string().url(),
6
FRONTEND_URL: z.string().url(),
7
SMTP_HOST: z.string(),
8
SMTP_PORT: z.coerce.number(),
9
SMTP_USER: z.string(),
10
SMTP_PASSWORD:z.string(),
11
});
12
13
export const serverEnv = envServerSchema.parse(process.env);
14
15
declare global {
16
namespace NodeJS {
17
interface ProcessEnv extends z.infer<typeof envServerSchema> {}
18
}
19
}
20
--- app/root.tsx
21
export async function loader({ request }) {
22
const user = await getOptionalUser({ request });
23
return data({
24
user,
25
env: { FRONTEND_URL: serverEnv.FRONTEND_URL },
26
});
27
}
28
29
export function useEnv() {
30
const data = useRouteLoaderData<typeof loader>("root");
31
return data?.env ?? null;
32
}
33
--- app/routes/login.tsx
34
const { FRONTEND_URL } = useEnv() ?? {};
35
console.log("URL publique :", FRONTEND_URL);

Points clés à retenir

  • Zod valide process.env au boot : mieux vaut un crash immédiat qu’un bug obscur en prod.
  • serverEnv te donne l’autocomplétion et garantit l’existence des clés.
  • Sépare strictement variables privées (server only) et publiques (envoyées dans le loader).
  • Un hook useEnv() rend les variables publiques accessibles partout côté client, sans context API.
  • Déclaration globale sur ProcessEnv = fin des fautes de frappe.

Pour aller plus loin

Premium
Quiz interactif
Testez vos connaissances et validez votre compréhension du module avec notre quiz interactif.
1

Comprendre les concepts fondamentaux

Quelle est la principale différence entre les composants client et serveur dans React ?

Les composants client s'exécutent uniquement dans le navigateur
Les composants serveur peuvent utiliser useState
Les composants client sont plus rapides
Il n'y a aucune différence significative
2

Optimisation des performances

Quelle technique est recommandée pour éviter les rendus inutiles dans React ?

Utiliser React.memo pour les composants fonctionnels
Ajouter plus d'états locaux
Éviter d'utiliser les props
Toujours utiliser les class components
3

Architecture des données

Quel hook permet de gérer les effets de bord dans un composant React ?

useEffect
useState
useMemo
useContext
4

Gestion des erreurs

Comment implémenter la gestion des erreurs pour les requêtes API dans React ?

Utiliser try/catch avec async/await
Ignorer les erreurs
Toujours afficher un message d'erreur
Rediriger l'utilisateur
5

Déploiement et CI/CD

Quelle est la meilleure pratique pour déployer une application React en production ?

Utiliser un service CI/CD comme GitHub Actions
Copier les fichiers manuellement via FTP
Envoyer le code source complet
Ne jamais mettre à jour l'application

Débloquez ce quiz et tous les autres contenus premium en achetant ce cours