Elements vs SvelteKit

Markdown

SvelteKit is an open-source web app framework for Svelte, made by the Svelte team. Svelte is a UI framework with its own compiler: you write components in .svelte files that combine a script, HTML-like markup and styles, and mark reactive state with runes such as $state and $derived. SvelteKit adds routing based on folders and +page and +layout files, load functions that fetch a page's data, form actions that handle submissions, and rendering on the server, in the browser, or ahead of time as static files. It runs on Vite, and adapters package the built app for a Node.js server, static hosting, Vercel, Netlify or Cloudflare.

Elements builds the same kind of app. It is an integrated app environment, made for people and their agents: a project server that runs while you work, installing packages, running tests and migrations, and reporting every build error, with Postgres bundled and a deploy command for any Ubuntu server over SSH. It ships with a default framework that just works, with routing, server rendering and pages that update when their data changes, and with typed server functions, sessions, realtime data, background jobs and email. Pages are written in Elements HTML, plain HTML with reactive expressions that the build compiles and checks. The apps are TypeScript and run on Node.js.

A SvelteKit app comes together from add-ons. Past routing and rendering, sv add brings in Drizzle and drizzle-kit for the database and migrations, Better Auth for sign-in, Vitest and Playwright for tests, and an adapter for the host, and background jobs and email are yours to find. Each is its own project with its own config file and its own API, and each seam is a place to break: a Better Auth setup whose tables have to match the Drizzle schema, or a type error that the dev server never reports and that surfaces only when svelte-check runs. Elements is ready out of the box. Postgres, migrations, sessions, tests, jobs, email, realtime and deploy are engineered as one coherent system, so you build productively from the first minute. Between you and the platform there is only a thin layer: pages are HTML and CSS, queries are SQL, server calls are TypeScript function calls, and the framework words to learn are app.route(), session.login(), sql(), @rpc and tests.

At a Glance

Elements SvelteKit
What it is A project server and a default framework, with Postgres, tests and deploy A Svelte framework with routing and rendering, run on Vite
Templates Elements HTML: standard HTML with typed reactive expressions Svelte components, with runes marking reactive state
Routing Declared in index.ts with web standard URLPattern Folders and +page, +layout and +server files
Server functions @rpc: typed calls for reads and writes, built in Remote functions, turned on by two config flags; form actions for POST
Sign-in and sessions Built in, the same over HTTP and WebSockets Not included; the CLI can set up Better Auth
Database Postgres, bundled, with sql() built in Not included; the CLI can add Drizzle
Migrations Applied when you save them Not included
Build errors One build reports type errors, failing tests, migration errors and server code reachable from the browser svelte-check, Vitest and Playwright, each run on its own
Tests Part of the build Not included; the CLI can add Vitest and Playwright
Realtime LiveTable and channels query.live streams values to one page; reaching other users is yours to build
Background jobs and cron Built in Not included
Email Built in; messages are Elements HTML templates Not included
Deploy elements deploy to any Ubuntu server over SSH, usually under a second An adapter packages the build; hosting and the pipeline are yours

One Environment, Not a Stack of Add-Ons

Beyond routing and rendering, SvelteKit leaves each part of an app to an add-on. Its CLI, sv, adds them to a project: Drizzle for the database, Better Auth for sign-in, Vitest and Playwright for tests, Tailwind for styles, and an adapter for the host. Each is a separate project with its own configuration, its own docs and its own API. For background jobs, email and realtime, there is no add-on at all.

In Elements the database, sessions, the test runner, jobs, email and realtime ship together, and one build checks all the code that uses them. Styles are covered too: @elements/style is a design system of tokens, components and utilities, and elements create sets it up in every new app. An add-on saves an agent time only when it does something the agent cannot write quickly from standards. Tailwind is a case in point: an agent writes plain CSS fluently, so a utility vocabulary is one more thing to look up. Every add-on past that line is another config file and another seam. Leaving the sv add catalog does not mean leaving npm: Stripe, the AWS SDK and other packages install unchanged.

A framework still earns its keep. On a line from machine code to a package that subtracts two numbers, an agent wants the point in between where routing, typed server calls, sessions and a database driver are handled as plumbing, so the Elements app framework ships them as app.route(), @rpc, session.login() and sql(). Beyond that plumbing returns diminish, and each extra rune, block syntax or add-on API costs an agent more to learn and get right than it saves. There, the payoff is correction, knowing what it got wrong as soon as it gets it wrong, and in a stack of add-ons that answer is spread across svelte-check, Vitest and the dev server. Elements keeps the rest to HTML and CSS pages and TypeScript function calls, and spends its engineering on correction. One coherent system checks every template, function, query, test and migration, so every build error, from type checking, tests, migrations and program analysis, turns up in milliseconds. Its project server runs while you work and holds the build graph and build state in memory, so on every save the build state is updated almost instantly, and elements build -json answers agents and humans in microseconds (build). The editor, the terminal and every agent share that build, and server code has no async or await for an agent to forget.

