Running Svelte with a .NET or Go backend: lessons from production teaches you that the frontend and backend are not one monolith—they're two services that must talk securely and reliably. After shipping SvelteKit apps alongside .NET APIs and Go microservices in high-traffic environments, we've learned which patterns scale and which ones cause sleepless nights. This article distills what works.
Server loads call APIs — Keep tokens on the server
The most dangerous mistake is exposing your API tokens in browser JavaScript. Every SvelteKit page load is an opportunity to fetch fresh data from your backend, and the right place to do that is in a load function on the server.
When you use SvelteKit's server-side load function (the one in +page.server.ts, not +page.ts), your code runs in a Node.js environment where you control secrets. This is where you store your API keys, database credentials, and session tokens. Your browser never sees them.
// src/routes/dashboard/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ fetch, locals }) => {
// fetch here is a patched version that inherits cookies and auth
const res = await fetch('https://api.internal.example.com/user/profile', {
headers: {
'Authorization': `Bearer ${locals.accessToken}`,
},
});
if (!res.ok) {
throw error(res.status, 'Failed to load profile');
}
return {
profile: await res.json(),
};
};
The fetch passed to server-side load is not the browser's fetch. It's a patched version that:
- Runs on the server, so it can reach internal APIs (no CORS issues).
- Inherits cookies automatically from the user's session.
- Allows you to inject headers like
Authorizationwithout exposing secrets to JavaScript.
In production, we kept API tokens in environment variables and refreshed them server-side using SvelteKit hooks. The browser never gets a raw token—only a session cookie that the server validates.
Typed clients — Generate from OpenAPI
Calling a REST API by hand (writing fetch and parsing JSON) is error-prone. You end up repeating endpoint URLs, forgetting headers, and discovering type mismatches in production. A better approach: generate a typed client from your backend's OpenAPI spec.
Most modern backends (.NET with Swashbuckle, Go with swag) can export OpenAPI 3.0 specs. Tools like openapi-typescript or orval turn that spec into TypeScript types and client functions. Now your frontend has type safety—the compiler catches mismatches before code ships.
Here's a workflow:
- Your .NET or Go API exposes
GET /openapi.json. - You run
npx openapi-typescript https://api.internal.example.com/openapi.json -o src/lib/api.ts. - The generated file contains types for every endpoint.
- You use a thin wrapper in your load function.
// src/lib/api.ts (generated by openapi-typescript)
export interface components {
schemas: {
UserProfile: {
id: string;
email: string;
createdAt: string;
};
// ... hundreds of types
};
}
export interface operations {
getUserProfile: {
responses: {
'200': {
content: {
'application/json': components['schemas']['UserProfile'];
};
};
};
};
}
Then in your load function:
// src/routes/dashboard/+page.server.ts
export const load: PageServerLoad = async ({ fetch, locals }) => {
const res = await fetch('https://api.internal.example.com/users/me', {
headers: {
'Authorization': `Bearer ${locals.accessToken}`,
},
});
const profile: UserProfile = await res.json();
return { profile };
};
The UserProfile type is generated, so TypeScript will yell if you try to access a field that doesn't exist. In a high-volume SaaS or e-commerce system, this catches bugs before they reach customers.
Regenerate the client whenever you deploy a new backend. Most CI/CD pipelines (GitHub Actions, GitLab CI) can automate this with a simple script.
Auth — Forward cookies or exchange tokens in hooks
Authentication in a SvelteKit + backend setup comes down to one decision: do you forward the user's session cookie to the API, or do you exchange it for a token server-side?
Option 1: Forward cookies (simpler for monolith-like setups)
If your backend sets a session cookie on the same domain, SvelteKit's patched fetch will include it automatically. This works well when frontend and backend share a domain (e.g., app.example.com for the frontend and api.example.com for the backend, both under example.com).
// src/routes/+layout.server.ts
export const load: LayoutServerLoad = async ({ fetch, request }) => {
// fetch inherits cookies from request; the backend validates them
const res = await fetch('https://api.example.com/me');
return { user: await res.json() };
};
Option 2: Token exchange (better security for distributed systems)
If your frontend and API are on different domains or need stricter auth boundaries, exchange the session cookie for a short-lived access token on the server. This is common in microservices architectures.
// src/hooks.server.ts
import { json } from '@sveltejs/kit';
export const handle = async ({ event, resolve }) => {
// Extract session from incoming cookies
const sessionId = event.cookies.get('sessionId');
if (sessionId) {
// Exchange session for access token (backend validates & returns token)
const tokenRes = await fetch('https://api.internal.example.com/auth/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId }),
});
if (tokenRes.ok) {
const { accessToken } = await tokenRes.json();
event.locals.accessToken = accessToken;
}
}
return resolve(event);
};
This happens once per request, server-side. The browser never sees the access token. If the token expires, a new one is fetched on the next request.
In production, we've used both patterns. Cookie forwarding is simpler for smaller teams; token exchange scales better as you add multiple backend services and stricter security policies.
Deploy — Separate services with shared domain
Your SvelteKit app and your .NET or Go API are two separate services. They can live on different servers, scale independently, and deploy on different schedules. But they need to coordinate on a shared domain to avoid CORS headaches.
A typical production setup:
https://app.example.com→ SvelteKit frontend (Node.js or static export).https://api.example.com→ .NET or Go backend.- Both under
example.com, so cookies work across them.
Use a reverse proxy (NGINX, Cloudflare, or an API Gateway) to route traffic:
| Path | Routes to | Service |
|---|---|---|
/ |
localhost:3000 |
SvelteKit |
/api/* |
localhost:5000 |
.NET Core |
/.well-known/* |
localhost:5000 |
.NET Core (for OAuth, etc.) |
Or use subdomains:
frontend.example.com→ SvelteKit.backend.example.com→ .NET or Go API.
In your SvelteKit load functions, call https://api.example.com or https://backend.example.com directly. Because they're on the same domain, cookies travel automatically, and you don't need CORS headers.
For local development, run both services and point your frontend's API calls to http://localhost:5000. Use environment variables to switch between dev and prod:
// src/lib/config.ts
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000';
export default API_URL;
.env.local:
VITE_API_URL=http://localhost:5000
Common mistakes
Storing API keys in .env.public or exposing them to the browser. Your backend's API key should never be sent to the browser. Always fetch from the server and let the server hold secrets.
Forgetting to regenerate the OpenAPI client after backend changes. If you update an endpoint in your .NET or Go API, regenerate your TypeScript types immediately. Stale types cause runtime errors and confusing bugs.
Using CORS instead of a shared domain. CORS adds latency and complexity. A reverse proxy on a shared domain is cleaner and safer.
Calling external or third-party APIs from the browser. Even if a third-party API has a public key, route it through your backend. This gives you one place to log, rate-limit, and audit API usage. It also protects your users' privacy—your backend can strip PII before forwarding responses.
Not caching load function results. If multiple SvelteKit routes call the same backend endpoint, you'll hammer your API. Use SvelteKit's built-in caching or add a layer of Redis on your backend.
Key takeaways
- Server-side
loadfunctions fetch from your backend; keep tokens and secrets on the server. - Generate typed clients from OpenAPI specs to catch API mismatches at compile time.
- Exchange session cookies for tokens server-side (in hooks) for better auth boundaries; forward cookies if you're on the same domain.
- Deploy frontend and backend as separate services under a shared domain. Use a reverse proxy to route requests.
- Never expose API keys to the browser. Always route external API calls through your server.
- Regenerate client code after backend changes; stale types cause production incidents.






Comments
Loading comments…