Project Structure

Purpose and Scope

An Astro project is organized around a small set of predictable root files and two important content areas: source files that Astro processes, and public assets that Astro serves as-is. The official project-structure docs describe the expected root shape as src/, public/, package.json, and recommended astro.config.mjs and tsconfig.json. This page explains that structure from a developer workflow perspective: how a new project gets its directory name, where pages and components live, how starter-style projects are opened for development, and how the repository’s package build conventions reinforce the same separation between source and output.

The important mental model is that src/ is the authored application. Pages under src/pages/ define routes, components under src/components/ hold reusable UI, layouts centralize document structure, styles and images can be processed by Astro, and content files such as Markdown can participate in the build. public/ is different: it is for non-code assets such as icons, fonts, PDFs, and robots files that should not be transformed by Astro. The project root then ties the site together through package metadata, configuration, TypeScript settings, and the package-manager install state.

Sources: packages/create-astro/src/actions/project-name.ts, .devcontainer/basics/devcontainer.json, configs/tsconfig.build.json

Core Project Shape

The common Astro tree starts with src/pages/index.astro, because files in src/pages/ become routes. A starter can grow from there by adding src/components/Header.astro, src/layouts/PostLayout.astro, src/styles/global.css, and content such as Markdown posts. Blog-style sites commonly add nested route folders, for example src/pages/posts/[post].astro, plus content files under a blog or content directory. Basic sites may only need one or two pages and a shared component. Minimal sites can begin with just one page, a package manifest, and the recommended config files.

Astro components are the basic authoring unit for .astro files. They can be complete pages when placed in src/pages/, or smaller reusable pieces when placed elsewhere in src/. The official component docs emphasize that Astro components render to HTML at build time or on demand and do not add client JavaScript by default. That behavior is why project structure matters: files placed in the source tree are not merely copied, they are interpreted by Astro’s compiler and build pipeline according to their role.

A useful way to compare starter sizes is by asking which directories are already present. A minimal starter teaches the contract by keeping the tree small. A basic starter adds more recognizable application structure and is a good place to inspect the page/component boundary. A blog-style starter adds content, layouts, and dynamic routing, showing how pages can be generated from content instead of hand-written one route at a time. The same root conventions remain stable as the project becomes more complex.

Sources: .devcontainer/basics/devcontainer.json

From Scaffolded Directory to Project Identity

The create-astro scaffolding flow treats the project directory as the first structural decision. Its projectName() action checks whether the current working directory is empty, prompts for a target when needed, generates a fallback project name for yes-mode runs, and converts the selected path into a valid package-style project name. If the user chooses the current directory, a relative path, or a parent-relative path, the action derives the name from the final path segment. This is the bridge between a human-friendly folder choice and a valid project identity used by the generated application.

That behavior also explains why new Astro projects generally begin in their own empty directory. The CLI avoids writing starter files into a non-empty folder without user confirmation, because project structure is only predictable when the scaffold controls the initial layout. The generated directory becomes the project root: package.json lives there, src/ and public/ are created relative to it, and later commands such as dev, build, preview, and sync are expected to run from that root. In other words, the folder you choose in the wizard becomes the boundary of the Astro application.

Astro’s telemetry project-info module reinforces that the project root is operationally meaningful. It tries to create an anonymous project identifier from Git repository history when available, and otherwise falls back to hashing the working directory path with safeguards for CI-like paths. It also records the detected package manager and version. For structure documentation, the key point is not telemetry itself, but that Astro distinguishes a project by its repository or working directory and package-manager context, which are all properties of the project root.

Sources: packages/create-astro/src/actions/project-name.ts, packages/telemetry/src/project-info.ts

Starter Development Flow

The repository includes a development-container configuration for the basics example that shows how a starter-style project is meant to be opened and exercised. The container sets its workspace folder to /workspaces/astro/examples/basics, forwards port 4321, installs dependencies, builds the repository from the monorepo root, and then starts the example with pnpm start --host. It also opens src/pages/index.astro in Codespaces, which is a practical signal that the first page is the natural entry point for learning a basic Astro project.

