For most of the last decade, the default answer to "how do we build a website" has quietly become "build a JavaScript application." Even for a blog, a marketing page, or a documentation site, content that's fundamentally static and read-only, teams reach for a single page application framework, ship a full client side bundle, and then spend the rest of the project clawing back the performance that decision cost them.
Astro's own positioning gets at this directly: teams too often accept the complexity of SPAs by default, even when the actual business need never justified it in the first place. Astro is built around a different premise. Most websites are content-focused, not application-focused, and content doesn't need to be re-built inside a JavaScript runtime every time someone loads a page. So what if the framework shipped zero JavaScript by default, and only added it back in, deliberately, wherever a page genuinely needed interactivity?
That question is the whole architectural core of Astro, and it's worth walking through exactly how it pulls it off, because the mechanism is more interesting than the tagline suggests.
Why This Even Matters: Web Vitals
Before getting into the how, it's worth being clear on why this tradeoff is worth caring about at all. Google's Core Web Vitals exist specifically to measure real user experience on the web, things like how fast a page becomes visually complete and how quickly it responds to input. These metrics aren't just an engineering vanity number. They directly influence search ranking, and they correlate with real business outcomes: happier users, better brand perception, people staying on a page longer, and higher conversion rates. A framework's JavaScript footprint isn't an abstract architectural concern, it's something that shows up in whether your page ranks and whether visitors stick around.
It's also worth noting that this isn't just theoretical positioning. Astro is used by over 100,000 developers and teams worldwide at this point, and while it's smaller by download numbers than giants like Next.js, it's carved out a real and growing niche specifically among teams for whom this tradeoff matters.
The Core Idea: Islands, Not Applications
Astro describes its approach as an islands architecture combined with progressive, partial hydration, and unpacking that phrase is the fastest way to understand the whole framework.
To back up for a second: hydration, in the traditional sense, is what happens when a server sends down static HTML and the browser then has to download and execute a matching JavaScript bundle to make that HTML interactive again, attaching event listeners and restoring state so it behaves like a live application. In a typical framework, that hydration step happens for the entire page at once, whether or not most of the page actually needs it.
Astro's islands model breaks that assumption apart. Instead of one monolithic application that gets hydrated all at once, an Astro page is built from mostly static HTML with individual interactive components embedded inside it, islands of interactivity sitting in an ocean of plain, static content. A newsletter signup form or a comment widget gets hydrated. The article text, the header, the footer, the vast majority of the page, doesn't need to be, and simply isn't.
This is where "progressive" and "partial" both earn their place in the description. Partial, because entire regions of a page can skip hydration altogether if they're purely static. Progressive, because even the components that do need JavaScript don't all have to load it at the same moment. Astro exposes this directly through client directives, small attributes you attach to a component to control exactly when its JavaScript should load:
- client:load hydrates a component immediately, as soon as the page loads.
- client:idle waits until the browser has spare resources, falling back to immediate hydration if the browser doesn't support that kind of scheduling.
- client:visible waits until the component actually scrolls into view.
- client:media hydrates conditionally based on a CSS media query, useful for things like a mobile-only navigation menu.
- client:only skips server rendering entirely and renders purely on the client, still following the same timing as client:load.
The practical effect is a page where JavaScript loading is deliberate and granular rather than all-or-nothing. A component below the fold that most visitors never scroll to simply never costs anyone a byte of JavaScript unless they actually reach it.
Components That Compile Away
Astro's component model backs this philosophy up structurally. An .astro file has a syntax that will look immediately familiar to anyone who's touched HTML or JSX, but with one key structural feature: a frontmatter section at the top, delimited by triple dashes, where you write plain JavaScript or TypeScript that runs exclusively on the server or at build time. Imports, data fetching, variable definitions, all of it happens there. Below the frontmatter sits the template, the actual markup the component renders.
Critically, none of that frontmatter code ships to the browser. It runs once, during the build (or on each request, if you're using server rendering), and its only job is to produce static HTML. Components can also accept props, exposed inside the frontmatter through Astro.props, with an optional TypeScript interface for autocomplete and type safety, which makes Astro components compose cleanly the way you'd expect from any modern component-based framework, just without a JavaScript runtime attached by default.
Styling follows the same locally-scoped philosophy. A <style> tag inside an .astro file is scoped automatically to that component alone, so your CSS can't accidentally leak out and clash with styles elsewhere on the page, though you can opt out of scoping explicitly with is:global when you genuinely need to.
Perhaps the most distinctive part of Astro's component model, though, is that it isn't limited to Astro's own syntax at all. You can drop in components from React, Vue, or Svelte directly inside an Astro page, install support with a single setup command like npx astro add react, and use all of that framework's usual features, hooks included. This "bring your own framework" approach means a team doesn't need to abandon existing component libraries or a team's existing framework expertise just to adopt Astro. It sits above those frameworks as a meta layer, using each one only where you've explicitly asked it to.
Static, Server Rendered, or Both at Once
Astro didn't start out flexible on rendering strategy. Before version 2.0, a project had to commit upfront to one of two output modes: static, which discards server side JavaScript entirely and produces a fully pre-built site, or server, which hands rendering off to a server adapter at request time.
Astro 2.0 introduced hybrid rendering, which removes that all-or-nothing constraint. Developers can now decide, page by page, which routes get generated statically at build time and which get rendered on the server per request, and can flip the default in either direction: mostly server rendered with specific pages opted into static generation, or mostly static with specific pages opted into server rendering. A content-heavy blog might statically generate every article page, since the content rarely changes, while rendering a dashboard or search page on the server, since that content is inherently dynamic. Enabling server rendering at all requires installing an adapter, which gives Astro the hooks it needs to output a script that actually runs correctly on your chosen hosting platform.
Content Isn't an Afterthough
Given how much of Astro's target audience is building content-driven sites, it's not surprising that Markdown support is treated as a first-class citizen rather than a bolt-on. Markdown files dropped into src/pages automatically become pages, complete with YAML frontmatter for metadata, no JavaScript required in the frontmatter itself, and a draft: true flag that's enough to keep a page out of the build entirely. Under the hood, Astro runs Markdown through remark, which brings a large plugin ecosystem, automatic GitHub Flavored Markdown support, and things like automatically generated heading IDs for internal linking, without any extra setup.
For content that needs more than plain Markdown can offer, Astro supports MDX, effectively Markdown combined with JSX. MDX lets you drop live components directly into your content, and even override the default rendering of standard Markdown elements, swapping in a custom component wherever a blockquote or a code block appears, for instance, without changing how you actually write the Markdown itself.
Once a project has more than a handful of Markdown files, Astro's Content Collections, introduced in version 2.0, become the more structured answer. Each folder inside src/content is its own collection, and each collection can define a schema that enforces consistent frontmatter across every entry in it. Get a field wrong or leave a required one out, and Astro flags the error immediately rather than letting a malformed entry slip silently into production. Combined with a getCollection() call inside getStaticPaths(), collections plug directly into Astro's dynamic routing system, letting a single [slug].astro file generate an entire set of article or documentation pages from structured content living outside the pages directory entirely.
Talking to a Backend, and Sharing State Between Islands
Astro components, and even framework components used inside Astro, all have access to a standard fetch function for pulling in external data. By default that fetch runs at build time, baking the fetched data directly into the static output, but switch on server rendering and the same call runs fresh on every request instead. Combined with top-level await, this makes data fetching feel almost boringly simple, you just await the data you need directly in the frontmatter and use it in the template below.
This is also exactly the situation where headless CMS platforms pair naturally with Astro. A traditional CMS bundles content management and a rendering frontend together into one product, easy to set up but limiting in flexibility, tied to a single delivery surface, and awkward when a team needs something the platform wasn't built to support. A headless CMS strips out the frontend entirely and exposes content purely through an API, trading away built-in WYSIWYG editing and a bit of setup convenience for far more flexibility in how and where that content actually gets rendered. Pairing that kind of CMS with Astro means content authors keep an editing workflow they're comfortable with, while developers keep full control over a fast, purpose built frontend that pulls that content in at build time or on request, exactly the combination a lot of modern content sites are reaching for.
One thing Astro's islands model doesn't solve automatically is state sharing between separate interactive components. Since each island is hydrated independently, and possibly built with an entirely different framework than the island next to it, there's no shared runtime holding them together by default. Nano Stores fills that specific gap: a genuinely tiny state management library, under 1kb with zero dependencies, that's explicitly framework agnostic, meaning a React island and a Svelte island on the same page can read and write the exact same shared store without any friction between them. A store is just an atom created in a plain JavaScript or TypeScript file, read and written directly with .get() and .set(), or subscribed to reactively inside a component using each framework's respective useStore hook so the component automatically re-renders when the shared value changes. It's worth noting explicitly that Nano Stores isn't really meant to be used from inside .astro files themselves, since those only ever run at build or request time and have no live client side re-rendering to trigger in the first place.
Getting Found, and Getting Live
None of this matters if a site can't actually be found or shipped. Astro treats both concerns as fairly light lifts. A small custom SEO component, converting a page's relevant properties into proper meta tags, covers most of what search engines and social previews need, and generating a sitemap, an XML file listing every page so search engines can crawl a site more efficiently, is close to a one-command affair via the official sitemap integration, with an optional filter function if certain routes shouldn't be listed.
Deployment follows the same low-friction philosophy. Astro documents detailed deployment paths for around twenty different hosting providers, and for a workflow built around continuous deployment, connecting an existing Git repository straight to a host like Netlify is typically a matter of installing the relevant adapter and configuring the output mode, nothing more exotic than that.
The Bigger Point
None of these individual pieces, islands, scoped components, hybrid rendering, Content Collections, Nano Stores, are radical on their own. What makes Astro distinctive is that all of them are organized around the same single premise: JavaScript should be something a page opts into deliberately, not something it inherits by default just because a framework needed a runtime to render anything at all. For content-heavy, largely static sites, exactly the kind of project a portfolio, a blog, or a documentation site tends to be, that premise turns out to matter quite a bit more in practice than it might sound like on paper.
