Caricamento...
Caricamento...
Spent a weekend rebuilding the static parts of sapan.dev in Astro to see what the islands model actually feels like. TTI dropped from ~600ms to ~150ms, the JS bundle went from 240KB to 11KB — and there were a few sharp edges that made me appreciate what Next.js handles for free.
Out of curiosity, I spent a weekend rebuilding the static parts of sapan.dev in Astro. The portfolio is built in Next.js 16 with App Router, but most pages are content-heavy — bio, project lists, the blog index — with only a handful of truly interactive surfaces (theme switcher, language switcher, contact modal). I wanted to see whether the islands model would meaningfully change anything in that shape of project.
The numbers were striking on the landing page: TTI dropped from around 600ms to 150ms, and the JS bundle went from 240KB to 11KB. Most of that delta came from React not shipping at all on pages where nothing was actually interactive.
Every time a web framework trend cycles, the underlying question is the same — where does the work happen, server or client? Astro takes a pragmatic position. Most pages are mostly static. Ship static HTML. Hydrate only the components that actually need interactivity. Everything else stays HTML.
An island is a single interactive component embedded in an otherwise static page. Each island has its own JavaScript bundle and hydrates independently. The rest of the page — layout, copy, images — is HTML. No virtual DOM, no hydration cost, no framework runtime.
---
// Server-only code runs at build time
import NavBar from '../components/NavBar.astro';
import SearchBox from '../components/SearchBox.jsx';
import Newsletter from '../components/Newsletter.jsx';
const posts = await fetch('https://cms/posts').then((r) => r.json());
---
<html>
<body>
<NavBar /> <!-- Static HTML, zero JS -->
<main>
<!-- Interactive island — hydrates in browser -->
<SearchBox client:load />
<ul>
{posts.map((p) => <li>{p.title}</li>)}
</ul>
<!-- Hydrates only when visible in viewport -->
<Newsletter client:visible />
</main>
</body>
</html>Info
The client:* directives are the key insight. client:load hydrates immediately, client:idle hydrates when the browser is idle, client:visible hydrates when the component enters the viewport, and client:media hydrates based on a media query.
Astro runs React, Vue, Svelte, Solid, and Preact components side by side on the same page. You can use a React component library for your complex widgets and ship lightweight Preact for the rest. Each framework contributes its runtime only to its own islands.
Astro builds at build time by default, with SSR available on demand. Next.js defaults to server-rendering with static generation opt-in. For a documentation site that updates weekly, Astro is dramatically simpler. For a dashboard with per-user data, Next.js is the right tool.
If you have a Gatsby or Hugo site, Astro is an obvious migration — MDX, file-based routing, and content collections all map over cleanly. If you have a Next.js site that is 90% static content, consider splitting — keep the app in Next.js and move the marketing/blog sections to Astro.
// Astro Content Collections — typed frontmatter
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
publishedAt: z.date(),
author: z.string(),
tags: z.array(z.string()),
featured: z.boolean().default(false),
}),
});
export const collections = { blog };Suggerimento
For a hybrid strategy, deploy both. Astro on astro.yourdomain.com for marketing/docs, Next.js on app.yourdomain.com for the product. Each framework does what it is best at.
I am not migrating sapan.dev to Astro full-time. The i18n story for 16 locales is a lot smoother in Next.js (next-intl is doing real work there), and React Server Components in App Router cover most of what islands solve for me without leaving the React mental model. The sharp edges I hit in the weekend port were mostly around the i18n routing and around React Query for the contact form — both solvable, neither delightful.
But for content-heavy sites where the interactive parts are isolated — a documentation site, a marketing landing page, a single-purpose blog — Astro is genuinely better than what we are doing in Next.js right now. Worth knowing about, even if it is not your daily framework.
Altro in Tooling
Migrated sapan.dev from ESLint + Prettier to Biome over a weekend — pre-commit hooks went from ~12s to under a second, and the config dropped from three files to one. Notes on what migrated cleanly and what I had to keep ESLint for.
The WPDeveloper plugin suite — BetterDocs, NotificationX, SchedulePress, BetterLinks, and a few smaller ones — was a monorepo that took close to 8 minutes for a clean build. After Turborepo with remote caching, the same build dropped to ~40 seconds on warm caches. Notes on what actually moved the needle.
The xCloud v1 frontend runs on Vue 3 + Vite + a Laravel API — the Vite dev server is what makes that loop tolerable. Notes on what Vite 6 ships, where Rolldown changes things, and the few cases where I still hit friction.