CodeWorlds
Back to collections
Guide15 min readCodeWorlds Team

Starlight, a documentation theme for Astro

Starlight 0.41.7 under MIT adds Pagefind search, translations and a sidebar to an Astro project. Where the local search index hits its scale limit.

Starlight, a documentation theme for Astro

Starlight is an Astro integration that turns a directory of Markdown files into a documentation site with a sidebar, dark mode, translations and a search engine that runs locally. The current version of the @astrojs/starlight package is 0.41.7, released on 5 August 2026, under the MIT licence.

A theme, not a separate framework

The difference between a theme and a framework sounds like an argument about words until you start fitting the tool to a repository. Docusaurus is its own framework: its own commands, its own router, its own build cycle. Starlight has none of that, because it takes everything from Astro. You install it as a dependency, add one entry to the integrations array in astro.config.mjs, and carry on with plain astro dev and astro build.

The practical consequence is that documentation stops being a separate project. A marketing site, a company blog and the docs can live in one repository, share one config file, one set of dependencies and one deployment pipeline. Starlight claims only the routes it injects itself, and the rest of src/pages/ stays yours.

The package ships 25 components that can be swapped through the components config field, plus a set of components for use inside content: Aside, Badge, Card, CardGrid, Icon, Tabs, TabItem, LinkCard, Steps, FileTree, LinkButton and a re-exported Code from the astro-expressive-code package. Syntax highlighting in code blocks is handled by Expressive Code, version 0.44.

The flip side of that dependency is obvious and has to be said plainly. A team that does not know Astro starts by learning the framework: content collections, islands, adapters, the syntax of .astro files. If nobody in the company touches Astro outside the docs, that is the cost of maintaining knowledge you cannot reuse anywhere else.

Version, licence and the number below one

The licence agrees across all three places I checked. The LICENSE file on the main branch of the withastro/starlight repository holds the MIT text with a copyright notice starting in 2023. The license field in the npm registry for version 0.41.7 reads MIT. The published tarball, 336 kilobytes in size, contains 195 files including package/LICENSE, along with 144 .astro, .ts and .js files, meaning actual code rather than a stub pointing at another package. This is a textbook case with nothing to resolve during a dependency audit.

The version number, on the other hand, deserves its own paragraph. The package first appeared as 0.0.1 in May 2023 and, after 186 releases, still sits below one, even though the project is maintained, ships on a regular cadence and is backed by the Astro team. Neither the package README nor the changelog carries a sentence declaring a compatibility policy, so the change history is the only hard source. It says the following: out of 41 minor releases, from 0.1.0 to 0.41.0, eighteen contain at least one entry marked as a breaking change. The most recent example is 0.41.0, which added support for Astro 7 and dropped Astro 6 in the same release. The conclusion for planning work: moving from 0.40 to 0.41 is not a change you can let a dependency bot push through without reading the release notes.

Peer dependency ranges are worth checking alongside that. Starlight 0.41.7 declares astro in the ^7.0.2 range and @astrojs/markdown-remark in the ^7.2.0 range. Current Astro is 7.2.4, released on 19 August 2026, so both ranges are satisfied without stretching.

Installation and configuration

Two entry paths: a new project from a template, or adding it to an existing one.

Code
Bash
# new project from the ready-made documentation template
npm create astro@latest -- --template starlight

# or add Starlight to an existing Astro project
npx astro add starlight

# local work and building
npx astro dev
npx astro build

The astro add starlight command installs the package and adds the integration to the config, but it will not create the content collection for you. That has to be done by hand, in src/content.config.ts, using the loader and schema Starlight provides.

TSsrc/content.config.ts
TypeScript
// src/content.config.ts
import { defineCollection } from 'astro:content'
import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders'
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'

export const collections = {
  docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
  i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() })
}

docsLoader reads files from src/content/docs/ with the extensions md, mdx, markdown, mdown, mkdn, mkd and mdwn, skipping names that start with an underscore. If the project has the @astrojs/markdoc integration, the loader adds mdoc to that list. The i18n collection is optional and accepts json, yml and yaml files.

