New Site Guide

How to scaffold a new microsite, wire it into the monorepo, and deploy it to Cloudflare Pages — including watch path configuration so it only rebuilds when relevant files change.

1

Scaffold the directory

Create a kebab-case folder at the repo root. Copy the structure from an existing site — mario-odyssey-guide is the canonical reference.

my-new-site/
├── astro.config.mjs
├── package.json
├── tsconfig.json
├── wrangler.toml
├── src/
│   ├── layouts/Layout.astro
│   └── pages/
│       ├── index.astro
│       ├── contact.astro
│       ├── thank-you.astro
│       └── privacy.astro
├── public/
│   ├── styles/global.css
│   └── favicon.png
└── functions/
    └── api/contact.ts
2

Config files

package.json

{
  "name": "my-new-site",
  "type": "module",
  "version": "0.0.1",
  "scripts": {
    "dev": "astro dev",
    "build": "astro build",
    "preview": "astro preview"
  },
  "dependencies": {
    "@guides/ui": "workspace:*",
    "@guides/workers": "workspace:*",
    "astro": "^5.7.0"
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20240529.0"
  }
}

astro.config.mjs

import { defineConfig } from 'astro/config';
import { createSitemap } from '@guides/ui/sitemap';

export default defineConfig({
  output: 'static',
  site: 'https://my-new-site.pages.dev',
  integrations: [createSitemap()],
  vite: {
    ssr: { noExternal: ['@guides/ui'] },
  },
});

wrangler.toml

name = "my-new-site"
pages_build_output_dir = "dist"
compatibility_date = "2024-09-23"

tsconfig.json

{
  "extends": "astro/tsconfigs/base",
  "compilerOptions": {
    "types": ["@cloudflare/workers-types"]
  }
}

functions/api/contact.ts

import type { EventContext } from '@cloudflare/workers-types';
import { handleContactForm } from '@guides/workers/contact';

export const onRequestPost = (
  context: EventContext<Record<string, unknown>, string, Record<string, unknown>>
) => handleContactForm(context.request);
3

Layout & branding

src/layouts/Layout.astro is the only place you set site-wide branding. The SiteConfig object flows through to the nav, footer, and all meta tags.

---
import SiteLayout from '@guides/ui/SiteLayout.astro';
import type { SiteConfig } from '@guides/ui/SiteLayout.astro';

const config: SiteConfig = {
  themeColor: '#cf1f2e',      // hex — used in meta theme-color and OG image
  ogSiteName: 'My New Site',  // shown in Open Graph previews
  emoji: '⭐',                // nav brand icon + footer logo
  brandName: 'My Site',       // nav brand text
  navCtaLabel: 'Get the app', // nav CTA button (omit to hide)
  navCtaHref: '/app/',
  footerTagline: 'A short description of what this site is.',
  footerResources: [
    { label: 'Game Wiki', href: 'https://wiki.example.com' },
    { label: 'Official Site', href: 'https://game.example.com' },
    { label: 'Community', href: 'https://reddit.com/r/example' },
    { label: 'Privacy Policy', href: '/privacy' },
  ],
  copyright: '© 2026 My New Site.',
  // establishedYear: 2024, // generates "© 2024–2026" date range
  // gaCode: 'G-XXXXXXXXXX',
};

interface Props {
  title: string;
  description: string;
  canonical?: string;
  keywords?: string;
  noindex?: boolean;
  variant?: 'default' | 'contact' | 'thanks';
}

const { title, description, canonical, keywords, noindex, variant = 'default' } = Astro.props;
---
<SiteLayout {config} {title} {description} {canonical} {keywords} {noindex} {variant}>
  <slot name="head" slot="head" />
  <slot />
</SiteLayout>
4

Colour scheme

public/styles/global.css defines the CSS custom properties used by @guides/ui base styles. Only override what you need — the defaults are sensible.

:root {
    --primary:       #cf1f2e;   /* main brand colour */
    --primary-dark:  #a8161f;   /* darker hover state */
    --primary-light: #e84455;   /* lighter tint */
    --accent:        #f5a623;   /* secondary highlight */
    --accent-dark:   #d4890f;
    --accent-light:  #ffd166;
    --dark:          #1a1a2e;   /* hero/nav background */
    --dark-mid:      #2d2d44;
    --text:          #1f2328;
    --text-muted:    #5a6270;
    --surface:       #f7f8fc;
    --border:        #e4e7ed;
    --white:         #ffffff;
}

Tip: Set --primary to match themeColor in your SiteConfig so the nav gradient, hero, and OG image all stay in sync.

5

Pages

The minimum page set. Contact, thank-you, and privacy are thin wrappers.

src/pages/contact.astro

---
import Layout from '../layouts/Layout.astro';
---
<Layout
  title="Contact – My New Site"
  description="Get in touch."
  variant="contact"
/>

src/pages/thank-you.astro

---
import Layout from '../layouts/Layout.astro';
---
<Layout
  title="Message Sent – My New Site"
  description="Your message has been received."
  variant="thanks"
/>

src/pages/privacy.astro

---
import Layout from '../layouts/Layout.astro';
import PrivacyPage from '@guides/ui/PrivacyPage.astro';
---
<Layout
  title="Privacy Policy – My New Site"
  description="Privacy policy."
  noindex={true}
>
  <PrivacyPage
    siteName="My New Site"
    lastUpdated="May 2026"
    localStorageDescription="The app saves your progress locally. This data never leaves your device."
    thirdPartyLinks={[]}
  />
</Layout>

src/pages/index.astro (minimal skeleton)

---
import Layout from '../layouts/Layout.astro';
import ContactForm from '@guides/ui/ContactForm.astro';
import DownloadSection from '@guides/ui/DownloadSection.astro';

const title = 'My New Site – Tagline here';
const description = 'Short meta description.';
const canonical = 'https://my-new-site.pages.dev/';
---
<Layout {title} {description} {canonical}>
  <main>
    <section class="hero">
      <div class="hero-content">
        <h1>My New Site</h1>
        <h2>Subtitle or tagline</h2>
        <p>Hero paragraph copy.</p>
        <a href="/app/" class="cta-button">Open the App</a>
      </div>
    </section>

    <DownloadSection
      heading="Get the Android App"
      downloadUrl="https://github.com/you/your-app/releases"
      downloadAriaLabel="Download My App APK"
    />

    <ContactForm />
  </main>
</Layout>
6

Register in the workspace

pnpm-workspace.yaml

Add your slug to the packages list:

packages:
  - 'mario-odyssey-guide'
  - 'my-new-site'       # add your slug here
  - 'packages/*'

Root package.json — dev script

{
  "scripts": {
    "dev:my-site": "pnpm --filter my-new-site dev"
  }
}

Then install to link workspace dependencies:

pnpm install

Start the dev server:

pnpm dev:my-site
7

Deploy to Cloudflare Pages

Create the Pages project

  1. Go to Cloudflare Dashboard → Workers & Pages.
  2. Click Create → Pages → Connect to Git.
  3. Select your GitHub repository (android-game-guides).
  4. Set the build settings:
    Build command:      pnpm --filter my-new-site build
    Build output dir:   my-new-site/dist
  5. Click Save and Deploy.

Configure build watch paths

By default, Cloudflare Pages rebuilds on every push to the repo. Watch paths limit that to commits which actually affect your site or the shared packages.

  1. Open Workers & Pages → my-new-site → Settings → Build → Build watch paths.
  2. Under Include paths, add:
    my-new-site/*
    packages/*
  3. Leave Exclude paths blank.
  4. Click Save.

Why this matters: Without watch paths, pushing to any other site would also trigger a rebuild of yours. With watch paths, your Pages project only rebuilds when your folder or packages/ changes.

Custom domain (optional)

In Pages → my-new-site → Custom domains, add your domain and follow the DNS instructions. Once connected, update site in astro.config.mjs to the real URL so sitemaps and canonical tags are correct.

Environment variables

If you wire up an email provider later, add secrets under Pages → my-new-site → Settings → Environment variables. They're available to Pages Functions at runtime.

8

Update README

Add rows to both tables in README.md at the repo root:

## Sites
| Folder          | CF Pages project | Live URL                       |
| --------------- | ---------------- | ------------------------------ |
| `my-new-site` | `my-new-site`  | https://my-new-site.pages.dev  |

## Cloudflare Pages build watch paths
| Pages project   | Include paths                 | Exclude paths |
| --------------- | ----------------------------- | ------------- |
| `my-new-site` | `my-new-site/*, packages/*` | leave blank   |