Skip to content

Getting Started

Four commands. That's how long it takes to go from nothing to a running documentation site with navigation, search, dark mode, and syntax highlighting. Let's get there.

Prerequisites

You'll need:

  • Node.js version 22 or higher
  • pnpm (recommended) or npm/yarn

Quick Start

The fastest path is the create-ardo CLI. It scaffolds a complete project — configuration, example content, and a GitHub Pages deployment workflow — so you can focus on writing.

pnpm create ardo@latest my-docs
cd my-docs
pnpm install
pnpm dev

Open http://localhost:5173 and you'll see your site. Add an MDX file to app/routes/, and it appears in the sidebar automatically.

Manual Installation

If you'd rather set things up yourself, here's the full walkthrough. This is also helpful for understanding what create-ardo does under the hood.

1. Create a new project

mkdir my-docs
cd my-docs
pnpm init

2. Install dependencies

pnpm add ardo react react-dom react-router isbot
pnpm add -D @react-router/dev typescript vite @types/react @types/react-dom

3. Create Vite configuration

This is the heart of your Ardo setup. Everything — title, navigation, sidebar — is configured in one place through the ardo() plugin:

import { defineConfig } from "vite"
import { ardo } from "ardo/vite"

export default defineConfig({
  plugins: [
    ardo({
      title: "My Documentation",
      description: "My awesome documentation site",

      themeConfig: {
        nav: [{ text: "Guide", link: "/guide/getting-started" }],
        sidebar: [
          {
            text: "Guide",
            items: [{ text: "Getting Started", link: "/guide/getting-started" }],
          },
        ],
      },
    }),
  ],
})

4. Create React Router configuration

Ardo uses React Router 7 under the hood. For a static documentation site, you want pre-rendering enabled and SSR off:

import type { Config } from "@react-router/dev/config"

export default {
  ssr: false,
  prerender: true,
} satisfies Config

5. Create app files

These are standard React Router entry files. If you've worked with React Router before, they'll look familiar.

app/root.tsx:

import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"
import { Layout, Header, Sidebar, Footer } from "ardo/ui"
import { ArdoProvider } from "ardo/runtime"
import config from "virtual:ardo/config"
import sidebar from "virtual:ardo/sidebar"
import "ardo/ui/styles.css"

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  )
}

export default function Root() {
  return (
    <ArdoProvider config={config} sidebar={sidebar}>
      <Layout header={<Header title="My Docs" />} sidebar={<Sidebar />} footer={<Footer />}>
        <Outlet />
      </Layout>
    </ArdoProvider>
  )
}

app/entry.client.tsx:

import { startTransition, StrictMode } from "react"
import { hydrateRoot } from "react-dom/client"
import { HydratedRouter } from "react-router/dom"

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <HydratedRouter />
    </StrictMode>
  )
})

app/entry.server.tsx:

import type { EntryContext } from "react-router"
import { ServerRouter } from "react-router"
import { renderToReadableStream } from "react-dom/server"
import { isbot } from "isbot"

export default async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  routerContext: EntryContext
) {
  const userAgent = request.headers.get("user-agent")

  const stream = await renderToReadableStream(
    <ServerRouter context={routerContext} url={request.url} />,
    {
      onError(error: unknown) {
        console.error(error)
        responseStatusCode = 500
      },
    }
  )

  if (userAgent && isbot(userAgent)) {
    await stream.allReady
  }

  responseHeaders.set("Content-Type", "text/html")

  return new Response(stream, {
    status: responseStatusCode,
    headers: responseHeaders,
  })
}

6. Create your first document

Create app/routes/guide/getting-started.mdx:

---
title: Getting Started
---

# Getting Started

Welcome to your documentation site!

That's it. Run pnpm dev and you've got a documentation site.

TIP

The create-ardo CLI generates all of these files automatically — use it when you want to skip the boilerplate and get straight to writing.

Development

Start the development server:

pnpm dev

Your site will be running at http://localhost:5173. Changes to your MDX files show up instantly thanks to Vite's hot module replacement.

Production Build

When you're ready to publish:

pnpm build

The output lands in build/client/ — a fully static site ready for any hosting platform. Preview it locally before deploying:

pnpm preview

Project Structure

Here's what an Ardo project looks like. If you've worked with React Router, the layout will feel natural:

my-docs/
├── app/
│   ├── routes/                  # Your MDX/MD content files
│   │   ├── guide/
│   │   │   └── getting-started.mdx
│   │   └── home.tsx             # Home page
│   ├── root.tsx                 # Root layout
│   ├── entry.client.tsx         # Client entry
│   └── entry.server.tsx         # Server entry
├── vite.config.ts               # Vite + Ardo configuration
├── react-router.config.ts       # React Router configuration
├── tsconfig.json
└── package.json

Next Steps

Your site is running. Now make it yours: