diff --git a/README.md b/README.md index 4ad778fb..dee3b42c 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ - ⚙️ EditorConfig - Consistent coding styles across editors and IDEs - 🗂 Path Mapping — Import components or images using the `@` prefix - 🔐 CSP — Content Security Policy for enhanced security (default minimal policy) +- 🧳 T3 Env — Type-safe environment variables ## Quick Start @@ -127,6 +128,12 @@ This starter uses pnpm by default, but this choice is yours. If you'd like to sw > **Note:** If you use Yarn, make sure to follow these steps from the [Husky documentation](https://typicode.github.io/husky/troubleshoot.html#yarn-on-windows) so that Git hooks do not fail with Yarn on Windows. +### Environment Variables + +We use [T3 Env](https://env.t3.gg/) to manage environment variables. Create a `.env.local` file in the root of the project and add your environment variables there. + +When adding additional environment variables, the schema in `./src/lib/env/client.ts` or `./src/lib/env/server.ts` should be updated accordingly. + ## License This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for more information. diff --git a/next.config.ts b/next.config.ts index 66a7cacf..5ad529aa 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,5 +1,8 @@ import type { NextConfig } from 'next'; +import './src/lib/env/client'; +import './src/lib/env/server'; + /** * CSPs that we're not adding (as it can change from project to project): * frame-src, connect-src, script-src, child-src, style-src, worker-src, font-src, media-src, and img-src diff --git a/package.json b/package.json index ff64caa4..907988d2 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,11 @@ ] }, "dependencies": { + "@t3-oss/env-nextjs": "0.11.1", "next": "15.1.2", "react": "19.0.0", - "react-dom": "19.0.0" + "react-dom": "19.0.0", + "zod": "3.24.1" }, "devDependencies": { "@commitlint/cli": "19.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87e28767..421658ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@t3-oss/env-nextjs': + specifier: 0.11.1 + version: 0.11.1(typescript@5.7.2)(zod@3.24.1) next: specifier: 15.1.2 version: 15.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -17,6 +20,9 @@ importers: react-dom: specifier: 19.0.0 version: 19.0.0(react@19.0.0) + zod: + specifier: 3.24.1 + version: 3.24.1 devDependencies: '@commitlint/cli': specifier: 19.6.1 @@ -459,6 +465,24 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@t3-oss/env-core@0.11.1': + resolution: {integrity: sha512-MaxOwEoG1ntCFoKJsS7nqwgcxLW1SJw238AJwfJeaz3P/8GtkxXZsPPolsz1AdYvUTbe3XvqZ/VCdfjt+3zmKw==} + peerDependencies: + typescript: '>=5.0.0' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@t3-oss/env-nextjs@0.11.1': + resolution: {integrity: sha512-rx2XL9+v6wtOqLNJbD5eD8OezKlQD1BtC0WvvtHwBgK66jnF5+wGqtgkKK4Ygie1LVmoDClths2T4tdFmRvGrQ==} + peerDependencies: + typescript: '>=5.0.0' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@types/conventional-commits-parser@5.0.0': resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} @@ -1987,6 +2011,9 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} @@ -2362,6 +2389,19 @@ snapshots: dependencies: tslib: 2.8.1 + '@t3-oss/env-core@0.11.1(typescript@5.7.2)(zod@3.24.1)': + dependencies: + zod: 3.24.1 + optionalDependencies: + typescript: 5.7.2 + + '@t3-oss/env-nextjs@0.11.1(typescript@5.7.2)(zod@3.24.1)': + dependencies: + '@t3-oss/env-core': 0.11.1(typescript@5.7.2)(zod@3.24.1) + zod: 3.24.1 + optionalDependencies: + typescript: 5.7.2 + '@types/conventional-commits-parser@5.0.0': dependencies: '@types/node': 22.10.2 @@ -4107,3 +4147,5 @@ snapshots: yocto-queue@0.1.0: {} yocto-queue@1.0.0: {} + + zod@3.24.1: {} diff --git a/src/lib/env/client.ts b/src/lib/env/client.ts new file mode 100644 index 00000000..9f795cb2 --- /dev/null +++ b/src/lib/env/client.ts @@ -0,0 +1,22 @@ +import { createEnv } from '@t3-oss/env-nextjs'; + +export const clientEnv = createEnv({ + /** + * Specify your client-side environment variables schema here. This way you can ensure the app + * isn't built with invalid env vars. To expose them to the client, prefix them with + * `NEXT_PUBLIC_`. + */ + client: {}, + + experimental__runtimeEnv: {}, + /** + * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially + * useful for Docker builds. + */ + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + /** + * Makes it so that empty strings are treated as undefined. + * `SOME_VAR: z.string()` and `SOME_VAR=''` will throw an error. + */ + emptyStringAsUndefined: true, +}); diff --git a/src/lib/env/server.ts b/src/lib/env/server.ts new file mode 100644 index 00000000..340d0701 --- /dev/null +++ b/src/lib/env/server.ts @@ -0,0 +1,11 @@ +import { createEnv } from '@t3-oss/env-nextjs'; +import { z } from 'zod'; + +export const serverEnv = createEnv({ + server: { + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), + }, + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + emptyStringAsUndefined: true, + experimental__runtimeEnv: {}, +});