The theme configuration itself lives in astro.config.mjs. Below is a set of fields that actually exist in the 0.41.7 schema.

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

export default defineConfig({
  site: 'https://docs.example.com',
  integrations: [
    starlight({
      title: 'Documentation',
      tagline: 'Deployment handbook',
      titleDelimiter: '|',
      credits: false,
      prerender: true,
      editLink: { baseUrl: 'https://github.com/example/docs/edit/main/' },
      lastUpdated: true,
      pagination: true,
      tableOfContents: { minHeadingLevel: 2, maxHeadingLevel: 3 },
      customCss: ['./src/styles/docs.css'],
      routeMiddleware: './src/starlightRouteData.ts',
      markdown: { headingLinks: true, processedDirs: [] },
      components: {
        Footer: './src/overrides/Footer.astro'
      },
      sidebar: [
        { label: 'Start', link: '/start/' },
        {
          label: 'Guides',
          collapsed: false,
          items: [{ autogenerate: { directory: 'guides', collapsed: true } }]
        }
      ]
    })
  ]
})

Two fields from that list are a common source of silent errors. routeMiddleware rejects any path matching ./src/middleware.ts or ./src/middleware/index.ts, because those locations belong to Astro, and prints a message suggesting you rename the file. Meanwhile, autogenerate groups may no longer carry a label field: support for autogenerated groups was removed in 0.39.0, and the schema returns a descriptive error with a ready-made fix.

Pagefind search and where its limit sits

This is the reason to compare Starlight with Docusaurus at all. Docusaurus has no search in core and points you at the external Algolia DocSearch service, free only for public technical documentation and only after an accepted application. Starlight has search from the first build and requires no configuration and no account anywhere.

Underneath sits Pagefind version 1.5.2 under MIT, together with the @pagefind/default-ui package at the same version. The mechanism is simple: in the astro:build:done hook Starlight takes the directory of finished HTML, calls createIndex, then index.addDirectory on the output directory, and finally index.writeFiles into a pagefind/ subdirectory. The build log shows a message with the number of HTML files found and the indexing time.

Two limitations follow from that mechanism and no configuration can work around them. First: the index is produced from generated HTML, so pages have to be prerendered. The config schema explicitly rejects prerender: false combined with Pagefind enabled and returns a message saying Pagefind search is not supported with prerendering disabled. Second: indexing adds to build time, and it happens after the build proper finishes, so on large documentation you will see it in the deployment pipeline.

On scale, the Pagefind authors state their own measure: a site of ten thousand pages should be searchable with a total network payload under 300 kilobytes including the library itself, and for most sites that figure should be closer to 100 kilobytes. The key to that promise is splitting the index into chunks, from which the browser fetches only the ones matching the query.

I measured this on Starlight's own site. The pagefind-entry.json file weighs 1,133 bytes and lists 17 separate language indexes, 36 pages each, so 612 pages in total. The English index consists of three .pf_index files of 20,279, 33,546 and 34,745 bytes, totalling 88,570 bytes. On top of that come the pieces fetched every time the search opens for the first time: pagefind.js at 45,555 bytes, pagefind-ui.js at 119,987 bytes, pagefind-ui.css at 14,482 bytes, wasm.en.pagefind at 72,209 bytes, the entry file mentioned above at 1,133 bytes and the index metadata file at 450 bytes. Those six items sum to 253,816 bytes. Adding one index chunk, the first search costs between 274,095 and 288,561 bytes before the compression your server will apply anyway.

The scale limit therefore sits in two places at once. Over 250 kilobytes of fixed overhead is a lot for a documentation site, and everyone who clicks the search field pays it. The second place is multilingual content: Pagefind builds a separate index per language, so documentation in a dozen or so languages means a dozen or so indexes, a dozen or so WebAssembly files and a correspondingly longer build. With two hundred pages in one language you will never notice. With a few thousand pages across eight languages, measure the build time before you commit.

