|
1 | | -import { isBrowser } from '../helpers/is-browser' |
| 1 | +import { isBrowser as isBrowserWithEnv } from '../helpers/is-browser' |
2 | 2 | import { PUBLIC_ENV_KEY } from './constants' |
3 | 3 |
|
| 4 | +const IS_BROWSER = typeof window !== 'undefined' |
| 5 | + |
4 | 6 | /** |
5 | 7 | * Reads environment variables safely from both browser and server contexts. |
6 | 8 | * |
@@ -64,7 +66,7 @@ import { PUBLIC_ENV_KEY } from './constants' |
64 | 66 | export function env(key: string): string | undefined |
65 | 67 | export function env<T extends string>(key: string, defaultValue: T): string |
66 | 68 | export function env(key: string, defaultValue?: string): string | undefined { |
67 | | - if (isBrowser()) { |
| 69 | + if (isBrowserWithEnv()) { |
68 | 70 | if (!key.startsWith('NEXT_PUBLIC_')) { |
69 | 71 | throw new Error( |
70 | 72 | `Environment variable '${key}' is not public and cannot be accessed in the browser.\n` + |
@@ -125,3 +127,73 @@ export function requireEnv(key: string): string { |
125 | 127 | } |
126 | 128 | return value |
127 | 129 | } |
| 130 | + |
| 131 | +/** |
| 132 | + * Safely reads a server-only environment variable. |
| 133 | + * |
| 134 | + * Returns the fallback value when running in the browser, allowing this function |
| 135 | + * to be safely called from code that runs on both client and server (e.g., shared |
| 136 | + * lazy getters, utility modules). |
| 137 | + * |
| 138 | + * Unlike {@link env}, this function: |
| 139 | + * - **Never throws** in the browser—it gracefully returns the fallback |
| 140 | + * - Is designed for **non-`NEXT_PUBLIC_*`** variables that shouldn't be exposed to clients |
| 141 | + * - Provides a clean pattern for isomorphic code that needs server secrets |
| 142 | + * |
| 143 | + * @param key - The environment variable name to retrieve |
| 144 | + * @param fallback - Optional fallback value returned in the browser or if undefined |
| 145 | + * @returns The environment variable value on the server, or the fallback on the client/if undefined |
| 146 | + * |
| 147 | + * @example |
| 148 | + * Basic usage with server-only secrets: |
| 149 | + * ```ts |
| 150 | + * import { serverOnly } from 'next-dynenv' |
| 151 | + * |
| 152 | + * // Returns the actual value on server, undefined on client |
| 153 | + * const dbUrl = serverOnly('DATABASE_URL') |
| 154 | + * |
| 155 | + * // With a fallback for development |
| 156 | + * const apiKey = serverOnly('API_SECRET_KEY', 'dev-key') |
| 157 | + * ``` |
| 158 | + * |
| 159 | + * @example |
| 160 | + * In shared code with lazy evaluation (e.g., with Zod schemas): |
| 161 | + * ```ts |
| 162 | + * import { env, serverOnly } from 'next-dynenv' |
| 163 | + * import { z } from 'zod' |
| 164 | + * |
| 165 | + * const configSchema = z.object({ |
| 166 | + * supabaseUrl: z.string().url(), |
| 167 | + * supabaseAnonKey: z.string(), |
| 168 | + * // Server-only: returns fallback on client, real value on server |
| 169 | + * supabaseServiceKey: z.string().optional(), |
| 170 | + * }) |
| 171 | + * |
| 172 | + * // This lazy getter can be safely imported anywhere |
| 173 | + * const config = lazy(() => configSchema.parse({ |
| 174 | + * supabaseUrl: env('NEXT_PUBLIC_SUPABASE_URL'), |
| 175 | + * supabaseAnonKey: env('NEXT_PUBLIC_SUPABASE_ANON_KEY'), |
| 176 | + * supabaseServiceKey: serverOnly('SUPABASE_SERVICE_KEY'), |
| 177 | + * })) |
| 178 | + * ``` |
| 179 | + * |
| 180 | + * @example |
| 181 | + * Conditional server-side logic: |
| 182 | + * ```ts |
| 183 | + * import { serverOnly } from 'next-dynenv' |
| 184 | + * |
| 185 | + * // Safe to call anywhere—returns undefined on client |
| 186 | + * const internalUrl = serverOnly('INTERNAL_SERVICE_URL') || publicUrl |
| 187 | + * ``` |
| 188 | + * |
| 189 | + * @see {@link env} for public variables accessible on both client and server |
| 190 | + * @see {@link requireEnv} for required variables that throw if undefined |
| 191 | + */ |
| 192 | +export function serverOnly(key: string): string | undefined |
| 193 | +export function serverOnly<T extends string>(key: string, fallback: T): string |
| 194 | +export function serverOnly(key: string, fallback?: string): string | undefined { |
| 195 | + if (IS_BROWSER) { |
| 196 | + return fallback |
| 197 | + } |
| 198 | + return process.env[key] ?? fallback |
| 199 | +} |
0 commit comments