├── .eslintrc.json ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── env.mjs ├── examples ├── with-manual-implementation │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── env.d.ts │ ├── env.mjs │ ├── next.config.mjs │ ├── package.json │ ├── pages │ │ ├── _app.tsx │ │ └── index.tsx │ ├── postcss.config.js │ ├── public │ │ └── favicon.ico │ ├── styles │ │ └── globals.css │ ├── tailwind.config.js │ └── tsconfig.json └── with-package │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── env.d.ts │ ├── env.mjs │ ├── next.config.mjs │ ├── package.json │ ├── pages │ ├── _app.tsx │ └── index.tsx │ ├── postcss.config.js │ ├── public │ └── favicon.ico │ ├── styles │ └── globals.css │ ├── tailwind.config.js │ └── tsconfig.json ├── index.ts ├── package.json ├── rollup.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2018, 4 | "sourceType": "module" 5 | }, 6 | "extends": [ 7 | "plugin:prettier/recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "overrides": [{ "files": ["*.mjs", "*.ts", "*.tsx"] }] 11 | } 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jacobadevore 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### STANDARD GIT IGNORE FILE ### 2 | 3 | # DEPENDENCIES 4 | node_modules/ 5 | /.pnp 6 | .pnp.js 7 | package-lock.json 8 | yarn.lock 9 | 10 | # TESTING 11 | /coverage 12 | *.lcov 13 | .nyc_output 14 | 15 | # BUILD 16 | build/ 17 | public/build/ 18 | dist/ 19 | generated/ 20 | 21 | # ENV FILES 22 | .env 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # LOGS 29 | logs 30 | *.log 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | 35 | # MISC 36 | .idea 37 | .turbo/ 38 | .cache/ 39 | .next/ 40 | .nuxt/ 41 | tmp/ 42 | temp/ 43 | .docusaurus 44 | 45 | # MAC 46 | ._* 47 | .DS_Store 48 | Thumbs.db 49 | 50 | .turbo 51 | .vercel 52 | .rollup.cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jacob Devore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Next-ValidEnv

2 |

Typesafe environment variables for Next.js

3 |

4 | 5 | Bundle Size 6 | 7 | 8 | Downloads 9 | 10 | 11 | Github Stars 12 | 13 | 14 | Github Stable Release 16 | 17 |

18 |

Created by

19 |
20 |
Jacob Devore
23 |
24 | 25 | --- 26 | 27 | ### Installation 28 | 29 | ```sh 30 | npm install zod next-validenv 31 | 32 | yarn add zod next-validenv 33 | ``` 34 | 35 | ```sh 36 | npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint typescript 37 | 38 | yarn add --dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint typescript 39 | ``` 40 | 41 | ### First, modify `.eslintrc.json` 42 | 43 | Append `"extends"` with `"plugin:@typescript-eslint/recommended"` 44 | 45 | ```js 46 | { 47 | "extends": ["plugin:@typescript-eslint/recommended"] 48 | } 49 | ``` 50 | 51 | ### Create `env.mjs` 52 | 53 | Where your server and client schemas live for typesafe environment variables 54 | 55 | ```js 56 | //@ts-check 57 | import { validateEnvironmentVariables } from "next-validenv"; 58 | 59 | import { z } from "zod"; 60 | 61 | /** 62 | * Specify your environment variables schema here. 63 | * This way, you can ensure the app isn't built with invalid environment variables. 64 | * By default, environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. 65 | * -> Don't use any Zod .transform() methods in the schema (will cause `implicitly has type 'any'` error) <- 66 | */ 67 | export const schema = z.object({ 68 | NODE_ENV: z.enum(["development", "test", "production"]), 69 | }); 70 | 71 | /** 72 | * Environment variable declarations based on the schema help structure your environment variables programmatically. 73 | * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} 74 | */ 75 | export const env = { 76 | NODE_ENV: process.env.NODE_ENV, 77 | }; 78 | 79 | validateEnvironmentVariables(schema, env); 80 | ``` 81 | 82 | ### Update ~~`next.config.js`~~ to `next.config.mjs` 83 | 84 | ```js 85 | // @ts-check 86 | /** 87 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. 88 | * This is especially useful for Docker builds. 89 | */ 90 | !process.env.SKIP_ENV_VALIDATION && (await import("./env.mjs")); 91 | 92 | /** @type {import("next").NextConfig} */ 93 | const config = { 94 | reactStrictMode: true, 95 | }; 96 | export default config; 97 | ``` 98 | 99 | ### Create `env.d.ts` 100 | 101 | ```js 102 | import { env } from "./env.mjs"; 103 | 104 | type EnvType = typeof env; 105 | 106 | export {}; 107 | 108 | declare global { 109 | namespace NodeJS { 110 | interface ProcessEnv extends EnvType, NodeJS.ProcessEnv {} 111 | } 112 | } 113 | ``` 114 | 115 | ### That's it! Now you can use `process.env` and get typesafe environment variables 116 | 117 | ```js 118 | process.env.NODE_ENV; // Typesafe environment variables 119 | ``` 120 | 121 | --- 122 | 123 | ## Manual Implementation 124 | 125 | Follow the below guide to manually implement typesafe environment variables in Next.js without installing the Next-ValidEnv library 126 | 127 | ### Installation 128 | 129 | ```sh 130 | npm install zod 131 | 132 | yarn add zod 133 | ``` 134 | 135 | ```sh 136 | npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint typescript 137 | 138 | yarn add --dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint typescript 139 | ``` 140 | 141 | ### First, modify `.eslintrc.json` 142 | 143 | Append `"extends"` with `"plugin:@typescript-eslint/recommended"` 144 | 145 | ```js 146 | { 147 | "extends": ["plugin:@typescript-eslint/recommended"] 148 | } 149 | ``` 150 | 151 | ### Create `env.mjs` 152 | 153 | Where your server and client schemas live for typesafe environment variables 154 | 155 | ```js 156 | // @ts-check 157 | import { z } from "zod"; 158 | 159 | /** 160 | * Specify your environment variables schema here. 161 | * This way, you can ensure the app isn't built with invalid environment variables. 162 | * By default, environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. 163 | * -> Don't use any Zod .transform() methods in the schema (will cause `implicitly has type 'any'` error) <- 164 | */ 165 | export const schema = z.object({ 166 | NODE_ENV: z.enum(["development", "test", "production"]), 167 | }); 168 | 169 | /** 170 | * Environment variable declarations based on the schema help structure your environment variables programmatically. 171 | * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} 172 | */ 173 | export const env = { 174 | NODE_ENV: process.env.NODE_ENV, 175 | }; 176 | 177 | /** 178 | * -------------------------------- 179 | * -------------------------------- 180 | * Next-ValidEnv Manual Implementation 181 | * -------------------------------- 182 | * -------------------------------- 183 | */ 184 | 185 | export const formatZodErrors = ( 186 | /** @type z.ZodFormattedError, string> */ errors 187 | ) => 188 | Object.entries(errors) 189 | .map(([name, value]) => { 190 | if (value && "_errors" in value) 191 | return `${name}: ${value._errors.join(", ")}\n`; 192 | 193 | return; 194 | }) 195 | .filter(Boolean); 196 | 197 | const safeParsedEnv = schema.safeParse(env); 198 | 199 | if (!safeParsedEnv.success) { 200 | console.error( 201 | "❌ Invalid environment variables:\n", 202 | ...formatZodErrors(safeParsedEnv.error.format()) 203 | ); 204 | throw new Error("Invalid environment variables"); 205 | } 206 | ``` 207 | 208 | ### Update ~~`next.config.js`~~ to `next.config.mjs` 209 | 210 | ```js 211 | // @ts-check 212 | /** 213 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. 214 | * This is especially useful for Docker builds. 215 | */ 216 | !process.env.SKIP_ENV_VALIDATION && (await import("./env.mjs")); 217 | 218 | /** @type {import("next").NextConfig} */ 219 | const config = { 220 | reactStrictMode: true, 221 | }; 222 | export default config; 223 | ``` 224 | 225 | ### Create `env.d.ts` 226 | 227 | ```js 228 | import { env } from "./env.mjs"; 229 | 230 | type EnvType = typeof env; 231 | 232 | export {}; 233 | 234 | declare global { 235 | namespace NodeJS { 236 | interface ProcessEnv extends EnvType, NodeJS.ProcessEnv {} 237 | } 238 | } 239 | ``` 240 | 241 | ### That's it! Now you can use `process.env` and get typesafe environment variables 242 | 243 | ```js 244 | process.env.NODE_ENV; // Typesafe environment variables 245 | ``` 246 | -------------------------------------------------------------------------------- /env.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { z } from "zod"; 3 | 4 | /** 5 | * Specify your environment variables schema here. 6 | * This way, you can ensure the app isn't built with invalid environment variables. 7 | * By default, environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. 8 | * -> Don't use any Zod .transform() methods in the schema (will cause `implicitly has type 'any'` error) <- 9 | */ 10 | export const schema = z.object({ 11 | NODE_ENV: z.enum(["development", "test", "production"]), 12 | }); 13 | 14 | /** 15 | * Environment variable declarations based on the schema help structure your environment variables programmatically. 16 | * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} 17 | */ 18 | export const env = { 19 | NODE_ENV: process.env.NODE_ENV, 20 | }; 21 | 22 | /** 23 | * -------------------------------- 24 | * -------------------------------- 25 | * Next-ValidEnv Manual Implementation 26 | * -------------------------------- 27 | * -------------------------------- 28 | */ 29 | 30 | export const formatZodErrors = ( 31 | /** @type z.ZodFormattedError, string> */ errors 32 | ) => 33 | Object.entries(errors) 34 | .map(([name, value]) => { 35 | if (value && "_errors" in value) 36 | return `${name}: ${value._errors.join(", ")}\n`; 37 | 38 | return; 39 | }) 40 | .filter(Boolean); 41 | 42 | const safeParsedEnv = schema.safeParse(env); 43 | 44 | if (!safeParsedEnv.success) { 45 | console.error( 46 | "❌ Invalid environment variables:\n", 47 | ...formatZodErrors(safeParsedEnv.error.format()) 48 | ); 49 | throw new Error("Invalid environment variables"); 50 | } 51 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_TEST= -------------------------------------------------------------------------------- /examples/with-manual-implementation/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/env.d.ts: -------------------------------------------------------------------------------- 1 | import { env } from "./env.mjs"; 2 | 3 | type EnvType = typeof env; 4 | 5 | export {}; 6 | 7 | declare global { 8 | namespace NodeJS { 9 | interface ProcessEnv extends EnvType, NodeJS.ProcessEnv {} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/env.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { z } from "zod"; 3 | 4 | /** 5 | * Specify your environment variables schema here. 6 | * This way, you can ensure the app isn't built with invalid environment variables. 7 | * By default, environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. 8 | * -> Don't use any Zod .transform() methods in the schema (will cause `implicitly has type 'any'` error) <- 9 | */ 10 | export const schema = z.object({ 11 | NODE_ENV: z.enum(["development", "test", "production"]), 12 | NEXT_PUBLIC_TEST: z.string(), 13 | }); 14 | 15 | /** 16 | * Environment variable declarations based on the schema help structure your environment variables programmatically. 17 | * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} 18 | */ 19 | export const env = { 20 | NODE_ENV: process.env.NODE_ENV, 21 | NEXT_PUBLIC_TEST: process.env.NEXT_PUBLIC_TEST, 22 | }; 23 | 24 | /** 25 | * -------------------------------- 26 | * -------------------------------- 27 | * Next-ValidEnv Manual Implementation 28 | * -------------------------------- 29 | * -------------------------------- 30 | */ 31 | 32 | export const formatZodErrors = ( 33 | /** @type z.ZodFormattedError, string> */ errors 34 | ) => 35 | Object.entries(errors) 36 | .map(([name, value]) => { 37 | if (value && "_errors" in value) 38 | return `${name}: ${value._errors.join(", ")}\n`; 39 | 40 | return; 41 | }) 42 | .filter(Boolean); 43 | 44 | const safeParsedEnv = schema.safeParse(env); 45 | 46 | if (!safeParsedEnv.success) { 47 | console.error( 48 | "❌ Invalid environment variables:\n", 49 | ...formatZodErrors(safeParsedEnv.error.format()) 50 | ); 51 | throw new Error("Invalid environment variables"); 52 | } 53 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/next.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** 3 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. 4 | * This is especially useful for Docker builds. 5 | */ 6 | !process.env.SKIP_ENV_VALIDATION && (await import("./env.mjs")); 7 | 8 | /** @type {import("next").NextConfig} */ 9 | const config = { 10 | reactStrictMode: true, 11 | }; 12 | export default config; 13 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-manual-implementation", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@next/font": "13.1.2", 13 | "@types/node": "18.11.18", 14 | "@types/react": "18.0.26", 15 | "@types/react-dom": "18.0.10", 16 | "eslint": "^8.32.0", 17 | "eslint-config-next": "13.1.2", 18 | "next": "13.1.2", 19 | "react": "18.2.0", 20 | "react-dom": "18.2.0", 21 | "typescript": "^4.9.4", 22 | "zod": "^3.20.2" 23 | }, 24 | "devDependencies": { 25 | "@typescript-eslint/eslint-plugin": "^5.48.1", 26 | "@typescript-eslint/parser": "^5.48.1", 27 | "autoprefixer": "^10.4.13", 28 | "postcss": "^8.4.21", 29 | "tailwindcss": "^3.2.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { type AppType } from "next/dist/shared/lib/utils"; 2 | 3 | import "../styles/globals.css"; 4 | 5 | const MyApp: AppType = ({ Component, pageProps }) => { 6 | return ; 7 | }; 8 | 9 | export default MyApp; 10 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | 3 | export default function Home() { 4 | return ( 5 | <> 6 | 7 | 8 | Typesafe environment variables for Next.js - Next-ValidEnv 9 | 10 | 14 | 15 | 16 | 17 |
18 |
19 |

Next-ValidEnv

20 |

21 | Typesafe environment variables for Next.js 22 |

23 | 41 |
42 | 43 |
44 | 45 |
46 |

47 | By default environment variables are only available in the Node.js 48 | environment, meaning they won't be exposed to the browser. 49 |

50 |

51 | In order to expose a variable to the browser you have to prefix the 52 | variable with{" "} 53 | 54 | NEXT_PUBLIC_ 55 | 56 | . For example: 57 |

58 |
59 | 60 |
61 | 62 |

Typesafe environment variables:

63 | 64 |

{process.env.NEXT_PUBLIC_TEST}

65 |
66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacobADevore/next-validenv/849745110c8dfbe3b6214d42f8349b3bc894462d/examples/with-manual-implementation/public/favicon.ico -------------------------------------------------------------------------------- /examples/with-manual-implementation/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | background: #171717; 7 | color: #e5e5e5; 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /examples/with-manual-implementation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "noUncheckedIndexedAccess": true 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/with-package/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_TEST= -------------------------------------------------------------------------------- /examples/with-package/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/with-package/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/with-package/env.d.ts: -------------------------------------------------------------------------------- 1 | import { env } from "./env.mjs"; 2 | 3 | type EnvType = typeof env; 4 | 5 | export {}; 6 | 7 | declare global { 8 | namespace NodeJS { 9 | interface ProcessEnv extends EnvType, NodeJS.ProcessEnv {} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/with-package/env.mjs: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import { validateEnvironmentVariables } from "next-validenv"; 3 | 4 | import { z } from "zod"; 5 | 6 | /** 7 | * Specify your environment variables schema here. 8 | * This way, you can ensure the app isn't built with invalid environment variables. 9 | * By default, environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. 10 | * -> Don't use any Zod .transform() methods in the schema (will cause `implicitly has type 'any'` error) <- 11 | */ 12 | export const schema = z.object({ 13 | NODE_ENV: z.enum(["development", "test", "production"]), 14 | NEXT_PUBLIC_TEST: z.string(), 15 | }); 16 | 17 | /** 18 | * Environment variable declarations based on the schema help structure your environment variables programmatically. 19 | * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} 20 | */ 21 | export const env = { 22 | NODE_ENV: process.env.NODE_ENV, 23 | NEXT_PUBLIC_TEST: process.env.NEXT_PUBLIC_TEST, 24 | }; 25 | 26 | validateEnvironmentVariables(schema, env); 27 | -------------------------------------------------------------------------------- /examples/with-package/next.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** 3 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. 4 | * This is especially useful for Docker builds. 5 | */ 6 | !process.env.SKIP_ENV_VALIDATION && (await import("./env.mjs")); 7 | 8 | /** @type {import("next").NextConfig} */ 9 | const config = { 10 | reactStrictMode: true, 11 | }; 12 | export default config; 13 | -------------------------------------------------------------------------------- /examples/with-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-package", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@next/font": "13.1.2", 13 | "@types/node": "18.11.18", 14 | "@types/react": "18.0.26", 15 | "@types/react-dom": "18.0.10", 16 | "eslint": "^8.32.0", 17 | "eslint-config-next": "13.1.2", 18 | "next": "13.1.2", 19 | "next-validenv": "^1.2.2", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "typescript": "^4.9.4" 23 | }, 24 | "devDependencies": { 25 | "@typescript-eslint/eslint-plugin": "^5.48.1", 26 | "@typescript-eslint/parser": "^5.48.1", 27 | "autoprefixer": "^10.4.13", 28 | "postcss": "^8.4.21", 29 | "tailwindcss": "^3.2.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/with-package/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { type AppType } from "next/dist/shared/lib/utils"; 2 | 3 | import "../styles/globals.css"; 4 | 5 | const MyApp: AppType = ({ Component, pageProps }) => { 6 | return ; 7 | }; 8 | 9 | export default MyApp; 10 | -------------------------------------------------------------------------------- /examples/with-package/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | 3 | export default function Home() { 4 | return ( 5 | <> 6 | 7 | 8 | Typesafe environment variables for Next.js - Next-ValidEnv 9 | 10 | 14 | 15 | 16 | 17 |
18 |
19 |

Next-ValidEnv

20 |

21 | Typesafe environment variables for Next.js 22 |

23 | 41 |
42 | 43 |
44 | 45 |
46 |

47 | By default environment variables are only available in the Node.js 48 | environment, meaning they won't be exposed to the browser. 49 |

50 |

51 | In order to expose a variable to the browser you have to prefix the 52 | variable with{" "} 53 | 54 | NEXT_PUBLIC_ 55 | 56 | . For example: 57 |

58 |
59 | 60 |
61 | 62 |

Typesafe environment variables:

63 | 64 |

{process.env.NEXT_PUBLIC_TEST}

65 |
66 | 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /examples/with-package/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/with-package/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JacobADevore/next-validenv/849745110c8dfbe3b6214d42f8349b3bc894462d/examples/with-package/public/favicon.ico -------------------------------------------------------------------------------- /examples/with-package/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | background: #171717; 7 | color: #e5e5e5; 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-package/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /examples/with-package/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "noUncheckedIndexedAccess": true 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const formatZodErrors = ( 4 | errors: z.ZodFormattedError, string> 5 | ) => 6 | Object.entries(errors) 7 | .map(([name, value]) => { 8 | if (value && "_errors" in value) 9 | return `${name}: ${value._errors.join(", ")}\n`; 10 | 11 | return; 12 | }) 13 | .filter(Boolean); 14 | 15 | export const validateEnvironmentVariables = < 16 | T extends ReturnType, 17 | K 18 | >( 19 | schema: T, 20 | unparsedEnv: K 21 | ) => { 22 | const env = schema.safeParse(unparsedEnv); 23 | 24 | if (!env.success) { 25 | console.error( 26 | "❌ Invalid environment variables:\n", 27 | ...formatZodErrors(env.error.format()) 28 | ); 29 | throw new Error("Invalid environment variables"); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-validenv", 3 | "version": "1.2.2", 4 | "author": "Jacob Devore ", 5 | "description": "Typesafe environment variables for Next.js", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/JacobADevore/next-validenv.git" 10 | }, 11 | "keywords": [ 12 | "react", 13 | "nodejs", 14 | "web", 15 | "nextjs", 16 | "next-validenv", 17 | "env", 18 | "env-validation", 19 | "zod", 20 | "typescript" 21 | ], 22 | "type": "module", 23 | "files": [ 24 | "/dist" 25 | ], 26 | "exports": { 27 | ".": { 28 | "require": "./dist/index.js", 29 | "import": "./dist/index.mjs", 30 | "types": "./dist/index.d.ts" 31 | }, 32 | "./package.json": "./package.json" 33 | }, 34 | "main": "./dist/index.mjs", 35 | "module": "./dist/index.mjs", 36 | "types": "./dist/index.d.ts", 37 | "scripts": { 38 | "lint": "eslint ./index.mjs --report-unused-disable-directives", 39 | "format": "prettier --write --plugin-search-dir=. **/*.{mjs,md,json} --ignore-path ../.gitignore", 40 | "format:check": "prettier --check --plugin-search-dir=. **/*.{mjs,md,json} --ignore-path ../.gitignore", 41 | "pub:beta": "npm publish --tag beta", 42 | "pub:next": "npm publish --tag next", 43 | "pub:release": "npm publish", 44 | "build": "rollup --config rollup.config.js", 45 | "prepublishOnly": "npm run build" 46 | }, 47 | "devDependencies": { 48 | "@rollup/plugin-typescript": "^11.0.0", 49 | "@types/node": "^18.11.18", 50 | "@typescript-eslint/eslint-plugin": "^5.48.1", 51 | "@typescript-eslint/parser": "^5.48.1", 52 | "eslint": "^8.32.0", 53 | "eslint-config-prettier": "^8.6.0", 54 | "eslint-plugin-prettier": "^4.2.1", 55 | "prettier": "^2.8.2", 56 | "rollup": "^3.10.0", 57 | "tslib": "^2.4.1", 58 | "typescript": "^4.9.4", 59 | "zod": "^3.20.2" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import typescript from "@rollup/plugin-typescript"; 3 | 4 | export default [ 5 | { 6 | input: "index.ts", 7 | output: [ 8 | { 9 | file: "dist/index.mjs", 10 | format: "es", 11 | sourcemap: true, 12 | }, 13 | ], 14 | plugins: [ 15 | typescript({ 16 | tsconfig: "tsconfig.json", 17 | sourceMap: true, 18 | }), 19 | ], 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "outDir": "./dist", 5 | "declaration": true, 6 | "declarationMap": false, 7 | "emitDeclarationOnly": true, 8 | "sourceMap": false, 9 | "lib": ["es5", "es6", "es7", "esnext", "dom"], 10 | "target": "es2018", 11 | "removeComments": false, 12 | "esModuleInterop": true, 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "strict": true, 16 | "skipLibCheck": true, 17 | "strictPropertyInitialization": false, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "downlevelIteration": true, 23 | "isolatedModules": true 24 | }, 25 | "include": ["index.ts"] 26 | } 27 | --------------------------------------------------------------------------------