PrevelteKit is a minimalistic (>500 LoC) web application framework built on Svelte 5, featuring single page applications with built-time pre-rendering using Rsbuild as the build/bundler tool and jsdom as the DOM environment for pre-rendering components during build.
The inspiration for this project comes from the Vue SSR example in the Rspack examples repository. This project adapts those concepts for Svelte, providing a minimal setup.
While there is a go-to solution for SSR for Svelte (SvelteKit), I was missing a minimalistic solution just for build-time pre-rendering. There is the prerender option in SvelteKit, but it's part of SvelteKit that comes with many additional features that might not be necessary for every project.
From an architectural point of view, I prefer the clear separation between view code and server code, where the frontend requests data from the backend via dedicated /api endpoints. This approach treats the frontend as purely static assets (HTML/CSS/JS) that can be served from any CDN or simple web server.
Meta-frameworks such as Next.js, Nuxt.js, SvelteKit blur this separation by requiring a JavaScript runtime (Node.js, Deno, or Bun) to handle server-side rendering, API routes, and build-time generation. While platforms like Vercel and Netlify can help with handling this complex setup (they are great services that I used in the past), serving just static content is much simpler: deploy anywhere (GitHub Pages, S3, any web serve) with predictable performance. You avoid the "full-stack JavaScript" complexity for your deployed frontend - it's just files on a server, nothing more.
- ⚡️ Lightning Fast: Rsbuild bundles in the range of a couple hundred milliseconds
- 🎯 Simple Routing: Built-in routing system
- 🔄 Layout and staic content pre-rendered: With Svelte and hydration
- 📦 Zero Config: Works out of the box with sensible defaults
- 🛠️ Developer Friendly: Hot reload in development, production-ready in minutes
- 🛡️ Security: Docker-based development environments to protect against supply chain attacks
Make sure you have the following installed:
- Node.js (Latest LTS version recommended)
- npm/pnpm or similar
# Create test directory and go into this directory
mkdir -p preveltekit/src && cd preveltekit
# Declare dependency and the dev script
echo '{"dependencies": {"preveltekit":"^1.0.14"}, "scripts": {"dev": "preveltekit dev"}}' > package.json
# Download dependencies
npm install
# A very simple svelte file
echo '<script>let count = $state(0);</script><h1>Count: {count}</h1><button onclick={() => count++}>Click me</button>' > src/Index.svelte
# And open a browser with localhost:3000
npm run dev
One example is within this project in the example folder, and another example is the notary example. The cli supports the following: dev/stage/prod.
npm run dev
This starts an Express development server on http://localhost:3000, with:
- Live reloading
- No optimization for faster builds
- Ideal for rapid development
npm run build
The production build:
- Generates pre-compressed static files for optimal serving with best compression:
- Brotli (
.br
files) - Zstandard (
.zst
files) - Zopfli (
.gz
files)
- Brotli (
- Optimizes assets for production
npm stage
The development server prioritizes fast rebuilds and developer experience, while the production build focuses on optimization and performance. Always test your application with a stage and production build before deploying.
To build with docker in production mode, use:
docker build . -t preveltekit
docker run -p3000:3000 preveltekit
To run in development mode with live reloading, run:
docker build -f Dockerfile.dev . -t preveltekit-dev
docker run -p3000:3000 -v./src:/app/src preveltekit-dev
PrevelteKit uses rsbuild.config.ts for configuration with sensible defaults. To customize settings, create an rsbuild.config.ts file in your project - it will merge with the default configuration.
The framework provides fallback files (index.html and index.ts) from the default folder when you don't supply your own. Once you add your own index.html or index.ts files, PrevelteKit uses those instead, ignoring the defaults.
This approach follows a "convention over configuration" pattern where you only need to specify what differs from the defaults.