Skip to content

Project & .astro Files

A typical Astro project has a small, conventional structure:

my-site/
├── astro.config.mjs # config: integrations, adapter, output mode
├── src/
│ ├── pages/ # file-based routing — each file is a route
│ ├── components/ # reusable .astro (and framework) components
│ ├── layouts/ # shared page shells
│ ├── content.config.ts # content collections config (Astro 5+)
│ └── styles/
└── public/ # static assets served as-is (favicon, robots.txt)

Two folders are load-bearing:

  • src/pages/ is file-based routing: src/pages/index.astro/, src/pages/about.astro/about, src/pages/blog/[slug].astro/blog/:slug. The file’s location is the route.
  • public/ is copied to the output verbatim — files here are served at the root, unprocessed.

The .astro file is Astro’s own component format. It has two parts separated by a code fence (---):

---
// 1. The component script (frontmatter). Runs on the SERVER only.
// JavaScript/TypeScript here: imports, data fetching, props, logic.
import Card from '../components/Card.astro';
const title = "Latest posts";
const posts = await fetch('https://api.example.com/posts').then((r) => r.json());
---
<!-- 2. The template. Becomes HTML. Uses { } to embed JS expressions. -->
<h1>{title}</h1>
<ul>
{posts.map((post) => <Card title={post.title} />)}
</ul>

The key mental model:

  • Everything above the second --- is the component script — it runs once, on the server, at build time (or per request for on-demand pages). This is where you fetch data, import components, and compute values. None of it ships to the browser.
  • Everything below is the template — HTML with { } expressions that drop into JavaScript. This is what becomes the rendered HTML.
flowchart LR
  script["--- frontmatter ---
component script
(server only)"] --> compute["fetch data, compute props"]
  compute --> template["template (HTML + { } expressions)"]
  template --> html["rendered HTML"]
The two halves of a .astro file

Because the script runs on the server, you can await at the top level — fetch from an API or database directly in a component, with no useEffect, no loading state, no client round-trip. The data is already there when the HTML is built.

An .astro template is valid HTML plus JSX-like expressions. If you paste plain HTML into a .astro file, it just works. You add dynamism with { } (expressions), {condition && <div/>} (conditionals), and {items.map(...)} (loops) — the same expression syntax you saw in the landing example. Unlike JSX, you use standard HTML attribute names (class, not className).

How does routing work in Astro?
What runs in the frontmatter (the code above the second `---`) of a .astro file?
Why can you use top-level `await` in a .astro component script?
What is a .astro template, syntactically?