Client behaviour is sensible in the meantime. The search code loads dynamically through await import('@pagefind/default-ui') inside a <dialog> element, in a call scheduled by requestIdleCallback, and only in production builds. The PagefindUI constructor receives element, baseUrl, bundlePath, showImages: false, showSubResults: true and the interface translations.

Result relevance can be tuned, and the field names map one to one onto Pagefind options.

Code
JavaScript
starlight({
  title: 'Documentation',
  pagefind: {
    indexWeight: 1,
    ranking: {
      pageLength: 0.1,
      termFrequency: 0.1,
      termSaturation: 2,
      termSimilarity: 9,
      diacriticSimilarity: 0.8,
      metaWeights: { title: 5 }
    },
    mergeIndex: [
      { bundlePath: 'https://api.example.com/pagefind/', indexWeight: 0.5, language: 'en' }
    ]
  }
})

The defaults are exactly the values shown above, and metaWeights weighs the title at five by default. Search can also be switched off entirely with pagefind: false, a single page can be skipped with pagefind: false in its frontmatter, and a fragment of a page can be cut out with the data-pagefind-ignore attribute. Anyone with access to the DocSearch programme who prefers Algolia can reach for the official @astrojs/starlight-docsearch plugin at version 0.7.0.

Translations and sidebar navigation

Starlight has translations in core, with no plugins. The package contains 34 interface translation files, from Arabic to both Chinese variants, and Polish is among them. Configuration comes down to the locales field, where the root key marks the language served from the top-level path, and every entry accepts label, an optional lang and a dir of either ltr or rtl. Language tags are validated against BCP 47, so a typo in a language code stops the build instead of producing dead URLs.

Code
JavaScript
starlight({
  title: { pl: 'Dokumentacja', en: 'Documentation' },
  defaultLocale: 'root',
  locales: {
    root: { label: 'Polski', lang: 'pl' },
    en: { label: 'English', lang: 'en' },
    ar: { label: 'العربية', lang: 'ar', dir: 'rtl' }
  }
})

The sidebar works in two modes that can be mixed inside a single array. An item with a link field is a plain link and accepts label, translations, badge and attrs carrying HTML attributes. A group has label, items and collapsed. An autogenerate entry reads a directory and builds items from the files, accepting directory, collapsed and attrs. The order and labels of individual pages are set in their frontmatter.

Code
Markdown
---
title: Deploying to production
description: Deployment steps and a checklist
template: doc
tableOfContents: false
lastUpdated: 2026-08-10
draft: false
pagefind: true
sidebar:
  label: Deployment
  order: 3
  badge: New
prev: false
next:
  link: /guides/rollback/
  label: Rolling changes back
banner:
  content: This page covers the 3.x line
---

Page content in Markdown.

The full list of frontmatter fields is title, description, editUrl, head, tableOfContents, template, hero, lastUpdated, prev, next, sidebar, banner, pagefind and draft. The template field takes either doc or splash, where splash is a layout without a sidebar, meant for a landing page with a hero section.

Mixing docs with the rest of the project

This point is easy to miss and often decisive. Since Starlight is an Astro integration, the rest of the project stays a plain Astro project. The home page, pricing, blog and contact form live in src/pages/, the documentation in src/content/docs/, and a single build produces all of it at once.

Code
TEXT
src/
  content.config.ts
  content/
    docs/            # claimed by Starlight
      index.mdx
      guides/
    i18n/            # interface translations
  pages/
    index.astro      # marketing page, outside Starlight
    pricing.astro
  components/
    PricingTable.tsx # a [React] island or any other framework
  styles/
    docs.css

Interactive components written in React run on the marketing page as islands and can also be embedded in the MDX content of the docs. The bundler is Vite, types are checked by TypeScript, and if the project runs Tailwind CSS version 4, the official @astrojs/starlight-tailwind plugin at version 5.0.0 matches the documentation theme to the same palette. A hosted product cannot offer this by definition, because the documentation then lives outside your repository.

Starlight against Docusaurus, Mintlify and Nextra

