Thu Jul 03
A guide to the project structure, commands, and i18n setup of the Astro Starter Template (Astro 6 + Tailwind v4 + TypeScript).
This is a starter template built on Astro 6 + Tailwind CSS v4 + TypeScript, with a blog, full-text search, a UI component library, and multi-language (i18n) support — all wired up out of the box.
Demo → https://dev-astro-three.vercel.app/ ↗
What’s included
| Category | Details |
|---|---|
| Framework | Astro 6 (output: 'static') |
| Styling | Tailwind CSS v4 (@import "tailwindcss" style) |
| Typing | TypeScript 6 (strict) |
| i18n | ja / en (extendable to any number of locales) |
| Content | Markdown / MDX blog, locale-scoped folders |
| Search | Static full-text search via Pagefind |
| Slider | Embla Carousel with AutoScroll plugin |
| Images | Astro Image + sharp for optimization |
| UI components | Tabs, Accordion, Modal, Lightbox, Dropdown, Hamburger, etc. |
| SEO | OGP, Twitter Card, hreflang, locale-aware sitemap |
| AI guidelines | CLAUDE.md and .cursor/rules/ bundled |
Quick start
# 1. Install dependencies
npm install
# 2. Start the dev server (http://localhost:4321)
npm run dev
# 3. Production build
npm run build
# 4. Preview the production build
npm run preview
Note: Pagefind (search) does not work in dev mode. Run
npm run buildfirst.
Project structure
src/
├── assets/ # Static assets, optimized by Astro
├── components/ # Reusable UI components
├── content/blog/ # Blog entries ({locale}/{slug}.{md,mdx})
│ ├── en/ # Default locale (English) content
│ └── ja/ # Japanese translations
├── i18n/ # i18n config, dictionary, helpers
├── icons/ # Local icons for astro-icon
├── layouts/ # Page layouts
├── pages/ # File-based routing
│ ├── ja/ # Japanese static pages (about, index, library, search, etc.)
│ └── [lang]/ # Dynamic routes for non-default locales (blog, tags, etc.)
├── scripts/ # Client-side scripts
├── styles/ # Tailwind v4 + global CSS
├── types/ # Shared TypeScript types
└── env.ts # BASE_URL constant
Production site URL (site)
Set the production URL in astro.config.mjs so that absolute URLs are generated correctly:
export default defineConfig({
site: 'https://your-production-domain.com', // ← change to your real URL
// ...
});
This value is used by:
@astrojs/sitemap→ absolute URLs insitemap-0.xmlMetaTags.astro→ OGP / Twitterog:url,og:image, canonical, hreflang<link rel="alternate">Breadcrumbs.astro→ JSON-LDitemfield
If you forget to update this, the demo URL (https://dev-astro-three.vercel.app) ends up in your production HTML, breaking SEO and social sharing.
BASE_URL(subdirectory prefix) is a separate setting — see the “Environment BASE_URL” section below.
Using i18n
Displaying translated strings
---
import { getLocaleFromUrl, useTranslations } from '../i18n/utils';
const locale = getLocaleFromUrl(Astro.url);
const t = useTranslations(locale);
---
<h1>{t('nav.home')}</h1>
Add new translation keys to every locale block in src/i18n/ui.ts — TypeScript will flag any missing keys.
Locale-aware URLs
import { BASE_URL } from '../env';
import { localizePath } from '../i18n/utils';
const href = `${BASE_URL}${localizePath('/blog/post-1/', locale).slice(1)}`;
// en: "/blog/post-1/" ja: "/ja/blog/post-1/"
Adding a new locale
- Add the code to
locales,localeLabels, andlocaleHtmlLanginsrc/i18n/config.ts - Add a new dictionary block to
src/i18n/ui.ts(TypeScript enforces all keys) - Create
src/pages/{locale}/and add translated copies ofindex.astro,about.astro,library.astro, andsearch.astro(usesrc/pages/ja/as a reference) - Create
src/content/blog/{locale}/and add posts as needed (optional) - Add the locale to
astro.config.mjs(i18n.localesand thesitemap()call)
Static pages (step 3) follow the official Astro localized-folders pattern — each locale has its own per-page file. Everything else wires up automatically:
- All
[lang]/dynamic routes (blog, tags) - Language switcher entry for the new locale
hreflang<link>tags and sitemap alternates- Locale-scoped search (Pagefind filter)
- The
<html lang>attribute on every page
Pages without a translation
Blog posts can be translated selectively. If en/post-1.md exists but ja/post-1.md doesn’t, the language switcher on /blog/post-1/ shows Japanese as disabled (not clickable).
Search is locale-scoped
Search results are limited to the current locale. /search/?q=astro searches English pages; /ja/search/?q=astro searches Japanese pages.
How it works:
Layout.astroaddsdata-pagefind-filter="locale:en"(orja) to<main>- The search page calls
pagefind.search(query, { filters: { locale } }) SearchFormposts to the locale-aware action URL vialocalizePath('/search/', locale)
This wires up automatically when you add a new locale.
Where to customize
| What you want to change | Edit |
|---|---|
| Site title / OGP | src/i18n/ui.ts, astro.config.mjs (site) |
| Colors / fonts | src/styles/global.css (Tailwind v4 @theme) |
| Menu items | src/components/Navigation.astro |
| Header / Footer | src/components/Header.astro / Footer.astro |
| Top page | src/pages/index.astro (EN), src/pages/ja/index.astro (JA) |
| Blog posts | src/content/blog/{locale}/*.md |
| New components | src/components/ |
Environment BASE_URL
When deploying to a subdirectory, pass ASTRO_BASE at build time:
ASTRO_BASE=/subdir/ npm run build
src/env.ts exports a BASE_URL constant that you should use in internal links:
<a href={`${BASE_URL}${localizePath('/about/', locale).slice(1)}`}>About</a>
AI assistant rules
This repo ships with CLAUDE.md at the root and .cursor/rules/ for Cursor. They define naming conventions, i18n usage, and patterns to avoid — so Claude Code and Cursor stay consistent with the rest of the codebase. See CLAUDE.md for the full rules.
Deployment
Drop the dist/ folder onto any static host (Vercel, Netlify, GitHub Pages, Cloudflare Pages, S3, etc.). No Node runtime needed at runtime — this is a fully static site.
License
MIT. Note that redistribution or resale of this template (modified or not) as a competing product requires the author’s permission.
Created by: mooonycat ✨