SvelteKit Server-Only Modules: Keep Your Secrets Secret

SvelteKit's server-only modules are a firewall for your code, preventing sensitive data like API keys from ever being bundled for the browser. Use them for database clients or secret logic.
Why it exists
In a full-stack framework where frontend and backend code live together, it's easy to accidentally import server-side secrets (like API keys or database credentials) into client-side code. This creates a major security vulnerability by exposing secrets in the browser. Server-only modules solve this by creating a hard boundary enforced at build time.
The mental model
Think of server-only modules as a "no trespassing" sign for your SvelteKit project's bundler. If any code destined for the browser tries to import something from a file with this sign, the bundler stops and throws an error. It's a security guard that prevents sensitive information from ever leaving the server, not just a guideline.
How it works
SvelteKit's build process analyzes the entire import graph of your application. It identifies which code runs on the server and which runs in the browser. If it detects any path, direct or indirect, from a browser module to a server-only module, it terminates the build with an error message explaining the illegal import chain. This works for both static and dynamic imports.
When to use it
Use this feature for any code that handles sensitive information or performs server-exclusive operations. You can designate a module as server-only in two ways: first, place the file inside the lib/server/ directory; second, append .server to the filename (e.g., database.server.ts). This is ideal for modules that connect to a database, contain private API keys from env/static/private, or use server-side utilities from $app/server.
When not to use it
This feature is a blunt instrument. Don't place shared, non-sensitive utility functions in a server-only module if you also need them on the client. The check is disabled during testing (when process.env.TEST === 'true'), so don't rely on it for test-time logic. If you only need a type from a server-only module, use a type-only import (import type { MyType } from ...) which is safe and allowed.
One canonical example
Imagine a file $lib/server/secrets.js containing export const API_KEY = '...';. A shared utility file, utils.js, imports this key. A Svelte component, +page.svelte, then imports a different, harmless function from utils.js. Even though the component never touches API_KEY, SvelteKit will error because an import path exists from client code (+page.svelte) to server-only code ($lib/server/secrets.js). The entire import chain is considered unsafe.
Interview question
How does SvelteKit primarily enforce that server-only modules remain server-side?
- a.By halting the build process if a client-side module, directly or indirectly, imports from them.Correct
- b.By implementing runtime checks to prevent browser access to server-only module exports.
- c.By encrypting their contents, making them unreadable if accidentally included in client bundles.
- d.By automatically removing their code from client-side bundles during compilation.
Why? this is the answer
SvelteKit enforces this by terminating the build with an error if any client-side code, directly or indirectly, attempts to import from a server-only module. It does not automatically remove or encrypt code, nor does it rely on runtime checks.
Just read this? Test yourself on what you have been reading.
Read the original → svelte.dev
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Every open role lists the topics its interview covers, so you can prepare for the real thing rather than guessing.
See open roles