Building a Developer Docs Site with Thally

|  7 min read
thallydocsdeveloper-docs

A step-by-step guide to creating, configuring, writing and publishing a developer documentation site with Thally — plus the obstacles I hit along the way and how I got past them.

A step-by-step guide to creating, configuring, writing and publishing a developer documentation site with Thally — plus the obstacles I hit along the way and how I got past them.

I used Thally to document Mood Music, a small Spotify-playlist app I’d already built and shipped. Picking something I’d already written and deployed mattered more than I expected — it meant every troubleshooting entry in this walkthrough is a real failure mode I’d actually hit, not an invented one. If you’re trying this yourself, start from a project you know well enough to have opinions about where it breaks.

This covers the whole path: prerequisites, setup, getting the repo onto your machine, configuration, writing the content, publishing, and the handful of things that slowed me down along the way.

Prerequisites#

  • A GitHub account (Thally creates your docs repo there).
  • Git, with SSH (or HTTPS) access to GitHub, so you can clone and push the docs repo.
  • Node.js 22 or newer and npm, for previewing and validating locally. Thally’s own CI runs on Node 22.
  • A project you know well enough to document without having to research it as you go. It doesn’t need to be large — small and well-understood beats large and half-remembered. In our case, I am using Mood Music.

Setup#

  1. Sign in to app.thally.io, ideally with GitHub — you’ll need the GitHub App installed either way.
  2. Select Create a site, give it a name, and choose your thally.app subdomain.
  3. Choose Create a new docs repository rather than pointing at an existing one. If this is your first site, installing the GitHub App will ask for “All repositories” access to create a fresh repo — more than the task strictly needs, and worth knowing to expect rather than second-guessing in the moment.
  4. You’ll be offered a product source (to connect a separate code repo for change-driven doc updates) and knowledge surfaces. Neither is needed to get a basic site live — choose “I’ll do this later” and move on.
  5. Review the site name, address, and repository, then publish. Thally scaffolds the repo from its starter template and runs the first deploy. Once the dashboard shows it as live, you have both a public URL and a GitHub repo to clone.

Clone the repository#

Everything from here on happens locally, so you need the repo Thally just created on your machine. Pull it down, install dependencies, and start the preview:

git clone [email protected]:<your-username>/<your-docs-repo>.git
cd <your-docs-repo>
npm install
npx thally dev

The preview runs at http://localhost:3040 and reloads as you edit, except for changes made directly to mdx files - for those, you need to kill the preview and start it again. If you change anything through the Thally dashboard later, run git pull before you start writing again, so you’re not editing a stale copy.

The starter is a full Next.js app, so the first look at the repo can be a lot. Here’s what mine looked like for Mood Music, trimmed to what matters:

mood-music-docs/
├── docs.json              # navigation: tabs, groups, page order
├── openapi.yaml           # API spec, rendered as the API reference
├── src/
│   ├── content/           # your pages: one .mdx file per slug
│   │   ├── introduction.mdx
│   │   ├── quickstart.mdx
│   │   ├── deploying.mdx
│   │   ├── troubleshooting.mdx
│   │   ├── api/
│   │   │   ├── introduction.mdx
│   │   │   └── authentication.mdx
│   │   └── guides/
│   │       └── spotify-oauth.mdx
│   ├── data/
│   │   └── site.ts        # product name, links, brand defaults
│   ├── app/               # Next.js routes      ┐
│   ├── components/        # UI components      ├ Thally runtime
│   └── lib/               # runtime internals  ┘
├── public/                # logos, favicons, images
├── .github/workflows/     # CI: content checks, runtime sync, framework updates
├── starter-release.json   # pinned starter + runtime version
├── AGENTS.md              # instructions for AI coding agents in the repo
└── package.json

The split that matters: docs.json, src/content/, src/data/site.ts and public/ are yours. Everything under src/app, src/components and src/lib is the Thally runtime, a pinned snapshot that thally starter update refreshes for you. It’s tempting to patch a component directly when something looks off, but hand edits there are exactly what that update has to stop and ask you about. Leave starter-release.json in place too; it’s how the updater knows which version you started from.

