Collection of our preferred libraries / tools to use with SvelteKit for building web apps.
To run the project locally:
npm run dev
To create a production version of your app:
npm run build
To run the production version locally:
node build
Typescript = good, also native to SvelteKit.
Used by svelteKit to build project.
Great plugin to inspect the bundle size of libraries.
Check example for how to setup. There needs to be a global variable in common, that everyone uses, but it must be initialized in server/client hooks, so the server code doesn't leak into the client.
In the long run, it is better to standardize code. Even if the standard is bad, it's better than no standard. Make sure to enable eslint + prettier formatter on save on your IDE. Check example project for ts and svelte plugins.
SvelteKit is GREAT for web apps and has REALLY GOOD documentation. The only case where I would not use SvelteKit is for an almost-static website with no DB, in which case I'd go with Astro (and maybe even drop Svelte at that point in favor of Astro components).
Easy way to fully handle a DB, includes schema, migration, and type-safe queries.
Supports a lot of DBs, like PostgreSQL and SQLite, probably better to use SQLite since we're using this for simple projects anyways.
Good starting points for learning:
Schema declaration basics
SQLite connection
SQLite schema column types (also see example in this repo for how to handle dates and enums)
Schema relations
Queries
Generate Valibot schema from Drizzle schema
Drizzle offers a lot of value: by declaring DB schemas once, we also get TS types and Valibot schema validators, we don't have to repeat 3 times: DB + model + validation.
Note: generated valibot schemas can't be imported in frontend, so if this is required, the solution is to declare them separately and use 'satisfies' to ensure they have the correct fields (check example). Also, said common schema must be extended in the backend to add async validations that can only be executed in the backend (check example).
It probably gets worse the more complex the database is, at which point the only solution so far is to manually manage the DB (and probably not use a JS backend at that point, at least for the API part).
To migrate DB schema (assuming fully managed by Drizzle, read migration types):
npx drizzle-kit push
To launch Drizzle Studio db inspector:
npx drizzle-kit studio
Lucia has good instructions to easily implement auth with sessions. Sessions are saved to db with DrizzleORM (or with any db you use). Sessions are sent to client with svelteKit cookies.
SvelteKit docs also recommend Lucia and talk about auth with sessions vs JWT.
Generally better than Zod: more lightweight and more customizable. The only pain point is the i18n and generally getting readable errors to show to the user, but there are some clever ways to make it easier (check _valibot.ts in example).
Useful link: generate valibot schema from TS type
For simpler apps, ideally we can just use SvelteKit's load() and actions APIs. These are type-safe and also forces you to put state into the url/cookies, which is good. Especially interesting is the streaming load() promises API, this allow us to have SSR with immediate response, and then stream down data from DB that takes longer to load (for prod be careful about nginx config and other bugs, test well).
Makes your href and fetch calls type safe and warns when a route changes, making your reference broken.
On more complicated webapps, we might need a full-blown custom RESTful API.
Ideally, we also use OpenAPI definitions to generate types and serialization code.
At this point, we might as well use a real language in the backend.
Options for generating frontend types and APIs (haven't tried them yet):
https://github.com/astahmer/typed-openapi
https://github.com/OpenAPITools/openapi-generator-cli
If for some reason we're still using JS on the backend, try generating openAPI specs from zod schema definitions:
https://www.speakeasy.com/openapi/frameworks/zod
https://github.com/asteasolutions/zod-to-openapi
Really convoluted and hacky. Has to use its own router instead of integrating with svelteKit. Type inference (its main sale point) is buggy and hard to implement.
Best frontend JS framework by far, and also integrates with SvelteKit, of course.
Really easy to set up and gives user feedback when loading is taking a long time on bad connections.
Mandatory if the app needs complex forms. Helps automate most of the state management and communication of a form, and integrates super nicely with SvelteKit and Valibot (check example).
Make sure to check out the example and implement all features properly, this includes:
Frontend validation with Valibot.
Error handling (returning validation errors from backend and showing them in form).
Show loading spinner when submitting form.
Maintaining form state on refresh with SvelteKit snapshot().
Tailwind = good.
Easily use icons from https://heroicons.com/, without having to copy-paste them.
If using shadcn, use lucide-svelte instead, since it's already used by shadcn.
The UI can be built with html / tailwind alone, but it's often useful to have higher level components, especially since we dont have designers.
If using Svelte 5, make sure to use https://next.shadcn-svelte.com instead. Really good so far, has both simple components like buttons with understandable code that can be customized, and complicated components that save a lot of work. Has good integrations with Svelte and Superforms. The only negative so far is that it imports a lot of libs, some of them decently heavy, we need to keep track of this.
Useful links:
Theming:
https://next.shadcn-svelte.com/docs/theming
https://next.shadcn-svelte.com/themes
Theme generators:
https://zippystarter.com/tools/shadcn-ui-theme-generator
https://shadesigner.com/
Untested. Seems to have more variety of high level components than shadcn, otherwise I don't see the merit.
Headless tailwind components. The only sane way to use it is to create our own Svelte components from it and use them.
The main issue is that each component requires to import JS (even for things that can be done without JS), some components can be used, but it doesn't really work as a main UI lib as well as chadCN does.
Fide says NO.
Trying to do high complexity components is even discouraged in the tailwind documentation itself. Daisy often interferes with vanilla tailwind and makes it so you can't copy/paste tailwind code without it breaking. Ideally, we want a library that declares Svelte components, or headless tailwind components to create our own.
Modern browsers support view transitions. This is really easy to setup (see example in main layout and app.css), and makes navigation look way nicer. Reading the docs, it also seems to support a lot of complicated customization options, but just the default looks really nice. If app.css is set up properly, it even works in full ssr mode without JS.
The idea is simple: backend will read the value of a cookie "FullSSR", and if it is true, it will await all Promises instead of streaming them. This will make the site fully SSR: initial response time will be slower, but it should be usable without JS.
By adding a simple vanilla JS script to app.html (check example), we can listen to JS errors that happen before hydration, and prompt the user to refresh in case of a connection error, or use full SSR if an old browser doesn't support our JS.
After hydration, listen to errors, log them, and in case of connection errors, show a toast to the user prompting a reload (see example hooks.client.ts). Beware that prompting the user to reload is only a good solution if the page is set up in a way where a lot of state won't be lost on reload.