Templates Are Standard HTML, Not Svelte Components

Svelte writes reactive pages as components in its own file format. Markup uses its own template syntax, {#if}, {:else}, {#each} and {@render}, and reactive values are declared with runes, keywords that only work in .svelte, .svelte.js and .svelte.ts files (runes).

Elements uses Elements HTML: standard HTML tags, TypeScript expressions in {}, and a few e: attributes for conditions and loops. A page declares its data as typed attributes on its opening tag, and when that data changes, the page updates. Nothing marks a value as reactive.

interface Task {
  title: string;
}

function addTask(tasks: Task[], form: { title: string }) {
  tasks.push({ title: form.title });
  form.title = "";
}

<html (private tasks: Task[] = [], private form: { title: string } = { title: "" })>
  <input value={form.title}>
  <button onclick={() => addTask(tasks, form)}>Add</button>
  <ul>
    <li e:for={task of tasks}>{task.title}</li>
  </ul>
  <p e:if={tasks.length === 0}>Nothing to do.</p>
</html>

The handler is a plain TypeScript function. Pushing onto the array adds the row, and clearing the field empties the input, because form inputs bind both ways by default. Where Svelte needs $state to know what to track, the Elements runtime works it out from the expressions and patches only the markup that reads the changed value, after a server render that sends finished HTML. The build checks templates with the rest of the code, so a mistake in a template shows up in the same build report as any other, with no separate checker to run.

Server Functions Are Built In, for Reads and Writes

SvelteKit has several ways to reach the server: load functions for reading a page's data, form actions that take a POST from a <form>, and +server endpoints you call with fetch. Each has its own conventions and its own way of passing data back. Its typed server calls, remote functions, are off by default: an app turns them on with two flags in its config, and they add a fourth convention, query, form, command and prerender functions in .remote.ts files.

In Elements it is a regular feature, with nothing to opt into. Mark a function @rpc and a page can call it for a read or a write, passing typed arguments and getting a typed result, live data included. The compiler swaps the call for a network request and leaves the body on the server. Where a remote function relies on file naming to stay server side, Elements checks each call: reaching sql, tx or session.login from browser code fails the build with "Security error: cannot call server code from the browser without going through an rpc function."

The Database and Sign-In Ship With Elements

A web app needs a database, and most need users who sign in. SvelteKit includes neither. For the database, the CLI adds Drizzle, an ORM with its own migration tool. For sign-in, the authentication docs point to the CLI's Better Auth setup, or to the Lucia guide for building session auth yourself.

In place of Drizzle, Elements ships Postgres itself, configured on your laptop and on each server it deploys to. Queries are plain SQL in the sql() function; the compiler makes each ${value} a bound parameter, which closes off injection. In place of drizzle-kit, a migration is a SQL file that takes effect as you save it, and at deploy time the pending ones apply together in one transaction or the release does not go out.

In place of Better Auth, sessions come with the framework: route handlers, server functions and templates read the same session whether the request arrived over HTTP or a WebSocket, and a page showing the user's name redraws when they sign in. Sign-in is a short server function that checks a password hash inside Postgres and calls session.login(), as the authentication recipe shows.

Realtime, Jobs and Email Are Built In

Chat, live dashboards and shared lists need the page to update for every user watching it. SvelteKit's remote functions include query.live, an async generator that streams values to one page, and the docs' notification example re-queries the database every second in a loop (remote functions). Getting one user's change to everyone else is left to the app, and SvelteKit has no WebSocket support ("Native support for web sockets" is an open issue with 305 thumbs-up), so an app adds a third-party service or runs its own WebSocket server. Background jobs, scheduled tasks and sending email are not included either.

Elements includes all three. A LiveTable keeps a query's rows current in each open tab: the person editing sees the change immediately while the server confirms it, and other viewers get that one row patched. For events that are not rows, a channel pushes messages from the server to subscribed browsers. Jobs and cron schedules run on a worker process, with the queue in the same Postgres database, so a job enqueued inside a transaction commits with the rest of the write. Email templates are written in Elements HTML and sent through the SMTP provider you configure.

Every Build Error Comes From One Build

An agent needs to know what its change broke: a failing test, a type error, a migration that no longer applies, server code the browser can reach. In SvelteKit, those answers come from separate tools: Vitest and Playwright for tests, added with the CLI, and svelte-check for types, a command you run on its own or in CI. Vite's dev server does not type check (Vite).

Elements folds tests, migrations, type checking and program analysis, such as server code reachable from the browser, into one build, built in milliseconds, so any red result is a red build. Only the tests whose code changed run again, and each one runs in a Postgres transaction that is rolled back afterward, leaving nothing to clean up. Where you would run svelte-check, Vitest and Playwright in turn, elements build -json reports every build error, from templates and code to migrations and tests, as one JSON result in microseconds, and the editor, the terminal and any agents running at once all read that same build.

Adapters Package the App; Elements Deploys It

SvelteKit adapters are "small plugins that take the built app as input and generate output for deployment." The official ones target Vercel, Netlify, Cloudflare, static files and Node.js. With the Node adapter, you copy the build output, package.json and production dependencies to your server, start it with node build, and set the adapter's environment variables for the reverse proxy you put in front of it. Tests, migrations and rollback are steps you add to your own pipeline.

Elements has no adapter step, and no build folder to copy. elements deploy targets an Ubuntu machine you can SSH into, rented anywhere, and the project server on your laptop syncs with its twin on that machine, sending only what differs. Expect a few seconds on a fresh server and usually under a second after that. The pipeline you would script around node build is already in it: tests on each machine, migrations once on the first machine, and a halt that leaves the current release serving if anything fails. The reverse proxy goes away too, since Elements configures the load balancer, TLS certificates and Postgres itself. That local Postgres is meant for a prototype or one server; past that, or when you want managed backups and scaling, point DB_HOST at a hosted Postgres such as DigitalOcean Managed PostgreSQL or Amazon RDS (setup).

Who Pays for SvelteKit

SvelteKit is an on-ramp to Vercel's metered hosting business. Vercel has funded full-time work on Svelte since 2021, writing at the time, "With Vercel's backing, Svelte can get even more ambitious" (Vercel). Svelte also takes donations through Open Collective. What Vercel sells is metered hosting (Vercel pricing, Elements vs Vercel), and Vercel is one of the five targets of SvelteKit's official adapters (adapters).

Elements is a company that sells its tooling directly, per machine, with nothing metered (pricing), so what it earns from is the tooling itself. It charges for the tooling, not for the framework: the runtime packages your app depends on, @elements/app among them, are MIT licensed, and everything you build is yours (licenses).

Moving From SvelteKit

An agent can port a SvelteKit app easily. Svelte markup is already HTML with expressions in braces, so its components become Elements HTML templates, and its routes and server code map onto app.route() lines and @rpc functions. You keep TypeScript and your npm packages, and in place of add-ons and an adapter you get sign-in, a database, realtime, jobs, email, tests and deploy, ready in one system.

Questions

What is the difference between Elements and SvelteKit?

SvelteKit is an open-source framework for Svelte that handles routing, rendering and form handling, and leaves the database, sign-in, tests, migrations, jobs and email to add-ons, other libraries and services. Elements is an integrated app environment with a project server and a default framework. The Elements app framework handles routing, rendering and forms with sign-in, realtime, jobs and email ready in it, and the system around it comes with Postgres, migrations, tests and deploy already connected. A Svelte project reports problems through svelte-check, Vitest and the dev server; Elements reports them through one build.

Is Elements a SvelteKit alternative?

Yes. It replaces SvelteKit and the add-ons sv add would install, with nothing to wire together, and jobs, email and realtime, which have no add-on, come ready too.

Can I use Svelte components in Elements?

No. Pages are Elements HTML, which has typed expressions but no runes and no {#if} blocks. Both are HTML with expressions in braces, so an agent ports a Svelte component to an Elements HTML template directly.

Does Elements do server-side rendering?

Yes, on every request. Each page arrives as complete HTML for crawlers and new visitors, and it is also fully reactive in the browser, with no compromise.

Can I use npm packages in Elements?

Yes. Packages land in an ordinary node_modules folder, unmodified, and the build runs the install for you.

How do I move a SvelteKit app to Elements?

An agent can port a SvelteKit app easily. Svelte markup is already HTML with expressions in braces, so its components become Elements HTML templates, and its routes and server code map onto app.route() lines and @rpc functions. You keep TypeScript and your npm packages, and in place of add-ons and an adapter you get sign-in, a database, realtime, jobs, email, tests and deploy.