Own your foundation. A production-ready template for agencies and professional services—fast, secure, and designed to be extended.
- Beautiful by default — Warm, elegant design with Cormorant Garamond serif + Source Sans 3
- Self-hosted fonts — Built-in Fonts API downloads and serves fonts from your domain (no Google Fonts requests, better privacy and performance)
- One-pager structure — Hero, Services, About, Testimonial, Process, and Contact sections
- Blog ready — Content collections with Markdown, RSS feeds, and related posts
- SEO optimized — Meta tags, Open Graph, Twitter cards, JSON-LD schema, sitemaps, and robots.txt
- Security headers — CSP, X-Frame-Options, Referrer-Policy configured out of the box
- Analytics ready — Optional PostHog integration with event tracking
- Cloudflare native — Wrangler config for staging and production deployments
- Mobile-first — Responsive design tested across all breakpoints
- Bun — JavaScript runtime and package manager
- Node.js 22+ — Required by Astro 7
- just — Command runner; required for the deploy recipes, which apply safeguards a bare
wrangler deployskips
# Clone the repository
git clone https://github.com/socialtide/astro-template my-site
cd my-site
# Remove template git history
rm -rf .git && git init
# Install dependencies
bun install
# Start development server
bun devYour site is running at http://localhost:4321
Edit src/data/site.json:
{
"siteName": "Your Brand",
"siteUrl": "https://yourdomain.com",
"defaultTitle": "Your Brand | Growing Businesses with Clarity",
"defaultDescription": "Your compelling description...",
"contact": {
"email": "hello@yourdomain.com",
"phone": "+1 (234) 567-890"
}
}Edit src/data/navigation.json:
{
"logo": { "text": "Your Brand", "href": "/" },
"mainNav": [
{ "label": "Services", "href": "#services" },
{ "label": "About", "href": "#about" },
{ "label": "Blog", "href": "/blog/" }
],
"ctaButton": { "label": "Get in Touch", "href": "#contact" }
}Edit src/styles/global.css to change the color palette:
@theme {
/* Primary - Warm terracotta (or your brand color) */
--color-primary-500: oklch(0.58 0.14 35);
/* Accent - Sage green */
--color-accent-500: oklch(0.55 0.08 145);
/* Warm neutrals - Cream to charcoal */
--color-warm-50: oklch(0.985 0.008 85); /* Background */
--color-warm-900: oklch(0.18 0.015 55); /* Text */
}Tip: Use oklch.com to experiment with colors
Fonts are configured in astro.config.mjs using Astro's built-in Fonts API. They're downloaded at build time and served from your domain — no external requests to Google Fonts.
fonts: [
{
name: "Your Serif Font",
cssVariable: "--font-cormorant-garamond",
provider: fontProviders.google(),
},
// ...
],Then update the theme variables in src/styles/global.css to reference your new font:
@theme {
--font-family-serif: var(--font-your-serif-font);
}- Homepage sections — Edit
src/pages/index.astro - Images — Replace placeholders with your photography
- Blog posts — Add Markdown files to
src/content/blog/
├── src/
│ ├── components/
│ │ └── common/ # Header, Footer
│ ├── content/
│ │ └── blog/ # Blog posts (Markdown)
│ ├── content.config.ts # Content collection schemas + loaders
│ ├── data/ # Site & navigation config
│ ├── layouts/ # Page layouts with SEO
│ ├── pages/ # Routes
│ └── styles/ # Global CSS + Tailwind theme
├── public/ # Static assets
├── astro.config.mjs # Astro + fonts configuration
└── wrangler.toml # Cloudflare deployment config
Create Markdown files in src/content/blog/:
---
title: "Your Post Title"
description: "Brief description for previews and SEO"
author: "Your Name"
date: 2025-01-15
tags: ["business", "strategy"]
featured: true
readingTime: 5
---
Your content here...The contact form is wired to SocialTide Harbor by default. Set PUBLIC_SOCIALTIDE_CLIENT_ID, PUBLIC_SOCIALTIDE_API_BASE, and optionally PUBLIC_TURNSTILE_SITE_KEY to enable submissions. See docs/FORM_SETUP.md for:
- SocialTide Harbor setup (default)
- Google Sheets integration via Apps Script
- Custom API endpoints
- Cloudflare Turnstile spam protection
The template includes a privacy-friendly analytics pattern using data-* attributes. Add tracking to any element:
<!-- Track CTA clicks -->
<a href="/pricing" data-track-cta="Get Started" data-track-section="hero">
Get Started
</a>
<!-- Track form interactions (start, progress, submit, abandon) -->
<form data-track-form="contact">...</form>
<!-- Track content engagement (2s dwell time) -->
<section data-track-content="pricing" data-content-type="section">...</section>Works with PostHog out of the box, or adapt to your analytics provider. No cookies required for basic tracking.
Security headers are configured in public/_headers and applied automatically on Cloudflare:
- Content Security Policy — Restricts script/style sources, prevents XSS
- X-Frame-Options: DENY — Prevents clickjacking
- X-Content-Type-Options: nosniff — Prevents MIME sniffing
- Referrer-Policy — Controls referrer information
- Permissions-Policy — Disables unused browser APIs
Fonts are self-hosted at build time, so no external font domains are needed in the CSP. The policy is pre-configured for PostHog and Cloudflare analytics. Modify _headers if you add other third-party scripts.
The template includes preview and production environments out of the box. Deploys are manual — pushing to your default branch does not ship anything.
- Update
wrangler.tomlwith your project name and domains - Deploy:
bunx wrangler login
just preview # deploy a preview build
just preview-down # delete the preview when you are finished with it
just deploy # deploy to productionYour site is live on Cloudflare's global edge network (300+ locations).
Use the just recipes rather than calling wrangler deploy directly. They apply two safeguards that a bare deploy skips.
Previews are built unindexable. A preview is a complete public copy of your site, so just preview builds it with PUBLIC_SITE_ENV=preview, which emits noindex, nofollow in the page head, adds an X-Robots-Tag header covering every response, and drops the production sitemap from robots.txt. Crawling itself stays allowed, deliberately — a blanket Disallow: / would stop crawlers ever reading the noindex directive. Production never sets that variable and keeps its normal index directives.
Previews do not record analytics. The same PUBLIC_SITE_ENV=preview build leaves the PostHog snippet out entirely, so reviewing a preview, or submitting a test form on it, never lands in your production analytics. just preview also fails if the built output still contains the snippet. To check tracking end to end, use the production site.
Take previews down with just preview-down once you are done. A preview left running is a second copy of your site competing with it in search.
Deploys refuse to run without an analytics key. If PUBLIC_POSTHOG_KEY is missing, both just preview and just deploy stop before building, and a production build throws rather than shipping a site that silently records nothing. This matters most in a git worktree, which does not carry your .env.
Deploys also check the built site. After building, just deploy confirms the output contains the PostHog snippet and the key the build was given, and refuses to deploy otherwise. That catches a build that succeeds but drops tracking, such as one made with PUBLIC_SITE_ENV=preview left set in your shell or .env.
| Command | Description |
|---|---|
just install |
Install dependencies |
just dev |
Start dev server at localhost:4321 |
just build |
Build for production |
just check |
Build, then run tests |
just preview-local |
Serve the production build locally |
just preview |
Deploy an unindexable preview |
just preview-down |
Delete the preview |
just deploy |
Deploy to production |
Run just with no arguments to list every recipe. Each one wraps the equivalent bun or wrangler command.
- Astro 7 — Static site framework with built-in Fonts API and CSP
- Tailwind CSS v4 — Utility-first CSS with OKLCH colors
- TypeScript — Type safety
- Cloudflare Workers — Edge hosting
- PostHog — Analytics (optional)
MIT License — see LICENSE
Built by SocialTide — helping businesses build digital presences that work.
Want us to build your site? Get in touch for a free consultation.