Skip to content

Documentation Site Setup

This guide documents how this documentation site was set up, including the tech stack choices and configuration steps.

ComponentTechnologyPurpose
FrameworkAstro + StarlightStatic site generator optimized for documentation
CMSDecap CMSGit-based headless CMS with web UI
HostingCloudflare PagesFree static hosting with auto-deploy
AuthCloudflare WorkerOAuth proxy for GitHub authentication
StorageGitHubVersion control and content storage
  • Purpose-built for documentation sites
  • Fast static output with excellent SEO
  • Built-in search, navigation, and dark mode
  • MDX support for interactive components
  • Free and open source - No per-user pricing
  • Git-based - Content stored in GitHub, not a separate database
  • Web UI - Edit content without touching code
  • No vendor lock-in - Content is just Markdown files
  • Free tier - Unlimited sites, generous bandwidth
  • Auto-deploy - Push to GitHub, site updates automatically
  • Fast global CDN - Built into Cloudflare’s network
  • Custom domains - Easy SSL and DNS integration
  • Netlify: Would work, but we’re already using Cloudflare for DNS
  • Vercel: Great for Next.js, but Cloudflare is simpler for static sites
  • Contentful/Sanity: Overkill for docs, and have user/content limits on free tiers
Terminal window
npm create astro@latest jb-cloud-docs -- --template starlight
cd jb-cloud-docs
npm install
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
export default defineConfig({
site: 'https://docs.jbcloud.app',
integrations: [
starlight({
title: 'JB Cloud Docs',
sidebar: [
{
label: 'xCloud',
autogenerate: { directory: 'xcloud' },
},
{
label: 'Cloudflare',
autogenerate: { directory: 'cloudflare' },
},
// Add more sections as needed
],
}),
],
});

Create public/admin/index.html:

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex" />
<title>Content Manager</title>
</head>
<body>
<script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
</body>
</html>

Create public/admin/config.yml:

backend:
name: github
repo: YOUR_USERNAME/YOUR_REPO
branch: main
base_url: https://github-oauth.YOUR_SUBDOMAIN.workers.dev
auth_endpoint: /auth
media_folder: "public/images"
public_folder: "/images"
collections:
- name: "xcloud"
label: "xCloud"
folder: "src/content/docs/xcloud"
create: true
slug: "{{slug}}"
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Description", name: "description", widget: "string" }
- { label: "Sort Order", name: "sidebar", widget: "object",
fields: [{ label: "Order", name: "order", widget: "number", default: 0 }] }
- { label: "Body", name: "body", widget: "markdown" }
  1. Go to GitHub → Settings → Developer settings → OAuth Apps
  2. Click New OAuth App
  3. Fill in:
    • Application name: Your Docs CMS
    • Homepage URL: https://your-docs-site.com
    • Callback URL: https://github-oauth.YOUR_SUBDOMAIN.workers.dev/callback
  4. Save the Client ID and Client Secret

Cloudflare Pages doesn’t have built-in OAuth like Netlify, so we need a Worker to handle GitHub authentication.

  1. Go to Cloudflare → Workers & Pages → Create Worker
  2. Name it github-oauth
  3. Deploy, then edit with this code:
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === '/auth') {
const authUrl = `https://github.com/login/oauth/authorize?client_id=${env.CLIENT_ID}&scope=repo,user`;
return Response.redirect(authUrl, 302);
}
if (url.pathname === '/callback') {
const code = url.searchParams.get('code');
if (!code) {
return new Response('Missing code parameter', { status: 400 });
}
const response = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
client_id: env.CLIENT_ID,
client_secret: env.CLIENT_SECRET,
code: code
})
});
const data = await response.json();
if (data.error) {
return new Response(`Error: ${data.error_description}`, { status: 400 });
}
const token = data.access_token;
const html = `<!DOCTYPE html>
<html>
<head><title>Authorizing...</title></head>
<body>
<script>
(function() {
function receiveMessage(e) {
window.opener.postMessage(
'authorization:github:success:{"token":"${token}","provider":"github"}',
e.origin
);
window.removeEventListener("message", receiveMessage, false);
}
window.addEventListener("message", receiveMessage, false);
window.opener.postMessage("authorizing:github", "*");
})();
</script>
</body>
</html>`;
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
});
}
return new Response('Not found', { status: 404 });
}
};
  1. Add environment variables in Worker Settings → Variables:
    • CLIENT_ID = Your GitHub OAuth Client ID
    • CLIENT_SECRET = Your GitHub OAuth Client Secret (encrypt this)
  1. Push your code to GitHub
  2. Go to Cloudflare → Workers & Pages → Create → Pages
  3. Connect to your GitHub repo
  4. Configure build settings:
    • Framework: Astro
    • Build command: npm run build
    • Output directory: dist
  5. Deploy
  1. In Cloudflare Pages, go to Custom domains
  2. Add your domain (e.g., docs.jbcloud.app)
  3. Cloudflare handles DNS and SSL automatically
  1. Go to https://your-site.com/admin
  2. Login with GitHub
  3. Select a collection
  4. Click New to create a document
  5. Fill in the fields and write your content
  6. Click Publish