{
  "workspaceFolder": "/workspaces/astro/examples/basics",
  "forwardPorts": [4321],
  "postCreateCommand": "pnpm install && cd /workspaces/astro && pnpm run build",
  "postAttachCommand": {
    "Server": "pnpm start --host"
  }
}

For local work outside Codespaces, the same flow applies conceptually. Install dependencies at the project root, open the starter’s src/pages/index.astro, run the development server, and follow route files as they map to URLs. As the site grows, add components when markup repeats, layouts when document structure repeats, and content collections or Markdown when pages are driven by structured content. Keeping that division clear prevents the project from turning into a flat collection of pages and makes later migration to SSR, integrations, or adapter deployment easier.

Sources: .devcontainer/basics/devcontainer.json

Build and Package Layout Signals

The repository’s TypeScript build configuration shows the same source-to-output convention used by Astro projects and packages. The shared build config sets rootDir to a package’s src directory and outDir to dist, with TypeScript build information stored under dist/._cache/ts_build/build.tsbuildinfo. That is a package-building concern rather than a website-routing rule, but it is still a useful structural signal: authored source belongs in a stable source directory, generated artifacts belong in output directories, and caches should not be treated as source.

The astro-prism package extends the shared build configuration and includes ./src plus ./virtual.d.ts. This shows how package-level projects can refine the default source set while preserving the same build contract. In an Astro app, you normally interact with this idea through src/, public/, and build output rather than package internals, but the convention is consistent across the repository: source is explicitly included, outputs are generated, and type surfaces can be added when a project needs them.

A changeset for the Cloudflare adapter adds an operational reason to respect the distinction between source, rendering, and build output. It records a fix where prerender errors in workerd could be silently swallowed, allowing astro build to exit successfully while emitting truncated HTML. The fix buffers the response body so streaming errors surface as build failures. For project authors, the lesson is that build output is a result of rendering source routes; if a route throws, the build should fail rather than leave partial generated files that appear structurally valid but are incomplete.

Sources: configs/tsconfig.build.json, packages/astro-prism/tsconfig.build.json, .changeset/sharp-bags-build.md

Relevant Source Files

  • packages/create-astro/src/actions/project-name.ts — Implements the scaffolding step that chooses the project directory, validates emptiness, handles yes-mode naming, and derives a valid project name from the selected path.
  • packages/telemetry/src/project-info.ts — Defines how Astro identifies a project from Git history or the current working directory and records package-manager information, which reinforces the project-root boundary.
  • .changeset/sharp-bags-build.md — Documents a build behavior fix where rendering errors must surface during astro build instead of producing truncated HTML.
  • .devcontainer/basics/devcontainer.json — Shows a concrete basics starter development environment, including the workspace folder, forwarded dev-server port, setup command, start command, and default opened page.
  • configs/tsconfig.build.json — Establishes repository package build conventions for src input, dist output, and build-info cache placement.
  • packages/astro-prism/tsconfig.build.json — Demonstrates a package extending the shared build config while including its own src directory and virtual type declaration.

Practical Next Steps

When you open or create an Astro project, first identify the root: it is the folder with package.json, the recommended Astro and TypeScript config files, and the src/ and public/ directories. Next, inspect src/pages/ to understand the site’s routes, then inspect src/components/ and src/layouts/ to understand shared UI and document structure. If you are working from a starter, begin with the page opened by the development environment, usually src/pages/index.astro, and follow the imports outward.

As you add features, choose locations by behavior. Put route-producing files in src/pages/, reusable Astro or framework UI in src/components/, repeated wrappers in src/layouts/, processed styles and images inside src/, and untouched assets in public/. Keep generated output and caches out of source control unless a tool explicitly requires otherwise. For related reading, continue with pages on routing, layouts, Astro components, content collections, and the create-astro CLI so that each structural convention is tied to its runtime behavior.