A template for multilingual static websites using Astro 6, Sanity 5, and TypeScript.
This README is written to be useful both for:
- First-time users setting up the template.
- Returning users who need a quick re-onboarding months later.
Use this exact sequence:
git pull
npm install
cp .env.example .env # only if .env is missing
npm run sanity:codegen
npm run sanity:dev # terminal 1 — Studio at http://localhost:3333
npm run dev # terminal 2 — Site at http://localhost:4321Then confirm:
- Site opens at http://localhost:4321
- Content appears in both
enandnbwhere translations exist
For content editing, run Studio in a separate terminal:
npm run sanity:devStudio opens at http://localhost:3333 (or use the hosted Sanity Studio at manage.sanity.io).
- Astro static site output (Studio runs separately, not embedded)
- Locale-aware routing (
endefault,nbsecondary) - Localized Sanity fields via
sanity-plugin-internationalized-array - Centralized, typed GROQ queries in
src/sanity/lib/queries.ts - Prebuilt schemas:
siteSettings,navigation,footer,landing,page,post,category,author, andblockContent - Tailwind CSS + shadcn UI setup
Install before starting:
- Node.js 20+
- npm 10+ (bundled with modern Node)
- Sanity account (free tier is fine)
- Git
On GitHub:
- Click Use this template
- Create your new repository
- Clone it locally
git clone <your-new-repo-url> my-site
cd my-sitenpm installIf you need a new one:
npm run sanity:login
npm run sanity:create-project -- --display-name "My Site"Save the project ID and dataset name (production is typical).
cp .env.example .envSet values:
PUBLIC_SANITY_PROJECT_ID="your-project-id"
PUBLIC_SANITY_DATASET="production"
PUBLIC_SITE_URL="http://localhost:4321"This template has two config entry points:
.env(used by Astro integration +sanity.cli.ts)sanity.config.ts(used by Studio config)
Open sanity.config.ts and set:
projectId: "your-project-id",
dataset: "production",If these values do not match .env, you may see content/schema mismatches.
npm run sanity:cors-add -- http://localhost:4321 --credentialsThis is required for the Astro dev server to query Sanity.
npm run sanity:codegenRun this any time you change:
- Sanity schemas
- Query selections in GROQ files
Start Studio in a second terminal:
npm run sanity:devStudio opens at http://localhost:3333. The layout expects singleton content — create these before running the Astro dev server:
| Document type | Why it matters | Minimum fields to fill |
|---|---|---|
Site Settings |
Global title + nav reference + SEO defaults | title (en, nb), primaryNav reference |
Navigation |
Main menu rendering | title, at least 1 items[] row with label + (href or internalRef) |
Footer |
Footer text/contact section | address (en, nb) and/or copyright |
Landing |
Homepage hero content | title (en, nb) and ctaBtnText (en, nb) |
Optional but recommended:
post,author, andcategoryto validate list/detail routespageto validate CMS-driven/{locale}/[page]routes
Publish all documents in Studio, then move on.
npm run devOpen http://localhost:4321 — content should appear in both en and nb where translations exist.
When actively building:
- Pull latest code and install deps after lockfile changes:
git pull npm install
- Start both servers (separate terminals):
npm run sanity:dev # terminal 1 — Studio at http://localhost:3333 npm run dev # terminal 2 — Astro at http://localhost:4321
- If you changed schema/query types:
npm run sanity:codegen
- Before deploying:
npm run build
- Default locale is
en - Secondary locale is
nb - A localized page/post appears for a locale only when that locale has content for required localized fields
That means "missing page in one locale" is often data-related, not routing-related.
For example, add fr in all three places:
astro.config.mjs→i18n.localessanity.config.ts→internationalizedArray({ languages: [...] })src/sanity/lib/locale.ts→LOCALES
Then run:
npm run sanity:codegenRestart npm run dev and add translations in Studio for existing documents.
| Goal | File(s) |
|---|---|
| Change locales/default locale helpers | src/sanity/lib/locale.ts |
| Change Astro i18n routing | astro.config.mjs |
| Change Studio project/language plugin config | sanity.config.ts |
| Add/edit document schemas | src/sanity/schemaTypes/* |
| Change Studio sidebar organization | src/sanity/structure.ts |
| Add or adjust data fetching | src/sanity/lib/queries.ts |
| Update layouts/components | src/layouts/*, src/components/* |
| Update routes/pages | src/pages/* |
| Command | Purpose |
|---|---|
npm run dev |
Run Astro dev server at http://localhost:4321 |
npm run build |
Build static production output to dist/ |
npm run preview |
Preview production build locally |
npm run sanity:dev |
Run Sanity Studio directly |
npm run sanity:codegen |
Extract schema and generate src/sanity/sanity.types.ts |
npm run sanity:cors-add -- <origin> --credentials |
Allow frontend origin in Sanity CORS |
npm run sanity:dataset-list |
Show datasets in current Sanity project |
Before production deploy:
- Set production environment variables
- Ensure
PUBLIC_SITE_URLis your real domain - Add production CORS origin:
npm run sanity:cors-add -- https://your-site.example --credentials
- Deploy Sanity Studio (hosted, for browser-based content editing):
After this, use the hosted Studio URL (e.g.
npm run sanity:deploy
https://your-project.sanity.studio) for all content changes instead of runningsanity:devlocally. - Build:
npm run build
- Deploy
dist/to your host (or add an Astro adapter for SSR targets)
| Symptom | Likely cause | Fix |
|---|---|---|
Studio at :3333 fails to load content |
Missing CORS origin | npm run sanity:cors-add -- http://localhost:4321 --credentials |
| Studio shows unexpected project content | sanity.config.ts projectId/dataset mismatch |
Align sanity.config.ts with .env values |
sanity:codegen fails or types are stale |
Schema extraction/typegen not rerun | npm run sanity:codegen |
Some content appears in en but not nb |
Missing translation values in localized fields | Add missing locale values in Studio |
| Build succeeds but page content is empty | Required singleton docs not created | Create Site Settings, Navigation, Footer, Landing |