Skip to content
Astro as getting-started 4 min read

Your First Astro Project

The fastest way to understand Astro is to build something with it. In this quick start you will scaffold a new project, look at how its files are organized, edit the home page, and run the development server to watch your changes hot-reload in the browser. By the end you will have a working page rendered as zero-JavaScript HTML and a clear mental model of where everything lives.

Scaffold a new project

Astro ships a create command that sets up a project, installs dependencies, and optionally initializes git. Run it with your package manager of choice:

npm create astro@latest my-first-astro

The wizard asks a few questions. Choose the “Empty” template for the smallest possible starting point, pick TypeScript: Strict for the best authoring experience, and let it install dependencies. Then move into the folder and start the dev server:

cd my-first-astro
npm run dev

Output:

 astro  v5.0.0 ready in 312 ms

┃ Local    http://localhost:4321/
┃ Network  use --host to expose

watching for file changes...

Open http://localhost:4321/ and you will see your first page. The server stays running and rebuilds instantly as you edit files.

Tip: The default dev port is 4321 (the digits 4-3-2-1). If it is taken, Astro automatically picks the next free port and prints the new URL.

Understand the project layout

An empty Astro project is intentionally small. The pieces you will touch most often are:

PathPurpose
src/pages/File-based routes. Each .astro, .md, or .mdx file becomes a URL.
src/components/Reusable .astro (or framework) components.
src/layouts/Shared page shells — <html>, <head>, navigation.
public/Static assets served as-is (favicons, images, robots.txt).
astro.config.mjsProject configuration and integrations.

The router is file-based: src/pages/index.astro maps to /, and src/pages/about.astro maps to /about. There is no route table to maintain.

Edit the home page

Open src/pages/index.astro. Replace its contents with the following. Notice the two parts of every Astro component: the component script between the --- fences (which runs only on the server) and the HTML template below it.

---
// src/pages/index.astro
const siteName = "My First Astro Site";
const features = ["Zero JS by default", "File-based routing", "Instant HMR"];
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{siteName}</title>
  </head>
  <body>
    <main>
      <h1>{siteName}</h1>
      <p>You shipped a page rendered as plain HTML.</p>
      <ul>
        {features.map((feature) => <li>{feature}</li>)}
      </ul>
    </main>

    <style>
      main { max-width: 40rem; margin: 4rem auto; font-family: system-ui; }
      h1 { color: #6d28d9; }
    </style>
  </body>
</html>

Save the file. The browser updates immediately — no full reload, no flicker. The siteName and features values were computed on the server, so the page you receive is already finished HTML with no client-side JavaScript.

Add a reusable component

Real sites are built from components. Create src/components/Card.astro and accept props through Astro.props:

---
// src/components/Card.astro
interface Props {
  title: string;
  href: string;
}
const { title, href } = Astro.props;
---
<a class="card" href={href}>
  <h2>{title} &rarr;</h2>
  <slot />
</a>

<style>
  .card { display: block; padding: 1rem; border: 1px solid #e5e7eb; border-radius: 8px; text-decoration: none; color: inherit; }
  .card:hover { border-color: #6d28d9; }
</style>

The <slot /> renders whatever children you pass between the component’s tags. Import and use it back in index.astro:

---
import Card from "../components/Card.astro";
---
<Card title="Read the docs" href="https://docs.astro.build">
  <p>Everything you need to go further.</p>
</Card>

Astro components are HTML-first, so they compose just like ordinary markup — and they still ship no JavaScript.

Sprinkle in interactivity (optional)

When a piece of the page genuinely needs to run in the browser, you opt in with a client:* directive on a framework component. First add an integration:

npx astro add react

Then create a small island and hydrate only it:

---
// src/pages/index.astro
import Counter from "../components/Counter.jsx";
---
<!-- Hydrates only when scrolled into view -->
<Counter client:visible />

Everything else on the page remains static HTML. Only Counter downloads JavaScript, and only when the visitor reaches it.

Note: Component-script imports run at build/request time on the server. If you need browser-only APIs like window or localStorage, put that code inside a framework island, not in the .astro script.

Build for production

When you are ready to ship, build the optimized output and preview it locally before deploying:

npm run build
npm run preview

Output:

building static entrypoints...
✓ Completed in 1.21s.

▶ src/pages/index.astro
  └─ /index.html (+18ms)

Complete!

The static files land in dist/, ready to upload to any static host or CDN.

Best Practices

  • Keep static content in .astro components; reach for a framework island only when client-side state is truly required.
  • Pass data through typed Props interfaces so your components are self-documenting and autocomplete-friendly.
  • Use scoped <style> blocks instead of global CSS to avoid cascade conflicts between components.
  • Prefer client:visible or client:idle over client:load for non-critical widgets to keep the main thread free.
  • Run npm run build && npm run preview before deploying to catch issues the dev server hides.
  • Commit early — create astro can initialize git for you, giving you a clean baseline.
Last updated June 14, 2026
Was this helpful?