How Astro keeps a marketing site fast
Most marketing sites ship an entire framework to render text that never changes. Here is what Astro does instead, and the handful of settings that still matter once you are past the defaults.
A marketing page is mostly text, a few images, and two or three things that actually need to react to a click. The usual stack sends a runtime, a router, and a hydration pass for all of it, then spends the next six months trying to claw the milliseconds back.
Astro starts from the opposite default. Every component renders to HTML at build time and ships zero JavaScript unless you ask for some. You are not optimizing your way down to a fast page. You are deciding, one component at a time, what deserves to be interactive.
Components render on the server and stop there
An .astro component looks like a page and behaves like a function. The
frontmatter runs once, at build time, and the template becomes plain markup.
There is no client bundle for this file, because there is nothing left to run.
---
const posts = await fetch("https://api.example.com/posts/").then((res) =>
res.json(),
);
---
<ul class="post-list">
{posts.map((post) => (
<li>
<a href={post.href}>{post.title}</a>
</li>
))}
</ul>
That await at the top is doing real work during the build, not in a visitor’s
browser. By the time the page is requested, the list is already HTML sitting in
a file.
Islands are opt in, one directive at a time
When a component genuinely needs state, you mark it with a client:* directive.
Only that component and its dependencies reach the browser. Everything around it
stays static.
---
import PriceTable from "@/components/PriceTable.astro";
import Newsletter from "@/components/Newsletter.tsx";
---
{/* Rendered to HTML at build time. Ships nothing. */}
<PriceTable plans={plans} />
{/* Hydrates only once it scrolls into view. */}
<Newsletter client:visible />
The directive is also the scheduling decision:
client:loadhydrates immediately. Reserve it for something above the fold that breaks without JavaScript.client:visiblewaits for the component to scroll into view. This is the right answer far more often than people expect.client:idlewaits for the main thread to go quiet, which keeps it out of the way of first paint.client:mediahydrates only when a media query matches, so a desktop-only widget costs a phone nothing.
The fastest component is the one that never hydrates. The second fastest is the one that hydrates after the visitor has already seen the page.
Four settings worth turning on
The defaults are good, but a marketing site benefits from a few explicit choices. Prefetching is the one people notice: links are fetched before the click lands, so navigation feels instant without a client-side router.
import { defineConfig } from "astro/config";
import sitemap from "@astrojs/sitemap";
export default defineConfig({
site: "https://example.com/",
compressHTML: true,
trailingSlash: "always",
prefetch: {
prefetchAll: true,
defaultStrategy: "viewport",
},
integrations: [sitemap()],
});
Images are part of the build, not an afterthought
The image pipeline converts, resizes, and generates a srcset during the build. The only thing it needs from you is the display size, because that is the one thing it cannot infer from the file.
---
import { Image } from "astro:assets";
import hero from "@/assets/hero.jpg";
---
<Image
src={hero}
alt="Product dashboard"
widths={[640, 1024, 1536]}
sizes="(min-width: 64rem) 1024px, 100vw"
loading="eager"
fetchpriority="high"
/>
Skip widths and sizes and the browser cheerfully downloads a 2400 pixel
original to paint it at 380. It is the single most common reason an otherwise
clean Astro site scores badly.
What the build actually produces
The useful part is that you can check. If a page has no islands, there is no JavaScript directory to inspect, and the absence is the whole point.
$ npm run build
09:41:02 [build] 14 page(s) built in 1.83s
09:41:02 [build] Complete!
$ ls dist/_astro/*.js
ls: cannot access 'dist/_astro/*.js': No such file or directory
Where the effort goes instead
None of this removes the work of building a good site. It moves it. You spend the time on layout, copy, and the two or three interactions that earn their bytes, rather than on auditing a bundle you did not choose to ship. That is a much better trade, and it holds up as the site grows.