Nested folders map straight onto slugs: src/content/guides/spotify-oauth.mdx is the page guides/spotify-oauth in docs.json.

Configuration#

Everything that matters lives in three places:

  • docs.json — the site’s navigation. Pages are grouped under tabs:

    {
      "navbar": {
        "primary": { "label": "Get started", "href": "/quickstart" }
      },
      "tabs": [
        {
          "tab": "Get started",
          "groups": [
            {
              "group": "Get started",
              "pages": ["introduction", "quickstart", "deploying", "troubleshooting"]
            }
          ]
        },
        {
          "tab": "Guides",
          "groups": [{ "group": "Guides", "pages": ["guides/spotify-oauth"] }]
        }
      ]
    }

    This is a trimmed version of the one behind the Mood Music docs. Each entry in pages is a slug, matched to the .mdx file at the same path in src/content/. The order of the list is the order in the sidebar.

  • src/data/site.ts — the product identity: name, description, and links (repo, live app). This is what makes the starter template stop looking like a template.

  • src/content/*.mdx — the actual pages, plain Markdown with a small frontmatter block (title, description) on top.

Nothing here needs touching before your first page exists — write the content first, then come back and wire up the navigation.

Content creation#

The brief I was working from asked for three things: an introduction, a working quickstart, and one practical guide. I kept to that shape:

  • Introduction — what the project does, who the docs are for (not necessarily the same audience as the product itself), and a short architecture overview. For Mood Music, the product is for anyone wanting playlists; the docs are for developers who want to run or extend it.
  • Quickstart — one concrete, verifiable outcome, not just “install the dependencies.” Mine ends with the reader typing a mood and getting a real Spotify playlist back locally — something they can see work, not just a server that started without errors.
  • Practical guide — I picked Spotify’s OAuth setup, because it’s the part of the project with the most genuine, recurring friction: a redirect URI has to be registered in Spotify’s dashboard and allowlisted in code, and forgetting either one is the single most common way login breaks after a deploy. A guide earns the word “practical” by covering a real failure mode in enough detail that a reader hitting it can actually fix it — not just by existing.

Tip: write your pages first, then wire up docs.json. Hold off on the navigation until the content exists. Once you can see what you’ve actually written, the right grouping is usually obvious, and you won’t have to rework a navigation tree you designed for pages that never materialised.

Publishing#

Before pushing anything:

npx thally check
npm run build

npx thally check runs the Thally CLI version pinned in the repo’s package.json, so you’re checking against the same rules CI uses. check catches content and navigation problems — a docs.json entry pointing at a page slug that doesn’t exist, for instance — and build catches everything else. Both are fast enough to run after every real edit, not just once at the end.

Once both pass, commit and push to main, the production branch. CI re-runs the content check on every push, and Thally Cloud picks up the push and deploys automatically; there’s no separate “publish” button to find. Open the live URL afterward and click through every page in the nav — check and build passing is necessary, not sufficient, for the site actually reading the way you intended.

Obstacles, and how I resolved them#

  • Making changes to the starter template - Although I may not have caught the explanation in Thally’s documentation, I found out during CI/CD time that the changes made to the starter template are classified as errors meaning that the build fails. I had to revert the changes to the starter template and stick to the changes I made to the content. Hopefully, this will be fixed in the future or an alternative workflow will be provided.

    CI failing at “Verify generated runtime snapshot”: src/components/mdx/rich-content.tsx bytes differ from the pinned Thally runtime
    CI failing at “Verify generated runtime snapshot”: src/components/mdx/rich-content.tsx bytes differ from the pinned Thally runtime

  • Navigation mistakes only surface at check or build time, not as you write docs.json. The fix wasn’t a tool — it was a habit: run check after every navigation edit, not just before pushing, so a typo in a page slug gets caught in seconds instead of at the end of a session.

Result#

A live documentation site based on Thally with an introduction, a working quickstart, one practical guide, and short deploying and troubleshooting pages. Behind it is a plain Git repo of Markdown and one JSON file. Follow the steps above and you’ll end up with your own version of it.