Skip to content

Commit 318415a

Browse files
committed
Add post: Environment variables in SvelteKit
Fixes #985
1 parent 173b943 commit 318415a

5 files changed

Lines changed: 111 additions & 1 deletion

File tree

‎src/routes/api/posts/2025/+server.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { resolvePost } from '$lib/server/resolvers';
22
import postDroppingRequestsInSvelteKit from '$posts/(2025)/dropping-requests-in-sveltekit/meta';
3+
import postEnvVarsInSvelteKit from '$posts/(2025)/environment-variables-in-sveltekit/meta';
34
import { json } from '@sveltejs/kit';
45
import type { RequestHandler } from './$types';
56

67
export const prerender = true;
78

89
export const GET: RequestHandler = async (event) => {
910
// Sort order: latest first.
10-
const posts = [postDroppingRequestsInSvelteKit];
11+
const posts = [postEnvVarsInSvelteKit, postDroppingRequestsInSvelteKit];
1112

1213
const resolvedPosts = await Promise.all(
1314
posts.map((post) => {

‎src/routes/api/tags/[id]/tags.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ const tags: Tag[] = [
7373
label: 'TypeScript',
7474
path: '/tags/typescript'
7575
},
76+
{
77+
id: 'vercel',
78+
label: 'Vercel',
79+
path: '/tags/vercel'
80+
},
7681
{
7782
id: 'vscode',
7883
label: 'VS Code',
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { resolvePost } from '$lib/server/resolvers';
2+
import type { PageServerLoad } from './$types';
3+
import meta from './meta';
4+
5+
export const load: PageServerLoad = async (event) => {
6+
const post = await resolvePost({ postMeta: meta, event });
7+
const { title, description, ogImageUrl } = post;
8+
9+
return { post, seo: { title, description, ogImageUrl } };
10+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
Much has been written about environment variables in SvelteKit. Yet, I was often unsure which of the
2+
four options SvelteKit offers for environment variables is the right one. Until I realized that you
3+
have to put the four options into the context of your hosting environment to make sense.
4+
5+
Let's first look at where in a SvelteKit app you might need environment variables:
6+
7+
1. **On the server:** Some of your SvelteKit code will run on a server, be it in a serverless
8+
function on Vercel or on a Node.js server, possibly inside a Docker container. The server waits
9+
for requests, processes them, and returns a response. The code on the server is considered
10+
secure, which means that its code and the environment cannot be accessed or inspected. That's why
11+
you can store secrets on a server, either in environment variables or baked into the code that
12+
runs on the server.
13+
14+
2. **In the JavaScript code that runs in the browser:** When the server sends a response, it adds
15+
client-side code that runs in the browser. This highly optimized and bundled code is what creates
16+
a great UX for the users of your SvelteKit app. The server can send pre-bundled code containing
17+
values from environment variables, or it can dynamically add the values of environment variables
18+
into the code that it sends to the browser. In any case, these values must not contain any
19+
secrets, because as much as you may try to obfuscate them, they are part of the code shipped to
20+
the browser and can be inspected.
21+
22+
3. **During the build step:** When you build your SvelteKit app, the build runs in a safe
23+
environment. Either on your development machine (which you hopefully update regularly) or on a
24+
build server, for example Vercel or a service like Railway. During the build step, the values of
25+
environment variables can be bundled into the code. If a bundle is meant to run on the server
26+
only, it can normally contain secrets (with some exceptions). If a bundle is meant to be shipped
27+
to the browser, it must not contain any secrets.
28+
29+
Therefore, SvelteKit makes a distinction between environment variables that are secrets, referred to
30+
as **private variables**, and those that are safe to ship to a browser, referred to as **public
31+
variables**.
32+
33+
When you are dealing with private variables, you have to be mindful of how you deploy your SvelteKit
34+
app. If you deploy to Vercel, it's safe to embed private variables code that runs on Vercel. There
35+
is no way anyone can inspect the deployed serverless functions.
36+
37+
But when you deploy with a Docker container, you have to be careful. If your container is private
38+
and can never be accessed by anyone, you can safely embed private variables into SvelteKit's server
39+
code. But if you publish a Docker container to a registry, anyone with access could reverse engineer
40+
the container and extract the secrets.
41+
42+
With the distinction between public and private variables in mind, let's look at SvelteKit's four
43+
options for environment variables:
44+
45+
- [`$env/static/public`](https://svelte.dev/docs/kit/$env-static-public): These environment
46+
variables start with a configurable prefix, `PUBLIC_`, to signal that they are safe to ship to the
47+
browser. They must exist at build time, not runtime. They are baked into the code during the build
48+
and cannot be changed at runtime. Environment variables in this category are suitable for pages
49+
that are prerendered.
50+
51+
Example: The public tracking ID of an analytics tool, `PUBLIC_ANALYTICS_ID`.
52+
53+
- [`$env/dynamic/public`](https://svelte.dev/docs/kit/$env-dynamic-public): These environment
54+
variables cannot be accessed at build time. Therefore, they are not suitable for pages that are
55+
prerendered. On the server, there must be an environment variable prefixed with `PUBLIC_` that can
56+
be modified by the server during runtime. You can send this variable to the browser using a load
57+
function.
58+
59+
Example: A variable that triggers maintenance mode on the server, `PUBLIC_MAINTENANCE_MODE=false`.
60+
61+
- [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private): Like its public
62+
counterpart, static private variables must exist during the build step and cannot be changed at
63+
runtime. SvelteKit will throw an error if you try to import these variables into code that ships
64+
to the browser. Any page that uses these variables can be prerendered. Do not use in combination
65+
with published Docker containers.
66+
67+
Example: A private API key, `API_KEY`.
68+
69+
- [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private): These environment
70+
variables cannot be accessed during the build and, like their public counterpart, are not suitable
71+
for pages that are prerendered. They can be changed at runtime.
72+
73+
Example: A feature flag for a new feature, `CHAT_FEATURE_FLAG`, so DevOps can turn off the
74+
feature without redeploying if they detect a problem.
75+
76+
When I build for Vercel, most of the time I use static private variables with
77+
`$env/static/private`. Occasionally, I need a static public variable using `$env/static/public`, for
78+
example, to make a `PUBLIC_ANALYTICS_ID` available client-side.
79+
80+
If I ship a SvelteKit app as a Docker container, I should default to dynamic private variables with
81+
`$env/dynamic/private`. But this often interferes with prerendering. So, even with Docker, I prefer
82+
using static environment variables. In a published Docker container, you should skip prerendering
83+
if it means you risk exposing a secret.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { PostMeta } from '@maiertech/sveltekit-helpers';
2+
3+
export default {
4+
title: 'Environment variables in SvelteKit',
5+
author: 'thilo',
6+
publishedDate: '2025-08-01',
7+
description: 'TODO.',
8+
tags: ['svelte', 'vercel'],
9+
path: '/posts/environment-variables-in-sveltekit',
10+
filepath: 'src/routes/posts/(2025)/environment-variables-in-sveltekit/+page.svx'
11+
} satisfies PostMeta;

0 commit comments

Comments
 (0)