# Make an Astro site

> Create a new Astro project with pnpm, give it the Viralistic structure, and run the dev server, checks and build on your own machine.

Source: https://viralistic.nl/help/astro/make-an-astro-site

This guide is for developers, or anyone working alongside Claude Code, who wants to start a new website the way Viralistic builds them. You go from an empty folder to an Astro project where every page is a markdown file, schemas are strict and one route renders everything. You also learn the four commands you will use every day.

## What you need

- **Node.js**, an even-numbered LTS release. Viralistic projects pin the exact version in a `.nvmrc` file (currently Node 24).
- **pnpm**, the package manager we use for every project. Install it with the instructions on pnpm.io.
- **Git** and a GitHub account, so the site can be deployed later.
- **A code editor**, for example VS Code with the official Astro extension.

## Create the project

1. Open a terminal in the folder where you keep your projects.
2. Run the Astro setup wizard:

```bash
pnpm create astro@latest
```

3. Give the project a folder name.
4. Choose the smallest starter the wizard offers (minimal or empty). You will replace most of it.
5. Say yes to installing dependencies and to creating a git repository.
6. Move into the new folder with `cd your-folder-name`.

> **Tip:** The wizard's questions change between Astro versions. If an option looks different from this guide, pick the option that gives you the least starter code.

## Add the Viralistic structure

A fresh Astro project has a `src/pages/index.astro` file with markup inside it. Viralistic sites never put content in routes. Instead, content lives in `src/content`, and a `type` field in each page decides which layout renders it. The fastest way to get there is to copy the foundation files from an existing Viralistic project. Here is what they add and why.

### One content route

Delete `src/pages/index.astro` and create `src/pages/[...page].astro`. This is the only route on the site. It turns every file in the `pages` collection into a URL:

```astro
---
import { getCollection } from "astro:content"

import LayoutRenderer from "@/components/layout-renderer.astro"

export async function getStaticPaths() {
  const pages = await getCollection("pages")

  return pages.map((page) => ({
    params: { page: page.id === "index" ? undefined : page.id },
    props: page,
  }))
}
---

<LayoutRenderer {...Astro.props} />
```

### Collections and schemas

`src/content.config.ts` defines two collections: `pages` (markdown files) and `globals` (one YAML file per language with the site name, navigation, call to action and footer). Each collection is validated by a schema in `src/schemas/`:

- `layouts/base.ts` holds the fields every page has: `title`, `description`, an optional `image` and an optional `seo` block.
- `layouts/<type>.ts` holds one schema per page type, always ending in `.strict()` so unknown fields fail the build.
- `page.ts` combines them into one union keyed on `type`.

### Layouts, blocks and UI

`src/components/layout-renderer.astro` reads the page's `type` and loads `src/layouts/<type>.astro`. Layouts only pick blocks (`src/components/blocks/hero-1.astro`, `features-1.astro` and so on) and pass data to them. Small building blocks such as buttons come from the Fulldev UI registry:

```bash
pnpm dlx shadcn@latest add @fulldev/button
```

### Your first content

Create a globals file named after the default locale in `astro.config.ts` (Viralistic's own site uses `src/content/globals/nl.yaml`), and a home page at `src/content/pages/index.md`:

```md
---
type: home
title: We build calm, fast websites
description: A short description for search engines and social previews.
eyebrow: Welcome
buttons:
  - label: Get in touch
    href: /contact
---

Body text in markdown goes here.
```

Every field must match the `home` schema. Add a field to the schema first, then to the page.

Site URL, name, favicon and languages go into `fulldevIntegration()` in `astro.config.ts`. That one integration also wires Tailwind, the sitemap, `robots.txt` and favicons, so you do not configure them anywhere else.

## Run the site

Add these scripts to `package.json` if they are not there yet:

```json
{
  "scripts": {
    "dev": "astro dev --host 127.0.0.1",
    "build": "astro build",
    "preview": "astro preview",
    "typecheck": "astro check",
    "check": "pnpm format:check && pnpm typecheck",
    "format:write": "prettier --write .",
    "format:check": "prettier --check ."
  }
}
```

Then use them:

| Command        | What it does                                                              |
| -------------- | ------------------------------------------------------------------------- |
| `pnpm dev`     | Starts a local server at `http://127.0.0.1:4321` that reloads as you edit |
| `pnpm check`   | Checks formatting with Prettier and types with `astro check`              |
| `pnpm build`   | Builds the static site into the `dist` folder                             |
| `pnpm preview` | Serves the `dist` folder locally, exactly as it will go live              |

> **Note:** If `pnpm build` fails with a message about an unexpected key, that is the strict schema doing its job. A page has a field its layout does not know. Fix the field or add it to the schema.

## Commit your work

1. Run `pnpm format:write` so the code follows the shared Prettier settings.
2. Run `pnpm check` and `pnpm build`. Both must pass.
3. Commit with a conventional prefix, for example `chore: initialize Astro project foundation`.
4. Create an empty repository on GitHub and push to it.

## Next steps

- [How a Viralistic Astro site is organised](https://viralistic.nl/help/astro/project-structure)
- [Edit your site with Claude Code](https://viralistic.nl/help/astro/edit-your-site-with-claude-code)
- [Deploy to Netlify](https://viralistic.nl/help/astro/deploy-to-netlify)
