This episode covers configuring a Svelte app for different environments: environment variables and runtime config, Vite and SvelteKit configuration, asset management and static files, and secure feature flags and multi-environment setup.

The same application runs in many environments: developer machines, staging servers, and production servers. Each has a different API address, integration keys, and behavior. Hardcoding those values is a recipe for disaster — one wrong commit, and a secret leaks to the public.
The solution is environment-based configuration: values are pulled out of code, placed in environment variables, and read when the app runs. SvelteKit provides a dedicated module for this, with a clear boundary between variables that may appear in the browser and those meant only for the server.
This episode covers environment variables and runtime config, Vite and SvelteKit configuration, asset management and static files, and feature flags and multi-environment setup. When you're done, you can build an app that behaves differently in each environment without changing a single line of code.
SvelteKit exposes environment variables through the $env module — not directly through process.env. This module separates variables by visibility:
import { PUBLIC_API_URL } from "$env/static/public"
import { DATABASE_URL } from "$env/static/private"$env/static/public only contains variables prefixed with PUBLIC_ — safe to send to the browser. $env/static/private is only available on the server and holds secrets such as database connections. Forcing you to name variables explicitly is a security feature, not just a style rule.
A .env file at the project root defines local variables. Never commit this file:
PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgres://user:pass@db:5432/app
FLAG_THEME_BARU=truePUBLIC_API_URL will be visible in the browser, while DATABASE_URL is server-only. A commit-safe .env.example file contains empty versions as documentation. When you deploy, values are filled in from the hosting provider's panel or CI/CD — never from the repository.
The main configuration for a SvelteKit app lives in svelte.config.js. You can add preprocessors, path aliases, and kit settings:
import adapter from "@sveltejs/adapter-node"
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
alias: {
"@lib": "./src/lib",
},
},
}
export default configadapter() determines the deployment target — adapter-node for a Node server, adapter-vercel for Vercel. alias enables short imports like @lib/stores.js. This configuration is covered in full in episode 20 about deployment.
Vite handles bundling and the dev server. Additional configuration is written in vite.config.ts:
import { sveltekit } from "@sveltejs/kit/vite"
import { defineConfig } from "vite"
export default defineConfig({
plugins: [sveltekit()],
server: {
port: 5173,
proxy: {
"/api": "http://localhost:8080",
},
},
})server.proxy forwards /api requests to another backend server during development — no need to enable CORS on the backend. Build configuration such as build.target and build.minify is also set here to fine-tune production output.
Files in the static folder are served directly at the root URL without being processed by Vite:
static/
├── favicon.png # /favicon.png
├── robots.txt # /robots.txt
└── logo.svg # /logo.svgstatic/favicon.png can be referenced as /favicon.png in markup. This folder is for assets that don't change and don't need optimization: favicons, robots.txt, certificates, and files referenced from outside. Don't put assets that are imported by components here — that's the job of the Vite pipeline.
For processed assets — minified, hashed, optimized — import them directly in code:
<script>
import logo from "$lib/assets/logo.svg"
</script>
<img src={logo} alt="Logo perusahaan" />import logo from "$lib/assets/logo.svg" makes Vite process the file and produce a URL with a content hash that's optimal for caching. Unlike the static folder, imported assets get bundled and optimized — the right choice for images used by components.
Feature flags let you enable new features in some environments without a separate deploy:
import { env } from "$env/dynamic/private"
export const fiturTemaBaru = env.FLAG_THEMA_BARU === "true"$env/dynamic/private reads variables at runtime, not at build time — ideal for values that change without a rebuild. env.FLAG_THEMA_BARU === "true" converts an env string to a boolean. Gating features with flags makes gradual releases safe and instantly revertible.
A common pattern: three environments with different values:
# development (.env)
PUBLIC_API_URL=http://localhost:8080
# production (panel hosting)
PUBLIC_API_URL=https://api.example.comPUBLIC_API_URL holds the local backend URL during development and the production URL when you deploy. The code never changes — only its value. Combined with more logging in development and active error tracking in production, you get one codebase that behaves correctly in every environment.
Key takeaways:
$env module, not process.env, so secret visibility is clear.PUBLIC_ for variables allowed in the browser; keep the rest on the server..env; provide .env.example as documentation.svelte.config.js configures the adapter and aliases; vite.config.ts configures plugins and the proxy.static folder is for raw assets; import through Vite for optimized assets.Next, in episode 12 we will discuss authentication and authorization — auth patterns in SvelteKit, session management and secure cookies, protected routes and route guards, and role-based access control and client/server auth. The environment variables from this episode will store the secrets that protect your sessions.