|
3 | 3 | </br> |
4 | 4 | Nuxt Safe Runtime Config</h1> |
5 | 5 | <p align="center"> |
6 | | -Validate Nuxt runtime config at build or runtime using <b>Zod</b>, <b>Valibot</b>, <b>ArkType</b>, or any Standard Schema compatible library. |
| 6 | +Validate Nuxt runtime config at build time using <b>Zod</b>, <b>Valibot</b>, <b>ArkType</b>, or any Standard Schema compatible library. |
7 | 7 | </p> |
8 | 8 | <br/> |
9 | 9 |
|
10 | 10 | <p align="center"> |
11 | | - <a href="https://www.npmjs.com/package/nuxt-safe-runtime-config"><img src="https://img.shields.io/npm/v/nuxt-safe-runtime-config.svg" alt="npm version" /></a> |
12 | | - <a href="https://www.npmjs.com/package/nuxt-safe-runtime-config"><img src="https://img.shields.io/npm/dm/nuxt-safe-runtime-config.svg" alt="npm downloads" /></a> |
13 | | - <a href="https://github.com/onmax/nuxt-safe-runtime-config/blob/main/LICENSE"><img src="https://img.shields.io/github/license/onmax/nuxt-safe-runtime-config.svg" alt="License" /></a> |
14 | | - <a href="https://nuxt.com"><img src="https://img.shields.io/badge/Nuxt-3.0+-00DC82.svg" alt="Nuxt" /></a> |
15 | | -</p> |
| 11 | + <a href="https://www.npmjs.com/package/nuxt-safe-runtime-config"> |
| 12 | + <img src="https://img.shields.io/npm/v/nuxt-safe-runtime-config.svg" alt="npm version" /> |
| 13 | + </a> |
| 14 | + <a href="https://www.npmjs.com/package/nuxt-safe-runtime-config"> |
| 15 | + <img src="https://img.shields.io/npm/dm/nuxt-safe-runtime-config.svg" alt="npm downloads" /> |
| 16 | + </a> |
| 17 | + <a href="https://github.com/onmax/nuxt-safe-runtime-config/blob/main/LICENSE"> |
| 18 | + <img src="https://img.shields.io/github/license/onmax/nuxt-safe-runtime-config.svg" alt="License" /> |
| 19 | + </a> |
| 20 | + <a href="https://nuxt.com"> |
| 21 | + <img src="https://img.shields.io/badge/Nuxt-3.0+-00DC82.svg" alt="Nuxt" /> |
| 22 | + </a> |
16 | 23 |
|
17 | | -<p align="center"> |
18 | | - <a href="https://nuxt-safe-runtime-config.vercel.app">Documentation</a> |
| 24 | + <p align="center"> |
| 25 | + <a href="https://github.com/nuxt/nuxt/discussions/32301"> |
| 26 | + 🔗 Related Nuxt RFC: Enable Standard Schema Validation in Nuxt Config |
| 27 | + </a> |
| 28 | + </p> |
19 | 29 | </p> |
20 | 30 |
|
21 | 31 | ## Features |
22 | 32 |
|
23 | | -- **Build-time validation** with Zod, Valibot, ArkType, or any [Standard Schema](https://standardschema.dev/) library |
24 | | -- **Runtime validation** (opt-in) validates config when the server starts |
25 | | -- **Auto-generated types** - `useSafeRuntimeConfig()` is fully typed |
26 | | -- **ESLint plugin** warns when using `useRuntimeConfig()` instead of the type-safe composable |
| 33 | +- 🔒 **Build-time validation** with Zod, Valibot, ArkType, or any [Standard Schema](https://standardschema.dev/) library |
| 34 | +- 🚀 **Runtime validation** (opt-in) validates config when the server starts |
| 35 | +- ✨ **Auto-generated types** — `useSafeRuntimeConfig()` is fully typed without manual generics |
| 36 | +- 🛠 **ESLint plugin** warns when using `useRuntimeConfig()` instead of the type-safe composable |
| 37 | +- ⚡ **Zero runtime overhead** by default — validation happens at build time only |
| 38 | +- 🔐 **Shelve integration** — fetch secrets from [Shelve](https://shelve.cloud) and merge into runtime config |
27 | 39 |
|
28 | | -## Quick Start |
| 40 | +## Quick Setup |
| 41 | + |
| 42 | +Install the module: |
29 | 43 |
|
30 | 44 | ```bash |
31 | 45 | npx nuxi module add nuxt-safe-runtime-config |
32 | 46 | ``` |
33 | 47 |
|
34 | | -```ts [nuxt.config.ts] |
| 48 | +## Usage |
| 49 | + |
| 50 | +### 1. Define your schema |
| 51 | + |
| 52 | +Use Zod, Valibot, ArkType, or any Standard Schema compatible library: |
| 53 | + |
| 54 | +<details> |
| 55 | +<summary>With Valibot</summary> |
| 56 | + |
| 57 | +```typescript |
35 | 58 | import { number, object, optional, string } from 'valibot' |
36 | 59 |
|
37 | | -const schema = object({ |
38 | | - public: object({ apiBase: string() }), |
| 60 | +const runtimeConfigSchema = object({ |
| 61 | + public: object({ |
| 62 | + apiBase: string(), |
| 63 | + appName: optional(string()), |
| 64 | + }), |
39 | 65 | databaseUrl: string(), |
| 66 | + secretKey: string(), |
40 | 67 | port: optional(number()), |
41 | 68 | }) |
| 69 | +``` |
| 70 | + |
| 71 | +</details> |
| 72 | + |
| 73 | +<details> |
| 74 | +<summary>With Zod</summary> |
| 75 | + |
| 76 | +```typescript |
| 77 | +import { z } from 'zod' |
| 78 | + |
| 79 | +const runtimeConfigSchema = z.object({ |
| 80 | + public: z.object({ |
| 81 | + apiBase: z.string(), |
| 82 | + appName: z.string().optional(), |
| 83 | + }), |
| 84 | + databaseUrl: z.string(), |
| 85 | + secretKey: z.string(), |
| 86 | + port: z.number().optional(), |
| 87 | +}) |
| 88 | +``` |
| 89 | + |
| 90 | +</details> |
| 91 | + |
| 92 | +<details> |
| 93 | +<summary>With ArkType</summary> |
| 94 | + |
| 95 | +```typescript |
| 96 | +import { type } from 'arktype' |
| 97 | + |
| 98 | +const runtimeConfigSchema = type({ |
| 99 | + 'public': { |
| 100 | + 'apiBase': 'string', |
| 101 | + 'appName?': 'string' |
| 102 | + }, |
| 103 | + 'databaseUrl': 'string', |
| 104 | + 'secretKey': 'string', |
| 105 | + 'port?': 'number' |
| 106 | +}) |
| 107 | +``` |
42 | 108 |
|
| 109 | +</details> |
| 110 | + |
| 111 | +### 2. Configure in nuxt.config.ts |
| 112 | + |
| 113 | +```typescript |
43 | 114 | export default defineNuxtConfig({ |
44 | 115 | modules: ['nuxt-safe-runtime-config'], |
| 116 | + |
45 | 117 | runtimeConfig: { |
46 | | - databaseUrl: process.env.DATABASE_URL || '', |
| 118 | + databaseUrl: process.env.DATABASE_URL || 'postgresql://localhost:5432/mydb', |
| 119 | + secretKey: process.env.SECRET_KEY || 'default-secret-key', |
47 | 120 | port: Number.parseInt(process.env.PORT || '3000'), |
48 | | - public: { apiBase: 'https://api.example.com' }, |
| 121 | + public: { |
| 122 | + apiBase: process.env.PUBLIC_API_BASE || 'https://api.example.com', |
| 123 | + appName: 'My Nuxt App', |
| 124 | + }, |
| 125 | + }, |
| 126 | + |
| 127 | + safeRuntimeConfig: { |
| 128 | + $schema: runtimeConfigSchema, |
49 | 129 | }, |
50 | | - safeRuntimeConfig: { $schema: schema }, |
51 | 130 | }) |
52 | 131 | ``` |
53 | 132 |
|
| 133 | +### 3. Use the type-safe composable |
| 134 | + |
| 135 | +Access your validated config with full type safety — types are auto-generated from your schema: |
| 136 | + |
54 | 137 | ```vue |
55 | 138 | <script setup lang="ts"> |
56 | 139 | const config = useSafeRuntimeConfig() |
57 | | -// config.public.apiBase - string (typed) |
| 140 | +// config.public.apiBase is typed as string |
| 141 | +// config.secretKey is typed as string |
58 | 142 | </script> |
59 | 143 | ``` |
60 | 144 |
|
61 | | -## Documentation |
| 145 | +## Configuration Options |
62 | 146 |
|
63 | | -Full documentation at **[nuxt-safe-runtime-config.vercel.app](https://nuxt-safe-runtime-config.vercel.app)** |
| 147 | +| Option | Type | Default | Description | |
| 148 | +| ------------------- | ------------------------------- | ----------------- | ------------------------------------------ | |
| 149 | +| `$schema` | `StandardSchemaV1` | — | Your validation schema (required) | |
| 150 | +| `validateAtBuild` | `boolean` | `true` | Validate during dev/build | |
| 151 | +| `validateAtRuntime` | `boolean` | `false` | Validate when server starts | |
| 152 | +| `onBuildError` | `'throw' \| 'warn' \| 'ignore'` | `'throw'` | How to handle build validation errors | |
| 153 | +| `onRuntimeError` | `'throw' \| 'warn' \| 'ignore'` | `'throw'` | How to handle runtime validation errors | |
| 154 | +| `logSuccess` | `boolean` | `true` | Log successful validation | |
| 155 | +| `logFallback` | `boolean` | `true` | Log when using JSON Schema fallback | |
| 156 | +| `jsonSchemaTarget` | `string` | `'draft-2020-12'` | JSON Schema version for runtime validation | |
| 157 | +| `shelve` | `boolean \| ShelveOptions` | `undefined` | Shelve secrets integration (see below) | |
64 | 158 |
|
65 | | -## License |
| 159 | +## Shelve Integration |
| 160 | + |
| 161 | +[Shelve](https://shelve.cloud) is a secrets management service. This module fetches secrets from Shelve at build time and merges them into your runtime config before validation. |
| 162 | + |
| 163 | +### Zero-Config Setup |
| 164 | + |
| 165 | +If you have a `shelve.json` file in your project root, the integration enables automatically: |
| 166 | + |
| 167 | +```ts |
| 168 | +export default defineNuxtConfig({ |
| 169 | + safeRuntimeConfig: { |
| 170 | + $schema: runtimeConfigSchema, |
| 171 | + shelve: true, // Auto-detects project, team, and environment |
| 172 | + }, |
| 173 | +}) |
| 174 | +``` |
| 175 | + |
| 176 | +The module resolves configuration from multiple sources (highest priority first): |
| 177 | + |
| 178 | +| Config | Sources | |
| 179 | +| ----------- | ---------------------------------------------------------------------- | |
| 180 | +| project | `nuxt.config` → `SHELVE_PROJECT` → `shelve.json` → `package.json` name | |
| 181 | +| slug | `nuxt.config` → `SHELVE_TEAM_SLUG` → `shelve.json` | |
| 182 | +| environment | `nuxt.config` → `SHELVE_ENV` → `shelve.json` → dev mode auto | |
| 183 | +| token | `SHELVE_TOKEN` → `~/.shelve` file | |
| 184 | + |
| 185 | +### Explicit Configuration |
| 186 | + |
| 187 | +You can override any auto-detected value: |
| 188 | + |
| 189 | +```ts |
| 190 | +export default defineNuxtConfig({ |
| 191 | + safeRuntimeConfig: { |
| 192 | + $schema: runtimeConfigSchema, |
| 193 | + shelve: { |
| 194 | + project: 'my-app', |
| 195 | + slug: 'my-team', |
| 196 | + environment: 'production', |
| 197 | + url: 'https://app.shelve.cloud', // Self-hosted Shelve |
| 198 | + fetchAtBuild: true, // Default: fetch at build time |
| 199 | + fetchAtRuntime: false, // Opt-in: fetch on server cold start |
| 200 | + }, |
| 201 | + }, |
| 202 | +}) |
| 203 | +``` |
| 204 | + |
| 205 | +### Variable Transformation |
| 206 | + |
| 207 | +Shelve variables transform from `SCREAMING_SNAKE_CASE` to `camelCase` with smart grouping: |
| 208 | + |
| 209 | +``` |
| 210 | +DATABASE_URL → databaseUrl |
| 211 | +GITHUB_CLIENT_ID → github.clientId (grouped) |
| 212 | +GITHUB_CLIENT_SECRET → github.clientSecret (grouped) |
| 213 | +PUBLIC_API_URL → public.apiUrl |
| 214 | +``` |
| 215 | + |
| 216 | +Variables with repeated prefixes (2+ keys) nest automatically. `PUBLIC_*` and `NUXT_PUBLIC_*` map to `runtimeConfig.public`. |
| 217 | + |
| 218 | +### Runtime Fetch (Opt-in) |
| 219 | + |
| 220 | +For dynamic environments or secret rotation, enable runtime fetching: |
| 221 | + |
| 222 | +```ts |
| 223 | +export default defineNuxtConfig({ |
| 224 | + safeRuntimeConfig: { |
| 225 | + shelve: { |
| 226 | + fetchAtBuild: true, // Bake secrets into build |
| 227 | + fetchAtRuntime: true, // Also refresh on cold start |
| 228 | + }, |
| 229 | + }, |
| 230 | +}) |
| 231 | +``` |
| 232 | + |
| 233 | +The runtime plugin runs before validation, so freshly fetched secrets are validated against your schema. |
| 234 | + |
| 235 | +## Runtime Validation |
| 236 | + |
| 237 | +By default, validation only runs at build time. Enable runtime validation to catch environment variable issues when the server starts: |
| 238 | + |
| 239 | +```ts |
| 240 | +export default defineNuxtConfig({ |
| 241 | + safeRuntimeConfig: { |
| 242 | + $schema: runtimeConfigSchema, |
| 243 | + validateAtRuntime: true, |
| 244 | + }, |
| 245 | +}) |
| 246 | +``` |
| 247 | + |
| 248 | +Runtime validation uses [@cfworker/json-schema](https://github.com/cfworker/cfworker/tree/main/packages/json-schema) to validate the config after environment variables are merged. This lightweight validator (~8KB) works on all runtimes including edge (Cloudflare Workers, Vercel Edge, Netlify Edge). It catches issues like: |
| 249 | + |
| 250 | +- Environment variables with wrong types (e.g., `NUXT_PORT=abc` when expecting a number) |
| 251 | +- Missing required environment variables in production |
| 252 | +- Invalid values that pass build-time checks but fail at runtime |
| 253 | + |
| 254 | +## ESLint Integration |
| 255 | + |
| 256 | +The module includes an ESLint plugin that warns when using `useRuntimeConfig()` instead of `useSafeRuntimeConfig()`. |
| 257 | + |
| 258 | +### With @nuxt/eslint (Automatic) |
| 259 | + |
| 260 | +If you use [@nuxt/eslint](https://eslint.nuxt.com), the rule is auto-registered. No configuration needed. |
| 261 | + |
| 262 | +### Manual Setup |
| 263 | + |
| 264 | +Add to your `eslint.config.mjs`: |
| 265 | + |
| 266 | +```javascript |
| 267 | +import { configs } from 'nuxt-safe-runtime-config/eslint' |
| 268 | + |
| 269 | +export default [ |
| 270 | + configs.recommended, |
| 271 | + // ... your other configs |
| 272 | +] |
| 273 | +``` |
| 274 | + |
| 275 | +Or configure manually: |
| 276 | + |
| 277 | +```javascript |
| 278 | +import plugin from 'nuxt-safe-runtime-config/eslint' |
| 279 | + |
| 280 | +export default [ |
| 281 | + { |
| 282 | + plugins: { 'safe-runtime-config': plugin }, |
| 283 | + rules: { 'safe-runtime-config/prefer-safe-runtime-config': 'warn' }, |
| 284 | + }, |
| 285 | +] |
| 286 | +``` |
| 287 | + |
| 288 | +The rule includes auto-fix support — run `eslint --fix` to automatically replace `useRuntimeConfig()` calls. |
| 289 | + |
| 290 | +## Type Safety |
| 291 | + |
| 292 | +Types are auto-generated at build time from your schema's JSON Schema representation. The `useSafeRuntimeConfig()` composable returns a fully typed object — no manual generics needed: |
| 293 | + |
| 294 | +```ts |
| 295 | +const config = useSafeRuntimeConfig() |
| 296 | +// config is fully typed based on your schema |
| 297 | +``` |
| 298 | + |
| 299 | +Generated types are stored in `.nuxt/types/safe-runtime-config.d.ts` and automatically included in your project. |
| 300 | + |
| 301 | +## Error Messages |
| 302 | + |
| 303 | +When validation fails, you see detailed error messages: |
| 304 | + |
| 305 | +``` |
| 306 | +[safe-runtime-config] Validation failed! |
| 307 | + 1. databaseUrl: Invalid type: Expected string but received undefined |
| 308 | + 2. public.apiBase: Invalid type: Expected string but received undefined |
| 309 | + 3. port: Invalid type: Expected number but received string |
| 310 | +``` |
| 311 | + |
| 312 | +The module stops the build process until all validation errors are resolved. |
| 313 | + |
| 314 | +## Why This Module? |
| 315 | + |
| 316 | +Nuxt's built-in schema validation is designed for module authors and broader configuration. This module focuses specifically on **runtime config validation** using Standard Schema, allowing you to: |
| 317 | + |
| 318 | +- Use your preferred validation library (Valibot, Zod, ArkType) |
| 319 | +- Catch configuration errors at build time |
| 320 | +- Optionally validate at runtime for environment variable issues |
| 321 | +- Get full type safety in your components |
| 322 | + |
| 323 | +## Contribution |
| 324 | + |
| 325 | +<details> |
| 326 | + <summary>Local development</summary> |
| 327 | + |
| 328 | +```bash |
| 329 | +# Install dependencies |
| 330 | +pnpm install |
| 331 | + |
| 332 | +# Generate type stubs |
| 333 | +pnpm run dev:prepare |
| 334 | + |
| 335 | +# Develop with the playground |
| 336 | +pnpm run dev |
| 337 | + |
| 338 | +# Build the playground |
| 339 | +pnpm run dev:build |
| 340 | + |
| 341 | +# Run ESLint |
| 342 | +pnpm run lint |
| 343 | + |
| 344 | +# Run Vitest |
| 345 | +pnpm run test |
| 346 | +pnpm run test:watch |
| 347 | + |
| 348 | +# Release new version |
| 349 | +pnpm run release |
| 350 | +``` |
66 | 351 |
|
67 | | -MIT |
| 352 | +</details> |
0 commit comments