Skip to main content

Astro Starlight on GitHub Pages

A project recipe for a documentation site built with Astro Starlight, deployed to GitHub Pages via GitHub Actions, with considerations for private repositories. Use this as a blueprint when setting up internal docs, product documentation, or team knowledge bases where the source repository may be private.

Project Overview

This recipe targets documentation sites for internal teams, product libraries, or knowledge bases. The key characteristic is that the source repository may be private — proprietary product docs, internal runbooks, team onboarding guides — while the built site is deployed to GitHub Pages.

DecisionChoice
RenderingStatic site generation (SSG)
HostingGitHub Pages
LanguageTypeScript (config) / Markdown + MDX (content)
FrameworkAstro + Starlight
SearchPagefind (build-time, zero-config)
CI/CDGitHub Actions
Access controlGitHub repo visibility + GitHub plan tier

Tech Stack

Core

LibraryRole
AstroWeb framework (SSG)
TypeScriptType-safe configuration
ViteBuild tool (used by Astro internally)

Documentation

LibraryRole
Astro StarlightDocumentation framework
PagefindBuild-time full-text search
Expressive CodeSyntax highlighting and code blocks
tip

All core choices align with the Recommended Tech Stack. See individual pages for rationale and alternatives.

Architecture Overview

The architecture is straightforward. Authors write Markdown or MDX files, push to the repository, and GitHub Actions builds the site with Astro. The output is deployed as static HTML to GitHub Pages. There is no backend, no database, and no client-side JavaScript beyond what Starlight includes by default (search index, theme toggle).

Authors commit content changes to the repository. On every push to main, GitHub Actions checks out the code, runs the Astro build, and deploys the resulting static files to GitHub Pages. Readers access the site through a browser — no authentication is involved unless the GitHub plan supports private Pages visibility.

Content Structure

project directory
src/
├── content/
│ └── docs/
│ ├── getting-started.md
│ ├── guides/
│ │ ├── installation.md
│ │ └── configuration.md
│ └── reference/
│ └── api.md
├── assets/
│ └── logo.svg
└── content.config.ts
astro.config.mjs
package.json

Starlight generates the sidebar automatically from the filesystem. Ordering is controlled via the sidebar configuration in astro.config.mjs or frontmatter in each file. Content goes in src/content/docs/.

Key Features Out of the Box

  • Auto-generated sidebar from filesystem structure
  • Pagefind search — full-text, zero-config, runs entirely at build time
  • Expressive Code — file titles, line highlighting, diff markers, terminal frames
  • Built-in components<Aside>, <Card>, <CardGrid>, <Tabs>, <Steps>, <LinkCard>
  • Dark / light mode — included in default theme
  • i18n support — built-in if multi-language docs are needed later
tip

Pagefind search works entirely at build time — no external search service, no API keys, no data leaving your infrastructure. This makes Starlight particularly well-suited for internal or confidential documentation.

GitHub Pages & Private Repos

Plan Requirements

GitHub Pages is available for private repos on paid plans, but the access model varies by plan tier.

GitHub PlanPages on Private ReposSite Visibility
Free❌ Public repos onlyPublic
Pro (personal)Public
TeamPublic (cannot restrict)
Enterprise CloudPublic or private (configurable)
caution

On the Team plan, a GitHub Pages site built from a private repo is publicly accessible on the internet. The repo is private, but the deployed site is not. Only GitHub Enterprise Cloud offers a toggle to restrict Pages site visibility to organization members. If your documentation contains confidential information and you're on a Team plan, consider alternative hosting with access control — for example, Cloud Run with Identity-Aware Proxy or Cloudflare Access.

Repository Configuration

  1. Go to repo Settings → Pages
  2. Under Source, select GitHub Actions
  3. No branch selection needed — the workflow handles everything
note

Do not select "Deploy from a branch." The Astro build produces output that must be uploaded as an artifact. The GitHub Actions source is required for the workflow described below.

Deployment Overview

Astro Configuration

astro.config.mjs
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';

export default defineConfig({
site: 'https://your-org.github.io',
// Set base only for project pages (not org pages or custom domains)
// base: '/repo-name/',
integrations: [
starlight({
title: 'My Docs',
sidebar: [
{ label: 'Guides', autogenerate: { directory: 'guides' } },
{ label: 'Reference', autogenerate: { directory: 'reference' } },
],
}),
],
});