Changes are committed to GitHub and auto-deployed.

Terminal window
# Create a new doc
touch src/content/docs/xcloud/new-guide.md
# Edit with your preferred editor
# Then commit and push
git add -A
git commit -m "Add new guide"
git push

Every doc needs frontmatter:

---
title: Your Page Title
description: Brief description for SEO and previews.
sidebar:
order: 1 # Controls sort order in navigation
---
Your content here...
  • Check that the GitHub OAuth callback URL matches your Worker URL exactly
  • Verify environment variables are set in the Worker
  • Try a different browser (Safari can be strict with popups)
  • Check Node.js version - add NODE_VERSION=18 in environment variables if needed
  • Review build logs for specific errors
  • Cloudflare Pages deploys take 1-2 minutes
  • Check the deployment status in Cloudflare dashboard
  • Hard refresh the site (Cmd+Shift+R)

This entire documentation site was set up with the help of Claude Code, Anthropic’s CLI tool for AI-assisted development. Here’s how Claude Code streamlined the process.

  1. Scaffolded the project - Created the Astro Starlight project with proper configuration
  2. Wrote configuration files - Generated astro.config.mjs, Decap CMS config, and Worker code
  3. Created initial documentation - Wrote the first docs based on our conversation
  4. Set up GitHub repo - Initialized git, created the repo, and pushed code
  5. Debugged OAuth issues - Fixed the Worker code when Safari had popup restrictions
  6. Generated this guide - Meta-documented the entire setup process

How to Use Claude Code for Similar Projects

Section titled “How to Use Claude Code for Similar Projects”

Install Claude Code:

Terminal window
npm install -g @anthropic-ai/claude-code

Start a session in your project directory:

Terminal window
cd ~/Sites/my-project
claude

Starting a docs site:

Create a documentation site using Astro Starlight with sections
for xCloud, Cloudflare, Supabase, and Vercel. Set up Decap CMS
for web-based editing and deploy to Cloudflare Pages.

Adding documentation:

Add documentation for how to update Docker apps on xCloud via SSH.
Include the issue with Docker permissions and the Command Runner workaround.

Debugging issues:

The OAuth login shows this error in the console: [paste error].
What's wrong and how do I fix it?

When running on your machine, Claude Code can:

  • Read and write files in your project
  • Run terminal commands (npm, git, etc.)
  • Create GitHub repos via gh CLI
  • Check configurations and suggest fixes

It does NOT have direct access to:

  • Cloud provider dashboards (Cloudflare, AWS, etc.)
  • Your browser or GUI applications
  • Services requiring OAuth (unless MCP servers are configured)
  1. Be specific - “Add a guide for updating OpenWebUI on xCloud” is better than “add some docs”

  2. Share context - Paste error messages, screenshots, or relevant config snippets

  3. Iterate - If something doesn’t work, share the error and Claude Code will debug

  4. Let it handle boilerplate - Config files, Worker code, and repetitive setup tasks are perfect for AI assistance

  5. Review before committing - Always check generated code makes sense for your use case

Here’s roughly how the conversation went to create this site:

  1. “Create a docs site for my cloud processes” → Claude Code suggested the tech stack options
  2. “Use Decap CMS, host on Cloudflare Pages” → Created the project and configuration
  3. “Set up the GitHub repo” → Initialized and pushed to GitHub
  4. “Deploy failed, wrong username” → Fixed the config
  5. “OAuth isn’t working” → Created the Cloudflare Worker
  6. “Still getting errors” → Debugged Safari popup issues
  7. “Document how we set this up” → Generated this guide

Total time: ~30 minutes from idea to fully deployed docs site with CMS.

Use Claude Code when:

  • Setting up new projects with multiple config files
  • Writing boilerplate code (Workers, configs, etc.)
  • Debugging errors you’re unfamiliar with
  • Generating documentation from conversations
  • Repetitive git operations

Do manually when:

  • Configuring cloud dashboards (Cloudflare, GitHub OAuth apps)
  • Sensitive operations (adding secrets, production deployments)
  • You want to understand each step deeply
  • The task requires visual/GUI interaction