Your First Page
In React, a “page” is a component — a JavaScript function that returns JSX. In Astro, a “page” is a .astro file — an HTML document with an optional JavaScript/TypeScript frontmatter block at the top delimited by ---.
The two-zone anatomy of a .astro file:
--- ← fence open// FRONTMATTER: runs on the server / at build time// fetch data, compute values, import componentsconst title = "My Page";--- ← fence close<!-- TEMPLATE: HTML + {expressions} --><html lang="en"> <head><title>{title}</title></head> <body><h1>{title}</h1></body></html>Everything above the second --- is Node.js code. Everything below is an HTML template that can embed expressions with {expr} — like JSX but rendered once to a string, never re-executed in the browser.
React page vs Astro page
Section titled “React page vs Astro page”// src/app/page.tsx (Next.js App Router)interface PageProps { params: { slug: string };}
export default function BlogPost({ params }: PageProps) { // Runs on server AND can re-run on client const publishedAt = new Date().toLocaleDateString();
return ( <article> <h1>My First Post</h1> <time>{publishedAt}</time> <p>Welcome to my blog built with React.</p> </article> );}---// src/pages/blog/first-post.astro// Runs ONLY on the server / at build timeconst title = "My First Post";const publishedAt = new Date().toLocaleDateString();---<html lang="en"> <head> <meta charset="utf-8" /> <title>{title}</title> </head> <body> <article> <h1>{title}</h1> <time>{publishedAt}</time> <p>Welcome to my blog built with Astro.</p> </article> </body></html>Two things stand out immediately:
- The Astro page is a complete HTML document — it includes
<html>,<head>, and<body>. React components return fragments; layout wrappers add the shell. In Astro, pages are responsible for the full document (or they delegate to a Layout component that provides the shell). new Date()in the frontmatter runs once at build time — the resulting string is baked into the HTML. In Next.js App Router, it also runs on the server, but the distinction matters: in Astro there is no React runtime, no hydration, and no possibility of the date recomputing in the browser.
Adding styles
Section titled “Adding styles”Styles in an Astro page can be inline <style> tags in the template — Astro automatically scopes them to the component, just like CSS Modules, with zero configuration:
---const color = "#6366f1";---<h1>Hello</h1><style> /* This style is scoped to this component automatically */ h1 { color: #6366f1; }</style>Global styles go in a separate CSS file and are imported in the frontmatter:
---import '../styles/global.css';---Runnable example
Section titled “Runnable example”---
const title = "My First Astro Page";
const author = "A React Developer";
const publishedAt = "2026-06-23";
const tags = ["astro", "web", "frontend"];
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 680px;
margin: 0 auto;
padding: 2rem 1.5rem;
color: #1e293b;
}
.meta { color: #64748b; font-size: 0.875rem; margin-bottom: 1.5rem; }
.tags { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1.5rem; }
.tag {
background: #ede9fe;
color: #5b21b6;
padding: 0.2rem 0.7rem;
border-radius: 999px;
font-size: 0.75rem;
}
</style>
</head>
<body>
<article>
<h1>{title}</h1>
<p class="meta">By {author} · {publishedAt}</p>
<p>
This page was built with Astro. The frontmatter ran at build time —
no JavaScript was shipped to the browser to render this content.
</p>
<p>
The tags below are rendered with a <code>.map()</code> in the template.
In React that would hydrate a component. Here it is pure HTML.
</p>
<div class="tags">
{tags.map(tag => <span class="tag">#{tag}</span>)}
</div>
</article>
</body>
</html>Open the browser DevTools Network tab — there is no JavaScript bundle. The .map() over tags produced three <span> elements at build time and disappeared.