Astro Fundamentals Questions
Astro is a web framework built around a simple but powerful idea: ship HTML, not JavaScript. Interviewers probing your Astro fundamentals want to know whether you understand the component model, how islands selectively rehydrate interactive code, and why the zero-JS-by-default philosophy produces fast sites. This page collects the questions that come up most often, with precise, modern (Astro 4/5) answers and runnable code.
What is Astro and what problem does it solve?
Astro is a content-driven, multi-page web framework that renders components to HTML on the server (or at build time) and sends zero JavaScript to the browser by default. The problem it solves is the over-shipping of client-side JS that plagues SPA frameworks: a marketing page or blog rarely needs a hydrated React tree, yet many stacks ship one anyway. Astro lets you author with components and even mix UI frameworks, while the output is mostly static HTML.
A typical interview framing: “Why pick Astro over Next.js for a docs site?” The answer is performance and simplicity — Astro defaults to no client runtime, so pages are lighter and faster to interactive, and you only opt into hydration where you actually need interactivity.
What does an Astro component look like?
An .astro file has two parts separated by a code fence (---), called the component script (runs on the server) and the component template (HTML-like markup). The script is plain TypeScript/JavaScript that runs at build or request time, never in the browser.
---
// Component script — runs on the server only
const { name } = Astro.props;
const items = ["Astro", "Islands", "Zero JS"];
---
<section>
<h1>Hello, {name}</h1>
<ul>
{items.map((item) => <li>{item}</li>)}
</ul>
</section>
<style>
/* Scoped to this component by default */
h1 { color: rebeccapurple; }
</style>
Note that the JSX-like expressions, the Astro.props global, and scoped <style> all resolve on the server. No part of this component ships JavaScript to the client.
What is the islands architecture?
An island is an isolated, interactive UI component on an otherwise static HTML page. Instead of hydrating the whole page, Astro hydrates only the specific components you mark as interactive. Everything else stays as static HTML. This is the core mental model interviewers look for.
You create an island by adding a client:* directive to a framework component (React, Vue, Svelte, Solid, etc.):
---
import Counter from "../components/Counter.jsx";
import StaticHeader from "../components/StaticHeader.astro";
---
<StaticHeader /> <!-- pure HTML, no JS -->
<Counter client:load /> <!-- island: ships + hydrates JS -->
The StaticHeader renders to HTML with no runtime. Counter becomes an island: its JavaScript is bundled and hydrated according to the directive.
Tip:
.astrocomponents are server-only and cannot have aclient:*directive. Only framework components (.jsx,.vue,.svelte, etc.) become islands.
What client directives exist and when do they hydrate?
The directive controls when the island’s JavaScript loads and hydrates. Choosing the right one is a common follow-up question because it directly affects performance.
| Directive | Hydrates when | Best for |
|---|---|---|
client:load | Immediately on page load | Critical, above-the-fold interactivity |
client:idle | After the main thread is idle (requestIdleCallback) | Low-priority widgets |
client:visible | When the element scrolls into view | Below-the-fold components |
client:media={query} | When a CSS media query matches | Mobile-only or desktop-only UI |
client:only="react" | Skips SSR; renders only on client | Components that can’t render on the server |
---
import HeavyChart from "../components/HeavyChart.jsx";
---
<HeavyChart client:visible />
Here the chart’s JS is fetched and hydrated only once the user scrolls to it, keeping initial load lean.
How does zero-JS-by-default actually work?
When Astro renders a page, every component executes on the server and produces an HTML string. Unless a component carries a client:* directive, its JavaScript is discarded after rendering — none of it reaches the browser. This means a page built entirely from .astro components and undirected framework components ships an empty client bundle.
You can confirm this with the build output, which reports per-route JS:
npm run build
Output:
12:01:03 [build] Complete!
├─ /index.html (0 kB JS)
└─ /blog/post.html (0 kB JS)
A 0 kB JS route is the goal for static content. Each island you add increases that number, which is why directives matter.
Can you mix multiple UI frameworks in one project?
Yes — this is a signature Astro feature and a frequent question. Through integrations you can render React, Vue, Svelte, and Solid components side by side, each becoming its own island.
// astro.config.mjs
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
import svelte from "@astrojs/svelte";
export default defineConfig({
integrations: [react(), svelte()],
});
---
import ReactWidget from "../components/Widget.jsx";
import SvelteForm from "../components/Form.svelte";
---
<ReactWidget client:idle />
<SvelteForm client:visible />
Each island carries only its own framework runtime, and only on the routes where it appears.
How do you pass data into components?
Server-rendered .astro components receive data via Astro.props, and you can also fetch data directly in the component script since it runs on the server. Framework islands receive props the same way, but those props must be serializable because they cross the server-to-client boundary.
---
const res = await fetch("https://api.example.com/posts");
const posts = await res.json();
---
<ul>
{posts.map((p) => <li>{p.title}</li>)}
</ul>
Warning: Functions and class instances cannot be passed as props to a hydrated island — only JSON-serializable values survive the network boundary. Pass plain data and reconstruct behavior on the client.
Best Practices
- Default to
.astrocomponents and static HTML; reach for a framework island only where interactivity is genuinely required. - Prefer
client:visibleorclient:idleoverclient:loadfor non-critical widgets to minimize main-thread work. - Keep islands small and focused so each bundle stays lightweight — avoid hydrating a whole page tree.
- Pass only serializable props to islands; do server-side data fetching in the component script.
- Use scoped
<style>blocks per component instead of global stylesheets to avoid leakage. - Watch the
npm run buildper-route JS report and treat growing bundle sizes as a signal to re-examine your directives.