Pre-amble
I try to write my posts for both engineers and non-engineers so I begin each post with a definition of terms listed in the order in which they appear in the article. This is also a good way to quickly see the concepts talked about in the piece. Those in the know can skip to the content below.
Quick Glossary
- Web Frameworks
- UX & DX
- Shipping
- JavaScript
- Client & Server
- Static Content
- Latency
- Payload Size
- Caching
- Cache Hit Ratio
- Rendering Patterns
- The Web Platform
- Hydration
- The DOM
- Native Features
- Performance Audit
- Refactor
Web Frameworks: A pre-built "starter kit" or a set of digital blueprints that gives programmers all the basic tools and foundations they need to build a website, so they don't have to reinvent the wheel for every new project.
User Experience (UX) & Developer Experience (DX): UX is how easy it is for someone to use a finished product, like a microwave, while DX is how easy it is for the builder to put that microwave together using the tools and instructions provided to them.
Shipping: The process of sending all the code and files from our company's computers over the internet so they can be delivered to and displayed on your screen.
JavaScript: One of the most popular and prevalent languages for giving websites instructions on how they should load on your screen and react to your interactions.
Client: The device you are using—like your phone, tablet, or laptop.
Static Content: Online content that is non-interactive, often also called 'read-only'. The opposite of a static piece of content might be a login portal.
Latency: The "travel time" or delay it takes for a request to zip across the internet from your house to the computer holding the website and back again.
Payload Size: The total "weight" or amount of digital data being sent to your device; just like a heavy physical package, a large payload size takes longer to move and open.
Caching: Keeping a copy of website or part of a website one your local device so it doesn't have to travel all the way back to the source every time you reload or return to the page.
Cache Hit Ratio: A measurement (usually a percentage) of how often the system successfully finds a requested file in its "quick-access" storage (the cache) versus how often it has to go all the way back to the main source to get it. A high hit ratio means the site feels much faster for the user.
Rendering Patterns: The different ways a website can be "cooked" and served to your screen, ranging from preparing the whole meal in the kitchen before you arrive to letting you assemble the ingredients yourself at the table.
The Web Platform: Think of it like the "built-in" features of a modern house—such as the lights, plumbing, and heating—that come ready to use the moment you move in.
Hydration: Think of this like "rehydrating" a packet of dried noodles. The server sends over the "dry" HTML (the structure), and then JavaScript comes in and "soaks" it to make it soft, flexible, and interactive.
The DOM (Document Object Model): The "skeleton" or digital map of a website. It is how the browser organizes every button, heading, and image so it knows exactly where everything sits on your screen.
Native Features: Tools that are already built into your web browser (like Chrome or Safari) by default. Using these is faster because the browser doesn't have to "learn" how to use them; it already knows.
Performance Audit: A digital health check-up. A tool scans the website and gives it a score based on how fast it loads and how "heavy" the files are.
Refactor: The process of cleaning up or rewriting code to make it run better without changing what the user actually sees. Like tidying up the wiring behind a TV—it looks the same in front, but it's safer and more efficient behind the scenes.
Codebase: The entire collection of "blueprints" and instructions (code) that make up a specific software project or website.
The transformation of the web and its effect on user experience.
From static restaurant menus to complex reservation systems
The web has evolved dramatically since its inception in the early 1990s. What began as a simple information exchange system has transformed into a sophisticated application platform. We’ve moved from static restaurant menus to complex reservation systems. This evolution has been driven largely by modern web frameworks, with React leading the charge as by far the most popular framework.
The 2024 State of JS survey shows React use at 31% over its nearest competitor
But this progress has come with a cost. While React and similar frameworks have given us incredible developer experiences, these benefits can come at the expense of user experience. When heavier framework solutions are used to solve problems that have lightweight answers the user suffers. Something I try to keep front of mind: Developer Experience (DX) should not trump User Experience (UX). You want to dial in both, but you need to know where your responsibilities really lies.
The Problem: We’re Shipping Too Much JavaScript
As a frontend engineer, my responsibility is to ensure a consistently fast, responsive, and, when relevant, search engine optimized experience across the platform. However when I ran performance audits on the domains I manage, they consistently revealed the same issues: We were shipping too much JavaScript to our users.
Lighthouse audits clearly reveal the top culprits of poor performance.
The root cause was straightforward: we were sending large JavaScript bundles to the client regardless of what the page actually required. Even pages that are mostly static were being forced to download and process entire framework libraries. While poor UX can certainly stem from bad design, from a purely engineering perspective, latency and payload size are the main areas we need to optimize to fix this.
To solve it, I look at it as a two-pronged attack: first, we must optimize the amount of JavaScript we ship; second, we must lock in our caching strategies—across the browser, application, and edge. I will write a follow up post on what I did to achieve a 15% increase in our cache hit ratio.
Optimizing JS involves leveraging the most lightweight rendering architecture for the product you’re building. There are dozens of patterns to choose from, and the "right" choice depends entirely on what you're trying to ship.
If you want a deep dive, I can’t recommend this 7-minute video enough, it speed-runs through ten of the most popular rendering patterns, and it’s quite an achievement in technical brevity.
As you can guess from the title of this post we are going to dive into Islands Architecture (IA) as the solution to our issue. IA tells us that to fix our latency and payload size issues we need to ensure we only ship JavaScript to the client for the interactive components that actually need it. One of the most powerful and efficient ways to minimize this JS is writing your code with an HTML-First philosophy.
The HTML-First Philosophy
The principle is straightforward — always send a complete, meaningful HTML document from the server first. The JavaScript you ship should be essential for the experience, not just reinventing functionality the browser already provides natively.
The Three Golden Rules (Principle of Least Power)
- If you can do it with HTML, use HTML
- If you can’t do it with HTML, use CSS
- If you can’t do it with HTML or CSS, use JavaScript that executes on the server
A note of these rules, there are actually 6 in total and they come from Tony Ennis' HTML-First Manifesto. A highly recommended read. For our purposes of lightly diving into this topic these three suffice. This approach might sound obvious, but it’s easy to forget when you’re deep in “frameworkitis” — a term coined by Juho Vepsäläinen to describe the overuse or misuse of web frameworks.
As Juho explains in his excellent paper “The Case for HTML First Development”:
Although web frameworks make it easy to create applica-
tions, leveraging them does not always mean you are getting
most out of the web platform. Overuse, or misuse, of web
frameworks could be dubbed as frameworkitis and it means
you are missing out on technical solutions outside of your
framework or may be forced to work against it to go your way.
By putting emphasis on the capabilities of modern HTML,
HTML First development asks developers to consider how far
they can go with HTML, CSS, and finally JavaScript before
prior options are considered.
The HTML-First approach doesn’t mean abandoning frameworks — it means being aware of what the web platform can do for you and considering whether you could do more with less.
Modern HTML Has Evolved
You might be thinking: Frameworks became popular because HTML couldn’t keep up with modern requirements. Perhaps you would say
"I stopped writing HTML because the complexity of what I was being asked to do from a product and design perspective exceeded the capabilities of HTML".
This was certainly valid a few years ago, but the rate of improvement on the web platform has accelerated dramatically in recent years.
Modern HTML now includes powerful native elements like:
-
<dialog>for modals and popups -
<details>and for accordions and collapsible content - Form validation attributes
- The Popover API
- View Transitions API
Many interactions that once required JavaScript can now be achieved with pure HTML and CSS. To get a sense of where we are (or were, given this video is now over a year old) check out Tony walking through the advancements in HTML at Big Sky Dev Con in Montana in 2024.
Increased rate of improvement on the web platform in the last few years.
A great way to stress-test your knowledge of modern HTML is to check out the latest State of HTML survey. The first section is essentially a reality check; it runs through a list of native HTML features that solve problems we typically reach for a framework or JavaScript to handle. It simply asks if you've heard of or used them before—it’s the perfect way to see where you can start swapping out heavy code for leaner, native solutions. Dont be too hard on yourself if you find your not up to date. MDN is not something you read casually just to learn about elements so it can be easy for this stuff to get away from us.
Just some of the HTML elements as listed on MDN.
Auditing my codebase
When looking through the codebase I manage I found many examples of JS solutions to problems that could be solved with HTML. Here is a classic example. The code in below is a simplified version of the sidebar found on the Artist Pages of Discogs.
import { useState } from 'react'
import { useHistory, useLocation } from 'react-router-dom'
<!-- This would come from the API -->
const filterData = {
name: 'Releases',
subcategories: ['Albums', 'Singles & Eps', 'Compilations', 'Videos', 'Miscellaneous'],
}
export function SidebarNonHtmlFirst(): React.ReactElement {
const history = useHistory()
const location = useLocation()
const searchParams = new URLSearchParams(location.search)
const currentCategory = searchParams.get('category')
// The component's open/closed state is managed using JS (useState).
// This is an example of re-inventing a browser feature with unnecessary JavaScript.
const [isOpen, setIsOpen] = useState(currentCategory === filterData.name)
function handleCategoryClick() {
setIsOpen((prevIsOpen) => !prevIsOpen)
const newParams = new URLSearchParams()
newParams.set('category', filterData.name)
history.push({ search: newParams.toString() })
}
function handleSubcategoryClick(subcategoryName: string) {
const newParams = new URLSearchParams()
newParams.set('category', filterData.name)
newParams.set('subcategory', subcategoryName)
history.push({ search: newParams.toString() })
}
return (
<div>
<button onClick={handleCategoryClick}>
{filterData.name}
<span>{isOpen ? '▼' : '►'}</span>
</button>
{isOpen && (
<div>
{filterData.subcategories.map((sub) => (
<button key={sub} onClick={() => handleSubcategoryClick(sub)}>
{sub}
</button>
))}
</div>
)}
</div>
)
}
Note how I use Reacts State Hook to manage the opening and closing of the dropdowns in this component, this effectively turns this component into a client side component meaning we have to ship all of its code down to the browser as opposed to having it evaluated and rendered on the server.
Contrast that against the below HTML-First refactor.
import Link from 'next/link'
// or if not using next
import Link from 'react-router-dom'
// In a Server Component, url data like the selected sidebar filters are passed as a prop.
export function SidebarHtmlFirst({
urlParams,
}: {
urlParams: { [key: string]: string | undefined }
}): React.ReactElement {
// 1. Read the state directly from the URL parameters passed by the server.
const currentCategory = urlParams.category
const currentSubcategory = urlParams.subcategory
const isCategorySelected = currentCategory === filterData.name
// 2. We use the native <details> HTML element.
// It handles its own open/close state in the browser without any JavaScript.
// We use `open` to set its initial state based on the URL.
return (
<details open={isCategorySelected}>
{/* The <summary> is the clickable part */}
<summary>{filterData.name}</summary>
<div>
{filterData.subcategories.map((sub) => {
// 3. Instead of buttons with onClick handlers, we use Next.js's <Link>
// component. When clicked, <Link> prevents a full-page reload and
// performs a "soft navigation":
// - It updates the URL in the browser's address bar without a refresh.
// - This triggers a refetch of the discography data with the new filter.
const isActive = isCategorySelected && currentSubcategory === sub
return (
<Link
key={sub}
href={`?category=${filterData.name}&subcategory=${sub}`}
className={isActive ? 'active' : 'inactive'}
>
{sub}
</Link>
)
})}
</div>
</details>
)
}
Ramifications of this Refactor
This entire component is now only using either JS that can be evaluated on the server or native HTML. This keeps us in harmony with the principles of least power. Now I don't want to turn this blog post into a severely long tome so I am going to use the <details> & summary markdown elements below to obscure content that dives deeper into the implication of this refactor. If you want to dive deeper into each aspect of the wins this refactor delivers just click on the following headings.
Performance & Architecture
As a Server Component, this sidebar's logic is never sent to the browser. It contributes zero bytes of its own JavaScript to the client-side bundle, leading to faster page loads. Client component --> Server component Zero Client-Side Footprint
The component is rendered to fully-functional HTML on the server. It is interactive (both for toggling and filtering) the moment it appears on the screen, even before the React framework has finished loading ("hydrating") on the client. A client-side equivalent would appear as "dead" HTML until its JavaScript loads. Instant Interactivity & Resilience
The component's state is simply the URL itself, which is read on the server. There is no need for client-side state synchronization. The architecture is simpler and more robust. Simplified Data Flow
Usability & Accessibility
The element is natively understood by screen readers. They will announce it as a "disclosure" or "collapsible" element and its state ("expanded" or "collapsed") without us needing to add any ARIA attributes (aria-expanded, aria-controls, etc.). Out-of-the-Box Accessibility
The browser handles keyboard accessibility for free. Users can tab to the element and press Enter or Space to toggle it open and closed. Keyboard Navigation
If our React/JavaScript code fails to load or is disabled, the sidebar still works. Users can still expand and collapse the content. They won't be able to filter (since that relies on JS), but the core UI remains functional. Progressive Enhancement
Performance (Minor but Real Improvement)
We have removed state management (useState) from this component. The browser's highly optimized, native code is now handling the open/close state and the show/hide animation. This reduces the component's memory footprint and removes a potential source of re-renders. Less JavaScript
While the difference is negligible for a single component, in a large application, replacing many custom, stateful components with their equivalent native element can lead to a measurable performance boost and a smaller bundle size. Lighter Component
Developer Experience & Maintainability
The intent of the code is now much clearer. semantically means we are creating a disclosure widget. The previous version with a and a conditionally rendered required a developer to read the code and infer its purpose. Simpler Code
We no longer need to worry about the logic for toggling state (setIsOpen((prev) => !prev)). The browser guarantees it works correctly. Our only job is to sync the URL to its onToggle event. Less to Test and Debug
Now perhaps you have some questions (I know I did) about the real utility of this entire approach. Here are some FAQ's you can dive into if they align with your skepticism.
Question: That's great, but the native and elements are notoriously ugly and difficult to style consistently across browsers. The little triangle marker is especially painful. With a and , I have a clean slate and complete, predictable control over the CSS. Isn't this a huge step back for design and developer experience?
Answer: That's a very valid and historically accurate point. For years, styling these elements was a major pain. However, modern CSS has caught up. You can now easily hide the default marker (summary::-webkit-details-marker { display: none; }) and replace it with your own custom icon, like an SVG chevron. You can then use CSS transforms and transitions on your custom icon based on the [open] attribute of the tag. While it might require a few more lines of CSS than styling a div, it's entirely achievable today, and in return, we get all the semantic and accessibility benefits for free. It's a small price to pay for a more robust and native foundation. Question: Styling Native Elements
Question: I can make a Answer: You are absolutely right—a diligent developer can replicate this. The key difference is the concept of 'accessible by default'. With the Question: Replicating Accessibility
Question: It's much harder to animate the opening and closing of a element's content. With a , I can easily use CSS transitions on max-height or a library like Framer Motion to create smooth animations. The native element just snaps open and closed instantly, which can feel jarring.
Answer: This is another well-known trade-off. You're right, animating the disclosure is the hardest part of using the element. However, this is where the 'HTML-first' philosophy really shines with progressive enhancement. The baseline experience is an instant, functional, and accessible sidebar that works even if JavaScript fails. From there, we can enhance it. We can add a small amount of JavaScript that hijacks the onToggle event, prevents the default action, and then runs our own smooth animation using a modern animation library. The key is that the animation is an enhancement, not a core requirement for functionality. The -based approach is broken without JavaScript, whereas the -based approach is functional without it and can be made beautiful with it. Question: Animating Transitions
Question: Your component is written in React. The entire application is useless without JavaScript enabled. So what's the real benefit of using a 'no-JS' native element like inside a framework that is 100% dependent on JavaScript to even render? Answer: This would be absolutely true if we were building a traditional Client-Side Rendered (CSR) application. But this is where modern server-first frameworks like Next.js change the game completely. Our SidebarHtmlFirst component is a React Server Component. It runs on the server and delivers a fully-formed, interactive HTML element to the browser. This means: If the user's JavaScript is slow to download or fails entirely, the sidebar still works. They can click the summary, and the browser will natively show and hide the content. The core functionality is not dependent on React hydrating on the client. The JavaScript we add is for enhancement only—in this case, to sync the sidebars's state with the URL. The div/button/useState version, even when server-rendered, would arrive as static HTML. The onClick handler and the useState logic wouldn't work until the entire React framework loads and hydrates, leaving the user with a dead button. Our HTML-first version provides a functional UI from the very first paint. Question: React Dependencies
Question: Does changing from a button to a link affect accessibility? Answer: Changing from elements to (link) elements absolutely affects accessibility, but in this specific context, it is a positive and correct change that improves the experience. Using a link is the more semantically appropriate and accessible choice for this functionality. Here’s the breakdown of why: The Fundamental Difference: Action vs. Navigation This is one of the most important distinctions in web accessibility: = Action A button tells the user they are performing an action on the current page, such as submitting a form, toggling a menu, or filtering a list of items without changing the URL. A screen reader announces it as a "button," setting the expectation that the context will change, but the user's location will not. A link tells the user they are navigating to a new location. This new location can be an entirely different page or, as in your case, a new state of the current page that is represented by a unique URL. A screen reader announces it as a "link," setting the expectation that the user is moving to a new resource that can be bookmarked, opened in a new tab, and has a distinct address. Why a Link is Better for this Server Component In our server-first model, clicking a filter option navigates the user to a new URL (e.g., /?category=Releases&subcategory=Albums). The server then renders the content for that specific URL. This is a textbook case for using a link. The change provides several accessibility benefits: Correct Expectation Setting: A screen reader user will hear "Link, Albums." This correctly informs them that activating this control will take them to a new view or page state. If it were a button, the URL change would be unexpected. Standard Browser Features: All users, including those with disabilities, gain standard link functionality. They can now: Right-click and "Open in New Tab" to compare filter results. Bookmark a specific filter view. Copy the link address to share it with someone else. Preserves Keyboard Accessibility: Both links and buttons are natively focusable and can be activated with the keyboard (Enter key for both, Space key for buttons). You lose no keyboard functionality by making the switch. So while a that changes the URL can be made to work, it violates the principle of least surprise. By changing to a , you have aligned the element's semantic meaning with its actual function. This makes the interface more predictable, robust, and accessible for everyone. Question: Button vs. Link Accessibility
Taking it too far.
Now like anything you can go too far with HTML-First development. Be sure to be a well informed citizen and seek out content on when these patterns will not work. Here are a couple posts detailing adoption of this strategy and an eventual return to equilibrium.
The Dark Side of HTML-First Development
The good, bad and ugly of the "HTML first" manifest
Remember, it’s about balance you don’t want to swing the pendulum too far in the opposite direction. If you keep your user front of mind you will ensure the ship is righted. A good thing to remeber is your users don’t care what architectural pattern you are using they just want the site to be fast reliable accessible and work. So don’t apply html-first programming for html-first programmings sake write it for the users sake.
So we will move on now to Island architecture which you will see really is complimented by html-first programming. The key takeaways first though are - go and check the latest developments of html you might surprised what you can do there now for things that formerly required a framework solution.
Understanding Island Architecture
Island Architecture is a rendering pattern that divides your application into two distinct areas:
Oceans: Static, server-rendered HTML content
Islands: Interactive components that require client-side JavaScript
The key insight is simple but powerful: only load JavaScript on the client for the specific interactive components that need it. Everything else should be delivered as lightweight, server-rendered HTML. Here hopefully you can see how this perfectly fits into an HTML-First code style.
Think of it like this: your page is an ocean of static HTML with small islands of interactivity scattered throughout. Users only pay the JavaScript cost for the islands they can actually interact with. Taking IA to its fullest you will take part in an interesting architectural move that has been taking hold in front end engineering of late and is captured really well in another paper by Juho called: The rise of disappearing frameworks.
The TLDR is that frameworks built for IA (like Astro) allow you to treat frameworks as an optional tool rather than a mandatory tax. You could have a static top-of-page, and a heavily interactive React component "below the fold." True IA ensures that the React framework doesn't even download until that component scrolls into view.
How Islands Architecture Works in React
Given its popularity let’s first walk through how Islands Architecture can be implemented in a React application, particularly with React Server Components (RSC). One disappointing thing I learned while researching Island Architecture is, you can bridge the gap but you cant get full IA if using React. You probably cottoned on to this when I described full IA above. React in its most traditional setup must own the entirety of the DOM of the application. If you search in your react application you will find these lines of code:
const root = ReactDOMClient.hydrateRoot(container, component)
root.render(component)
What this code is doing in, and why it prevents you from truly implementing full IA is a concept called Hydration.
When you call hydrateRoot, React expects to take over the entire container (usually the whole
or a main ). It walks through every single DOM element to match it with the React components in your JavaScript bundle. This means that even if a part of your page is 100% static text, React still has to 'touch' it during the hydration process. You are forced to ship the React library and your component logic to the client just so React can confirm that the static 'Ocean' matches what was rendered on the server. You can’t easily tell traditional React to 'just ignore this 80% of the page'—it’s an all-or-nothing ownership of the DOM.Server Components vs Client Components
React Server Components introduce a fundamental split:
Server Components (the default):
Execute only on the server
Have no client-side JavaScript footprint
Can directly access databases and APIs
Cannot use hooks or handle interactivity
Perfect for static content, data fetching, and layout
Client Components (marked with 'use client'):
Execute on both server (for initial HTML) and client (for interactivity)
Include JavaScript in the client bundle
Can use hooks like useState, useEffect
Handle user interactions
These are your “islands”
The Component Boundary
The key to Islands Architecture is understanding the server/client boundary. When a Server Component encounters a Client Component in the tree:
Server: The server renders the Server Component to HTML
Server: When it hits a Client Component, it inserts a placeholder (empty div) and includes a script tag with hydration instructions
Client: The browser receives HTML with placeholders
Client: JavaScript loads and “hydrates” only those specific interactive components
This means most of your page can be lightweight HTML, with JavaScript only downloaded and executed for the components that truly need interactivity.
Practical Example: A Simple Page
Consider a typical page structure:
// Server Component (default)
export default function ProductPage() {
return (
<div>
<Header /> {/* Server Component - static HTML */}
<ProductDetails /> {/* Server Component - static HTML */}
<ReviewsList /> {/* Server Component - static HTML */}
<AddToCartButton /> {/* Client Component - interactive island */}
</div>
);
}
In this example, only the AddToCartButton needs client-side JavaScript. Everything else is pure HTML, dramatically reducing the initial bundle size.
An Island is Born
An island is simply any UI component that requires client-side JavaScript to be interactive. You define it with the 'use client' directive:
`
'use client'
import { useState } from 'react';
export function AddToCartButton() {
const [count, setCount] = useState(0);
return (
setCount(count + 1)}>
Add to Cart ({count})
);
}
`
Bridging the Gap: Optimizing React Client Components
Once you’ve identified your islands, React provides several tools to optimize how that JavaScript is delivered:
Lazy Loading with Suspense
Lazy loading allows you to defer loading the JavaScript for a Client Component until it’s actually rendered. Think of it as on-demand loading for your interactive islands:
import { lazy, Suspense } from 'react';
const AddToCartButton = lazy(() => import('./AddToCartButton'));
export default function ProductPage() {
return (
<div>
<ProductDetails />
<Suspense fallback={<div>Loading...</div>}>
<AddToCartButton />
</Suspense>
</div>
);
}
Advanced Code Splitting
Code splitting allows you to explicitly declare what code ships in which chunk. For example, on a complex page, code for a recommendation engine shouldn’t be included in chunks for above-the-fold content like profiles or tracklists. Tools like loadable-components provide fine-grained control over this splitting.
Selective Hydration
Selective hydration is perhaps the most sophisticated optimization. It allows React to prioritize which components hydrate first based on user interaction. For example, if analytics show users frequently click a specific button immediately after page load, you can prioritize that button’s hydration — or even hijack the hydration waterfall to prioritize whatever the user clicks first. This is particularly beneficial for users on slower connections.
The Next Evolution: Disappearing Frameworks
While React Server Components bring us closer to true Islands Architecture, frameworks like Astro take it to the next level with what’s called “disappearing frameworks.”
The Traditional Approach
Currently, even with Server Components, your entire application is typically wrapped in your framework. This means that even for pages with all static HTML, you’re still sending the framework library to the client. Users pay the cost of architectural decisions they don’t benefit from.
The Astro Approach
Astro flips this model. Instead of wrapping everything in a framework, Astro starts with pure HTML and only includes framework code for specific islands:
`
// This runs on the server only
import Counter from './Counter.jsx';
My Page
Welcome to my site
This is all static HTML
<!-- Only this component includes React -->
<Counter client:visible />
`
Notice the client:visible directive. Astro provides several options:
client:load - Load JavaScript immediately
client:idle - Load when browser is idle
client:visible - Load when component enters viewport
client:media - Load based on media query
This explicit control over hydration makes it much harder to accidentally ship unnecessary JavaScript. Astro acts like TypeScript for performance — it makes you write better code by design.
The Results
Because of this architecture, Astro consistently ranks at the top when websites are surveyed for Core Web Vitals scores. The numbers speak for themselves: less JavaScript means faster load times, better user experience, and improved SEO.
Practical Takeaways
Here are the key lessons for implementing these patterns in your own work:
1. Revisit What’s Available in HTML
Much has changed in the last couple of years, and more changes are coming. Before reaching for JavaScript solutions, check if modern HTML can solve your problem natively.
2. Audit for Quick Wins
Review your existing code to find opportunities to:
Shift JavaScript execution to the server
Replace framework solutions with HTML equivalents
Remove unused JavaScript from client bundles
3. Think in Islands
When building or decomposing a product, start by auditing for the server/client boundary. Ask yourself:
What actually needs to be interactive?
What can be static HTML?
Where are my islands?
4. Optimize Asset Caching
Island Architecture works best when combined with effective caching strategies. Leverage edge caching (like Cloudflare) in combination with modern frameworks that enable Server-Side Rendering (SSR) and Incremental Static Regeneration (ISR) to set optimal cache policies.
Conclusion
The web platform has matured significantly, and our development practices should evolve with it. Islands Architecture, combined with HTML-First development, offers a path forward that doesn’t sacrifice developer experience for user experience — or vice versa.
By defaulting to HTML and carefully selecting which components truly need client-side JavaScript, we can build applications that are faster, more accessible, and more maintainable. The goal isn’t to eliminate JavaScript or abandon frameworks, but to use them judiciously where they provide real value.
As frameworks continue to evolve — from React Server Components to Astro’s disappearing framework approach — the future of web development looks lighter, faster, and more user-focused. The key is understanding these patterns and applying them thoughtfully to your own projects.
Remember: every kilobyte of JavaScript you don’t ship is a win for your users.










Top comments (0)