site is always required — Astro uses it to generate canonical URLs and sitemap entries. Set base only if the site is hosted at a subpath (e.g., https://org.github.io/repo-name/). For organization-level pages or custom domains, omit base entirely.

GitHub Actions Workflow

.github/workflows/deploy.yml
name: Deploy to GitHub Pages

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Build site
run: npm run build

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: dist/

deploy:
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

The workflow triggers on pushes to main and can be run manually via workflow_dispatch. Permissions are scoped per job: build only needs contents: read to check out code, while deploy needs pages: write and id-token: write for the OIDC token that authenticates with GitHub Pages. The concurrency group prevents overlapping deployments. The build job checks out code, installs dependencies, builds the Astro site, and uploads dist/ as a Pages artifact. The deploy job deploys the artifact and links the deployment to the github-pages environment.

note

Astro outputs to dist/ by default. If you change the output directory in astro.config.mjs, update the path in the upload artifact step to match.

Custom Domain

To use a custom domain, create a CNAME file in the public/ directory containing the domain name. Astro copies everything in public/ to the build output root, so the file ends up at the right location for GitHub Pages.

public/CNAME
docs.example.com

Configure DNS with your domain registrar: for a subdomain like docs.example.com, create a CNAME record pointing to your-org.github.io. For an apex domain, create A records pointing to GitHub's IPs (see the GitHub Pages custom domain docs). When using a custom domain, remove the base setting from astro.config.mjs — the site is served from the domain root.

tip

Place the CNAME file in public/, not in the repository root. Files in the repo root are not included in Astro's build output. If the CNAME file is missing from the deployed site, GitHub Pages removes the custom domain configuration on each deployment.

Common Pitfalls

  • Base path misconfiguration — Setting base when it's not needed (or omitting it when it is) breaks all asset URLs, stylesheets, and navigation links. If the site shows unstyled HTML, check base first.
  • Wrong Pages source — Selecting "Deploy from a branch" instead of "GitHub Actions" causes deployments to fail silently. The workflow uploads an artifact; it does not push to a branch.
  • Missing CNAME file — If public/CNAME is missing, each deployment resets the custom domain in GitHub Pages settings. The site reverts to *.github.io until manually reconfigured.
  • Permission errors — The workflow requires both pages: write and id-token: write. Missing either causes a "resource not accessible by integration" error.
  • Stale deployments — GitHub Pages uses a CDN. After deployment, it can take a few minutes for changes to appear. Hard-refresh or use a private/incognito window to verify.

Task Breakdown

EpicDescriptionBallpark (dev-days)
Project SetupScaffold Starlight project, configure TypeScript, set up linting and formatting0.5–1
Site ConfigurationConfigure astro.config.mjs (site URL, sidebar, navigation, branding)0.5–1
CI/CD & DeploymentGitHub Actions workflow, GitHub Pages configuration, custom domain (if applicable)0.5–1
Content StructureCreate directory layout, set up content categories, define information architecture1–2
Initial ContentWrite first batch of documentation pages (estimate 0.25–0.5 days per page)3–10
CustomizationCustom components, theme adjustments, logo and branding, custom CSS1–3
Review & PolishContent review, link checking, cross-browser testing, search verification1–2

Ballpark Totals

MetricRange
Total effort (excluding content writing)4–10 dev-days
Total effort (including ~20 pages of content)8–20 dev-days
Duration (1 developer)1–4 weeks
caution

Content writing dominates the effort. The infrastructure — scaffolding, CI/CD, deployment — can be done in a single day. Duration depends almost entirely on how many pages need to be written and how technical the content is. Apply complexity multipliers for i18n, custom component development, or migration from an existing docs platform. Always present estimates as ranges.

What's Not Included

  • Content writing beyond initial pages (ongoing documentation is a continuous effort)
  • UX/UI design for custom themes
  • Internationalization — add +10–20% if needed
  • Migration from an existing documentation platform (Confluence, Notion, etc.)
  • Custom search infrastructure (Pagefind covers most needs; add 1–2 days if Algolia or similar is required)
  • Project management overhead — typically +20–30% (see Common Pitfalls)

Further Reading

Internal docs:

External resources: