For content-heavy sites in 2026 — blogs, docs, marketing pages, e-commerce listing pages — the right framework is rarely Next.js. Astro is the default for “mostly static, lightly interactive.” SvelteKit is the default for “interactive throughout.” This post is the practical comparison and decision rule.
The split
The web in 2026 has two flavors of “site”:
- Content sites — words, images, light interactivity. Blogs, docs, marketing, content portals. Heavy SEO requirements.
- App sites — dashboards, forms, real-time data, lots of state. Often behind a login.
Different tools optimize for different ends:
| Astro | SvelteKit | Next.js | Remix / TanStack Start | |
|---|---|---|---|---|
| Default JS shipped | Near-zero | Small (Svelte runtime) | Larger (React) | Mid |
| Islands hydration | Yes, first-class | Built-in via Svelte | Limited (React Server Components fill the gap) | Limited |
| Mix frameworks | Yes (React + Vue + Svelte + …) | No | No | No |
| Interactivity sweet spot | Light | Heavy | Heavy | Heavy |
| Best for | Content | Apps | Apps | Apps |
For this blog (the one you’re reading), the right choice would be Hugo or Astro. For a fully-interactive multiplayer SaaS, the right choice is SvelteKit or Next.js.
Astro — for content
What Astro does
Astro renders pages on the server (or at build time) into pure HTML. It only ships JavaScript for the explicitly-interactive components — “islands.”
---
// src/pages/blog/[slug].astro
import { getCollection, getEntry } from "astro:content";
import Layout from "../../layouts/Post.astro";
import LikeButton from "../../components/LikeButton.svelte";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<Layout title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<Content />
<LikeButton client:visible postId={post.slug} />
</article>
</Layout>
That client:visible directive is the magic. The LikeButton is a Svelte component; it ships JS only when scrolled into view. Everything else is HTML.
Astro’s hydration directives
| Directive | When the JS loads |
|---|---|
client:load | At page load |
client:idle | After the page is idle |
client:visible | When scrolled into view |
client:media={query} | When media query matches |
client:only="svelte" | Skip server render; client-only |
A page can have 0, 1, or 50 islands. The default is zero. Compare to Next.js where everything is JavaScript by default and you opt out.
Content collections
Astro 4+ has typed content collections — your markdown frontmatter becomes TypeScript:
// src/content/config.ts
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
}),
});
export const collections = { blog };
const posts = await getCollection("blog", ({ data }) => data.pubDate < new Date());
// ^^^^ typed Array<{ data: { title: string; ... }, slug: string, ... }>
Markdown for content, type-safe references in code. The right shape for a blog or docs site.
MDX, images, view transitions
Astro ships:
- MDX with full TypeScript / JSX support.
<Image>component with automatic resizing and modern formats (AVIF, WebP).- View Transitions (CSS standard) for SPA-like navigation without React Router complexity.
- Markdoc / Markdown flavors as needed.
For docs sites, the Starlight template (built on Astro) is essentially “Hugo + better TypeScript”. For marketing, the templating + island story beats anything else.
SvelteKit — for apps
What SvelteKit does
SvelteKit is the React-Next-Router-Remix shape, but Svelte. Server-rendered routes, progressive enhancement, form actions, type-safe loaders.
<!-- src/routes/users/+page.server.ts -->
import { db } from "$lib/db";
export const load = async ({ url }) => {
const q = url.searchParams.get("q") ?? "";
return { users: await db.query.users.findMany({ where: q ? ilike(users.email, `%${q}%`) : undefined }) };
};
<!-- src/routes/users/+page.svelte -->
<script lang="ts">
import { enhance } from "$app/forms";
let { data } = $props();
</script>
<form method="GET" use:enhance>
<input name="q" />
<button>Search</button>
</form>
<ul>
{#each data.users as user}
<li>{user.email}</li>
{/each}
</ul>
+page.server.ts runs on the server only — DB queries are safe. +page.svelte runs on both. use:enhance upgrades the form to AJAX while still working without JavaScript.
Svelte 5’s runes ($props, $state, $derived) replaced the magic Svelte 4 syntax with explicit declarations. The model is closer to React Hooks but with smaller compiled output.
Form actions
// src/routes/login/+page.server.ts
export const actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
const email = data.get("email") as string;
// validate, authenticate
cookies.set("sid", sid, { httpOnly: true, secure: true, sameSite: "lax", path: "/" });
throw redirect(303, "/dashboard");
},
};
Forms are first-class. Progressive enhancement is real — your app works without JS and gets snappier with it. The Next.js equivalent is Server Actions (Next.js 15 Server Components in Production ).
Why pick SvelteKit
- Smaller bundle for the same features. Svelte compiles away the framework runtime.
- Cleaner reactivity — runes are easier to reason about than React hooks for many people.
- First-class progressive enhancement — forms still work without JS.
- Excellent DX — fast dev server, helpful errors, tight integrations.
The fragility historically: ecosystem. In 2026 it’s much better but still smaller than React. For a fresh project, Svelte’s ecosystem is enough; for a Fortune-500 codebase that needs every weird npm package, you’ll occasionally hit gaps.
Performance numbers
Cherry-picked, but representative:
| Next.js 15 | SvelteKit 2 | Astro 5 | |
|---|---|---|---|
| Static blog page TTFB | 60ms | 50ms | 30ms |
| Static blog page LCP | 1.4s | 1.1s | 0.7s |
| JS bundled (blog page) | ~80 KB | ~25 KB | ~5 KB |
| JS bundled (CRUD app) | ~120 KB | ~40 KB | ~50 KB (with islands) |
For a blog: Astro is the clear win. For a CRUD app with rich interactivity: SvelteKit beats Next.js on bundle, ties on perceived perf.
For SEO specifically — the topic of a blog like this — Astro’s near-zero default JS makes Lighthouse / Core Web Vitals scores cleaner with no effort. That matters for ranking.
A real decision rule
| If your site is… | Use |
|---|---|
| Blog / docs / marketing / content portal | Astro |
| Mostly static + a few interactive widgets | Astro |
| Login-gated dashboard with forms and real-time | SvelteKit |
| Complex stateful client app | SvelteKit or Next.js |
| Already deeply Next.js | Stay |
| Pure SPA, no SSR needed | Vite + Svelte / Solid |
There’s no “default framework” anymore. Pick by traffic shape.
What both share (and what’s underrated)
- Vite under the hood — fast dev, hot reload, plugin ecosystem.
- TypeScript first. No coercing it after the fact.
- File-based routing. Convention over config;
src/pages/...orsrc/routes/.... - Adapter ecosystem — deploy to Vercel, Netlify, Cloudflare, Node, static, AWS Lambda. One codebase, many targets.
- Zod everywhere. For schemas, form validation, env config.
Astro production checklist
For a Astro blog/docs/marketing site heading to prod:
- Content collections defined and typed.
-
<Image>component for every image. -
client:visible(notclient:load) for non-critical interactive components. - Sitemap integration (
@astrojs/sitemap). - RSS integration (
@astrojs/rss). - View Transitions enabled.
- Per-page meta tags (Open Graph, Twitter, canonical).
- Lighthouse run on the hot pages — aim for >95 across the board.
- Static export where possible; SSR only where dynamic.
For SEO specifics, pair with the patterns from my SEO config setup (the same JSON-LD, BreadcrumbList, FAQPage patterns work across frameworks).
SvelteKit production checklist
- Server-only code in
+page.server.ts/+server.ts. - Form actions for mutations (progressive enhancement).
- Auth via cookies + server hooks (see Authentication in 2026 ).
- Adapter for the target (Vercel, Cloudflare, Node).
- Static prerendering where pages don’t need per-request data.
- Sentry / OTel for errors (see OpenTelemetry End-to-End ).
Common mistakes
Astro
- Hydrating too eagerly.
client:loadeverywhere defeats the architecture. Default toclient:visible. - Mixing too many frameworks. Choose one or two, not all six.
- Forgetting the build step for collections.
astro:contenttypes regenerate onastro dev; production builds needastro check.
SvelteKit
- Putting secrets in
+page.svelte. Anything client-side ships to the browser. Secrets go in+page.server.ts. - Not handling errors.
+error.svelteper route is your friend. - Leaking server data via
data. Strip what the client doesn’t need before returning.
Read this next
- Next.js 15 Server Components in Production — the React side.
- Modern TypeScript Backend with Hono on Bun — the API side.
- Bun vs Node.js in 2026 — runtime decisions.
If you want a “Hugo-quality content site” Astro starter (MDX, RSS, sitemap, SEO partials, view transitions), it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .