An Astro starter for small-business marketing sites. The same skeleton I use to ship £200 client sites in 7 days.
Static-first, zero-JS by default, near-perfect Core Web Vitals on cheap hosting. Every customer-facing string lives in one config file, so a new site is a 30-minute job: edit src/site.config.ts, set a domain in astro.config.mjs, and ship.
Built by @sansware (Jordan Gilbert, bio).
- Home page — config-driven hero, services grid, "why us", "how it works", service areas, blog teaser, contact CTA.
- About page — story, values, "at a glance" sidebar.
- Contact page — phone, email, address, opening hours, Google Maps deep-link, enquiry form (Formspree-compatible).
- Blog — Markdown content collection with index and dynamic post pages; JSON-LD
BlogPostingschema baked in. - SEO — canonical URLs, OpenGraph, Twitter card,
LocalBusinessJSON-LD, sitemap, robots.txt. - Accessibility — skip link, focus rings, semantic landmarks, ARIA-correct mobile menu, reduced-motion fallback.
- Design system — one CSS file. Brand colour is injected per-site via CSS custom properties — no rebuild required.
- Mobile-first responsive — full-screen mobile menu, accordion mega-nav on small screens, three breakpoints.
| Layer | Choice | Why |
|---|---|---|
| Framework | Astro 5 | Zero JS by default. Ships HTML; hydrates only what you tell it to. |
| Hosting | Cloudflare Pages (or Vercel) | Global edge cache, free tier, automatic HTTPS. |
| Forms | Formspree (default) or any webhook | No backend needed. Swap the action URL in contact.astro. |
| Resend (optional) | Wire up in a serverless function if you need transactional email. | |
| CAPTCHA | Cloudflare Turnstile (optional) | Privacy-friendly, EU-hostable, no cookies. |
| Payments | Stripe Elements (optional) | Drop-in payment form. PCI scope minimised. |
I have shipped 200+ small-business sites on this skeleton. Some patterns hold up:
- Sub-second loads everywhere. Static HTML + edge cache + no client-side framework runtime means LCP under a second on a 4G phone in a field.
- Near-perfect Core Web Vitals out of the box. The default styles avoid layout shift; images are sized; fonts are preconnected.
- Zero JS by default. The only client-side script is the mobile menu, which is ~30 lines of vanilla JS. No React, no hydration, no waterfalls.
- Cloudflare edge cache. Free, global, fast. Pair with
cache-control: public, max-age=31536000, immutableon hashed assets. - One config file. Designers and copywriters can edit
src/site.config.tswithout touching templates. New site in 30 minutes. - Cheap to host. Cloudflare Pages free tier covers most small-business traffic. Vercel hobby tier likewise.
# 1. Scaffold a new project from this template
pnpm create astro@latest -- --template github:sansware/site-templates my-client-site
# 2. Install dependencies
cd my-client-site
pnpm install
# 3. Dev server
pnpm dev
# → http://localhost:4321
# 4. Build
pnpm build
# → dist/If you prefer npm or yarn, swap them in — they all work with Astro.
There are five files you need to touch. Everything else, leave alone.
This is the file. It has the business name, phone, email, address, hours, brand colour, navigation, hero copy, services, "why us" points, "how it works" steps, service areas, the about page, the contact page, and the CTA band footer. Edit it and the whole site updates.
The fields are typed, so your editor will tell you if you drop one.
export default defineConfig({
site: "https://your-client-domain.co.uk", // ← change this
integrations: [sitemap()],
// ...
});This drives the sitemap and every canonical URL. Get it right before deploying.
The build refuses to ship if
siteis stillhttps://example.com. You can either edit the default or passSITE=https://your-client-domain.co.uk pnpm buildin CI.
Replace the placeholder with a 64×64 SVG that matches the brand. Inline SVG is fastest; keep it under 1KB.
Find this line and replace your-form-id with the real Formspree ID (or swap the whole form to your own webhook):
<form ... action="https://formspree.io/f/your-form-id" method="POST" ...>Replace example.com with the real domain.
Four CSS custom properties drive the entire colour scheme:
brand: {
accent: "#2563eb", // primary buttons, links, accents
accentDark: "#1d4ed8", // hover state, dark fills
accentGlow: "#60a5fa", // light highlights on dark backgrounds
ink: "#0f172a", // headings, dark surfaces
}Base.astro reads these and injects them as inline CSS variables on <html>. The shared global.css references var(--accent) etc. — so the entire site re-themes from this one block.
The home page emits LocalBusiness schema by default. To switch — for example for a cafe, dental practice, or auto repair — change schemaType in site.config.ts to a more specific schema.org type (CafeOrCoffeeShop, Dentist, AutoRepair, etc.).
Blog posts emit BlogPosting schema automatically.
The .env.example lists the env vars used by optional integrations. Copy to .env, fill in the values you need, and they will be picked up by Astro's import.meta.env:
PUBLIC_FORM_ENDPOINT— alternate form endpoint (Web3Forms, your own webhook).PUBLIC_TURNSTILE_SITE_KEY— if you want CAPTCHA on the contact form.RESEND_API_KEY— server-only; only used in serverless functions.PUBLIC_STRIPE_PUBLISHABLE_KEY— if the site has Stripe Elements anywhere.
Never commit .env. The .gitignore already excludes it.
- Push your repo to GitHub.
- Cloudflare Dashboard → Workers & Pages → Create → Pages → Connect to Git.
- Build command:
pnpm build. Output directory:dist. - Add a custom domain.
- Push your repo to GitHub.
- Vercel → Add New → Project → Import.
- Framework preset: Astro. Build settings detected automatically.
- Add a custom domain.
Both work. Cloudflare Pages has the more generous free tier; Vercel has better preview deploys.
For EU-sovereign region-pinning, see the sovereignty checklist.
.
├── astro.config.mjs # Canonical URL + integrations
├── package.json
├── tsconfig.json
├── public/
│ ├── favicon.svg # Replace per client
│ └── robots.txt # Replace domain per client
└── src/
├── site.config.ts # ⭐ Every customer-facing string
├── content.config.ts # Blog collection schema
├── content/blog/ # Markdown posts
├── layouts/
│ └── Base.astro # Shared layout (Header + Footer + SEO)
├── components/
│ ├── Header.astro
│ ├── Footer.astro
│ ├── Hero.astro
│ └── Seo.astro
├── pages/
│ ├── index.astro # Home — service grid, why, how, areas, blog, contact
│ ├── about.astro
│ ├── contact.astro
│ └── blog/
│ ├── index.astro
│ └── [...slug].astro
└── styles/
└── global.css # Entire design system
MIT. Use it for client work, fork it, sell sites built on top of it — no attribution required (a link in the footer is appreciated but not demanded).
Built and maintained by Jordan Gilbert (@sansware) — founder of UK Web Marketing. Twenty-plus years building websites, since primary school in 2006. This is the same template I use day-to-day for production small-business sites across the £49–£695 client tier.
If you find a bug or want a feature, open an issue or PR. Security issues — see SECURITY.md.