As a SaaS platform developer based in Nepal, I have shipped dozens of production applications β from fintech portals to booking systems and marketplace platforms. The combination of SvelteKit and Supabase has become my default stack for building fast, scalable, production-grade products.
This guide walks through the full architecture: from project setup to deployment. Whether you are a beginner or an experienced developer switching stacks, every step here is explained with working code.
β Why SvelteKit?
Most JavaScript frameworks (React, Vue, Angular) run in the browser. They send a nearly empty HTML file, then load megabytes of JavaScript to render the page. This means:
- Slow first-page load (especially on mobile in Nepal where 4G speeds vary)
- Poor SEO by default β Google sees an empty page unless you add SSR separately
- High memory usage in the browser
Svelte is different. It is a compiler, not a framework. At build time, Svelte compiles your components into vanilla JavaScript with no runtime overhead. Bundles are 5β10x smaller than React equivalents.
SvelteKit adds full-stack capabilities on top: server-side rendering (SSR), file-based routing, API routes, and adapters for deploying anywhere (Vercel, Cloudflare, Node.js, static).
Learn more in the SvelteKit official docs.
β Why Supabase?
Building a backend from scratch takes months. You need auth, a database, file storage, real-time subscriptions, and API endpoints. Supabase gives you all of this in minutes, built on top of PostgreSQL β the world's most advanced open-source database.
Supabase gives you:
- PostgreSQL database β with a full REST API auto-generated from your schema. No controllers to write.
- Real-time subscriptions β listen to database changes via WebSockets. Perfect for live dashboards, chat, or booking confirmations.
- Authentication β email/password, magic links, OAuth (Google, GitHub, etc.) with JWTs handled automatically.
- Storage β S3-compatible file storage with access policies tied to your auth.
- Row-Level Security (RLS) β enforce data access rules at the database level. A user can only read their own data, enforced by PostgreSQL itself β not your application code.
β 1. Create the Project
Make sure you have Node.js 18+ and pnpm installed.
npm create svelte@latest my-saas-app
# Choose: Skeleton project, TypeScript, ESLint + Prettier
cd my-saas-app
pnpm install
pnpm add @supabase/supabase-js β 2. Set Up Your Supabase Project
- Go to app.supabase.com and create a free account
- Click New Project β choose Singapore region (closest to Nepal)
- Go to Settings β API and copy your Project URL and anon key
Create a .env file in your project root:
PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here Always add .env to your .gitignore β never commit secrets to version control.
β 3. Create the Supabase Client
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
import type { Database } from './database.types';
export const supabase = createClient<Database>(
PUBLIC_SUPABASE_URL,
PUBLIC_SUPABASE_ANON_KEY
); The $env/static/public import is SvelteKit's built-in way to access environment variables safely. Variables prefixed with PUBLIC_ are safe to expose to the browser.
β 4. Define Your Database Schema
Go to Supabase Dashboard β SQL Editor and run this migration:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title VARCHAR(300) NOT NULL,
slug VARCHAR(300) NOT NULL UNIQUE,
description TEXT,
content TEXT NOT NULL,
published BOOLEAN DEFAULT false,
author_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index for fast slug lookups (used on every page view)
CREATE INDEX idx_posts_slug ON posts(slug);
CREATE INDEX idx_posts_published_created ON posts(published, created_at DESC);
-- Auto-update updated_at on every UPDATE
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER posts_updated_at
BEFORE UPDATE ON posts
FOR EACH ROW EXECUTE FUNCTION update_updated_at(); β 5. Add Row-Level Security (RLS)
RLS is Supabase's killer feature. Instead of checking permissions in your application code (which can be accidentally bypassed), rules are enforced at the database level by PostgreSQL itself.
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Anyone can read published posts
CREATE POLICY "Public can read published posts"
ON posts FOR SELECT
USING (published = true);
-- Only the author can insert, update, or delete their own posts
CREATE POLICY "Authors can insert own posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = author_id);
CREATE POLICY "Authors can update own posts"
ON posts FOR UPDATE
USING (auth.uid() = author_id);
CREATE POLICY "Authors can delete own posts"
ON posts FOR DELETE
USING (auth.uid() = author_id); auth.uid() reads the user's ID from the JWT token attached to every request. No matter what the client sends, the database will reject any attempt to touch another user's data.
β 6. Generate TypeScript Types
Supabase can auto-generate TypeScript types directly from your live database schema:
pnpm add -D supabase
npx supabase login
npx supabase link --project-ref your-project-ref
npx supabase gen types typescript --linked > src/lib/database.types.ts Now every database query is fully typed β wrong column names or types are caught at compile time.
β 7. Load Data Server-Side with +page.ts
SvelteKit's load functions run on the server during SSR. Data is fetched before the page renders, so users never see a loading spinner and search engines see the full content.
// src/routes/blog/+page.ts
import { supabase } from '$lib/supabase';
import type { PageLoad } from './$types';
export const load: PageLoad = async () => {
const { data: posts, error } = await supabase
.from('posts')
.select('id, title, slug, description, created_at')
.eq('published', true)
.order('created_at', { ascending: false })
.limit(20);
if (error) {
console.error('Failed to load posts:', error.message);
return { posts: [] };
}
return { posts: posts ?? [] };
}; In the Svelte component, the data is available directly as a prop β no useEffect, no loading state:
<!-- src/routes/blog/+page.svelte -->
<script lang="ts">
export let data;
const { posts } = data;
</script>
{#each posts as post}
<article>
<h2><a href="/blog/{post.slug}">{post.title}</a></h2>
<p>{post.description}</p>
<time>{new Date(post.created_at).toLocaleDateString()}</time>
</article>
{/each} β 8. Real-Time Subscriptions
One feature that would require a separate WebSocket server in most stacks is built into Supabase:
import { onDestroy } from 'svelte';
import { supabase } from '$lib/supabase';
let comments = [];
const channel = supabase
.channel('comments-feed')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'comments', filter: `post_id=eq.${postId}` },
(payload) => { comments = [...comments, payload.new]; }
)
.subscribe();
// Always clean up subscriptions to avoid memory leaks
onDestroy(() => { supabase.removeChannel(channel); }); This is powerful for booking systems (real-time seat availability), fintech dashboards (live transaction feeds), and ride-sharing apps (driver location updates).
β 9. Authentication
async function signUp() {
const { error } = await supabase.auth.signUp({ email, password });
if (!error) goto('/dashboard');
}
async function signIn() {
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (!error) goto('/dashboard');
}
async function signOut() {
await supabase.auth.signOut();
goto('/');
} β 10. Deploy to Vercel
pnpm add -D @sveltejs/adapter-vercel
# Update svelte.config.js to use adapter-vercel Push to GitHub, connect to Vercel, and add your environment variables in the Vercel dashboard. Every push to main auto-deploys with SSL, CDN, and zero-downtime updates.
β What You Have Now
With ~200 lines of code and config, you have:
- Server-side rendered app with full SEO support
- PostgreSQL database with auto-generated REST API
- Row-level security enforced at the database level
- Real-time subscriptions out of the box
- Type-safe database queries end-to-end
- OAuth and email/password authentication
- Global CDN deployment
This is the foundation I use for every SaaS platform, marketplace, and booking system I build in Nepal. The stack scales from 1 user to 100,000 without architectural changes.
Need Help With This?
Let's talk about how I can architect a scalable solution for your business.
Get In Touch β