FeatureStarlight 0.41.7Docusaurus 3.10.2MintlifyNextra 4.6.1
FoundationAstro integrationits own React frameworkhosted platforma Next.js theme
Search in coreyes, Pagefind locallyno, external Algoliayes, on the vendor sideyes, local
Versioning in coreno, community pluginyesyes, on the vendor sideno
Blog in coreno, community pluginyes, in the classic presetyes, on the vendor sideno
Translations in coreyes, 34 interface languagesyesyes, on the vendor sideyes
LicenceMITMITElastic 2.0, closed engineMIT
Latest release5 August 202610 July 2026mint tool, 22 August 20264 December 2025

The most important difference against Docusaurus is the absence of documentation versioning in Starlight's core. Anyone maintaining several major product versions in parallel gets a ready-made versioned_docs mechanism in Docusaurus, while in Starlight they have to reach for the starlight-versions plugin. That project is maintained by one person outside the Astro team, at version 0.10.0 from 21 August 2026, under MIT. The blog is the same story: starlight-blog at version 0.29.0 comes from the same source. There are three official plugins carrying the @astrojs scope: starlight-docsearch, starlight-tailwind and starlight-markdoc. Everything beyond those is community maintained, with everything that implies over a longer maintenance horizon.

Against Mintlify the difference is of a different kind: there you pay and receive a finished platform with a closed engine, here you get MIT code in your own repository and take responsibility for hosting yourself. Nextra remains a sensible choice for a team already living in Next.js, but its latest release dates from December 2025, so the pace of work is visibly slower than in the other two projects.

Common mistakes

First: a missing src/content.config.ts. The astro add starlight command adds the integration but will not create the collection, and without docsLoader the site finds no content at all.

Second: prerender: false with search enabled. The configuration will be rejected with a message about Pagefind not being supported without prerendering. If part of the site has to run server side, disable prerendering for that route only, not globally.

Third: a label field inside an autogenerate object. That form stopped working in 0.39.0. You have to create a group with the label and place the autogenerate entry inside its items array.

Fourth: a middleware file named src/middleware.ts and passed to routeMiddleware. That path collides with Astro's own mechanism, so the schema returns an error. Name the file something else, for example src/starlightRouteData.ts.

Fifth: upgrading a minor release without reading the notes. Eighteen out of forty one minor releases carried a breaking change, so a bot bumping dependencies can break the build.

Sixth: peer dependency ranges in plugins are open at the top end. starlight-blog declares >=0.41.0, starlight-versions declares >=0.39.0, and starlight-tailwind declares >=0.38.0. The package manager will not stop you installing a plugin on a Starlight version newer than the one that plugin was tested against.

FAQ

Is Starlight suitable for large documentation?

Up to a few hundred pages in one language, without reservations. Above that threshold, check two things: Pagefind indexing time, which is added after the build finishes, and the number of languages, because each gets its own index and its own WebAssembly file. The Pagefind authors claim correct operation at ten thousand pages with a payload under 300 kilobytes.

Can documentation be versioned?

Not in core. The starlight-versions plugin handles it, maintained outside the Astro team, at version 0.10.0 under MIT. If versioning is a hard requirement, Docusaurus has it in core and that is a real argument in its favour.

What does a version number below one mean?

That minor releases may break compatibility, and in this project they actually did: eighteen out of forty one minor releases carry a changelog entry marked as a breaking change. A recent example is 0.41.0, which dropped support for Astro 6.

Do you need to know Astro to use Starlight?

To stand up a site from the template, no. To change the layout, swap any of the 25 components or add your own pages, yes, because you then work in .astro files and in Astro content collections.

How do I exclude a page from the index?

Put pagefind: false in its frontmatter. To cut out part of a page, wrap it in an element carrying the data-pagefind-ignore attribute. The whole search feature is disabled with pagefind: false in the integration config.

Can the docs sit next to a marketing site?

Yes, and that is one of the main advantages of this arrangement. Starlight handles the docs collection while src/pages/ stays at your disposal. One repository, one build, one deployment.

Sources: the Starlight documentation, the Pagefind documentation and the licence file in the repository.

Read next

We use cookies to enhance your experience on the site