Islands architecture in Next.js: performance-first web design
Serve static content fast and hydrate only the interactive parts: islands architecture in Next.js, and why it also helps teams share a product.
Modern web development often feels like a tug of war between interactivity and performance. On mobile especially, large JavaScript bundles and hydrating the whole page can cripple the experience. Islands architecture offers a smarter way forward: serve the static content fast and hydrate only the interactive parts.
Astro is built around the pattern, but you can get most of it in Next.js too. Here’s how.
What islands architecture is
- The page is rendered on the server.
- Only the interactive components, the “islands”, are hydrated in the browser.
- Each island is self-contained: it doesn’t depend on shared global state or on the rest of the page.
The result is faster loads, less JavaScript and better performance, on mobile above all.
Why Next.js
Next.js renders on the server and can generate static pages, which makes it a good fit. With the App Router, components are Server Components by default: they render to HTML and send no JavaScript to the browser. Only the components you mark with 'use client' are hydrated, and those are your islands.
- Faster interaction: the static content arrives as plain HTML, and the islands hydrate on their own.
- Smaller bundles: only the islands’ code is sent. Next.js still sends React itself, so the baseline is heavier than Astro’s.
- Better on mobile: less CPU and memory spent on low-end devices.
React meets Angular at Rabobank
At Rabobank, I worked on a project built with React. Midway through, another team joined the initiative, and they worked in Angular. Rather than force a rewrite or compromise on tooling, we took an islands approach.
Each team kept its own stack, and we embedded the Angular components as isolated islands in the React-rendered pages. Both teams could work without stepping on each other’s toes, and performance held up across devices. The pattern gave us the freedom to mix technologies and still deliver one seamless experience.
Governance benefits
Beyond performance, islands help with governance and team autonomy:
- Independent stacks: teams choose their own framework (React, Angular, Vue) without affecting the rest of the app.
- Clear ownership: each island can be owned, maintained and deployed by one team, which cuts the dependencies between teams.
- Collaboration that scales: several teams can contribute to the same page or product without touching each other’s code.
- Simpler testing: isolated components mean isolated tests, so functionality and performance are easier to check.
- Gradual migration: legacy parts can be replaced one island at a time, without a full rewrite.
In large organisations, the architecture becomes a governance tool: distributed teams can move independently while the product stays coherent.
Building an island in Next.js
Take a mostly static page with one interactive widget: a counter.
1. The interactive component
// app/counter.tsx
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}'use client' marks the island: this component, and only this one, is sent to the browser and hydrated.
2. The static page around it
// app/page.tsx: a Server Component, no JavaScript in the browser
import Counter from './counter';
export default function HomePage() {
return (
<main>
<h1>Welcome to my site</h1>
<p>This content is static and rendered on the server.</p>
<Counter />
</main>
);
}The page arrives as HTML with the counter already in it, and only the counter is hydrated.
On the Pages Router
The Pages Router has no Server Components, so every page is hydrated as a whole. The closest you get there is next/dynamic, to load a widget’s code only in the browser:
import dynamic from 'next/dynamic';
const Counter = dynamic(() => import('../components/Counter'), { ssr: false });That keeps the counter’s code out of the first bundle, but the counter isn’t in the server’s HTML either: it appears once its script has loaded, so reserve its space to avoid a layout shift.
Tips for effective islands
- Keep islands small: put
'use client'as far down the tree as you can, because everything it imports becomes client code too. - Avoid global state: keep each island self-contained.
- Defer what’s below the fold: load an island’s code only when it scrolls into view, with an Intersection Observer or a library such as
react-intersection-observer. - Split bundles: Next.js does it for you, as long as your imports are clean.
How it compares
- Single-page app: rendered in the browser, the whole app hydrated. Large bundles, the weakest on mobile.
- Multi-page app: rendered on the server, with little or no JavaScript. Small and fast, with limited interactivity.
- Islands in Next.js: rendered on the server, hydrated island by island. Small bundles and strong mobile performance, with interactivity where it’s needed.
When to use it
It suits marketing pages with embedded widgets, blogs with comment sections, product pages with filters or carousels, and dashboards with isolated charts. Avoid it when the app is interactive and stateful across the whole page, or relies heavily on client-side routing and global context.
Islands architecture isn’t just a buzzword. It’s a practical way to build fast, resilient, mobile-first web apps, and in Next.js you can adopt it without switching frameworks: hydrate what matters and let the rest stay lean. Whether you’re resolving a clash of stacks between teams, as we did at Rabobank, or simply optimising for mobile, it gives you flexibility, scale and clear governance.
