Overview
Chapinismos uses Astro’s file-based routing system with dynamic parameters for multilingual support and word pages.
File-Based Routing
Astro automatically creates routes based on files in src/pages/:
src/pages/
├── index.astro → /
├── [lang]/
│ ├── index.astro → /es/, /en/
│ ├── buscar.astro → /es/buscar/, /en/buscar/
│ ├── indice.astro → /es/indice/, /en/indice/
│ └── palabras/
│ └── [slug].astro → /es/palabras/*, /en/palabras/*
Root Redirect Page
The root index.astro detects browser language and redirects.
---
// Disable prerendering for server-side language detection
export const prerender = false;
// Server-side language detection and redirect
const acceptLanguage = Astro.request.headers.get("Accept-Language") || "";
const lang = acceptLanguage.toLowerCase().includes("es") ? "es" : "en";
// Use 302 (temporary) redirect for language-based routing
return Astro.redirect(`/${lang}/`, 302);
---
This is the only page with prerender: false to enable server-side rendering for dynamic language detection.
Dynamic Language Routes
Pages under [lang]/ use the [lang] parameter to generate routes for both Spanish and English.
Static Path Generation
Use getStaticPaths() to define which language routes to generate:
---
export const prerender = true;
import Base from "../../layouts/Base.astro";
import { getCollection } from "astro:content";
import { useTranslations } from "../../utils/i18n";
export async function getStaticPaths() {
return [
{ params: { lang: "es" } },
{ params: { lang: "en" } }
];
}
const { lang } = Astro.params;
const t = useTranslations(lang);
const collectionName = lang === "es" ? "words-es" : "words-en";
const allWords = await getCollection(collectionName);
---
<Base title={t("home.title")} lang={lang}>
<!-- Page content -->
</Base>
Double Dynamic Routes
The word detail page uses both [lang] and [slug] parameters.
Path: src/pages/[lang]/palabras/[slug].astro
Generates routes like:
/es/palabras/chispa/
/en/palabras/chispa/
/es/palabras/chunche/
---
export const prerender = true;
import Base from "../../../layouts/Base.astro";
import { getCollection } from "astro:content";
import { useTranslations } from "../../../utils/i18n";
export async function getStaticPaths() {
const esWords = await getCollection("words-es");
const enWords = await getCollection("words-en");
const esPaths = esWords.map((entry) => ({
params: { lang: "es", slug: entry.slug },
props: { entry },
}));
const enPaths = enWords.map((entry) => ({
params: { lang: "en", slug: entry.slug },
props: { entry },
}));
return [...esPaths, ...enPaths];
}
const { lang } = Astro.params;
const { entry } = Astro.props;
const word = entry.data;
const t = useTranslations(lang);
---
<Base title={`${word.word} — ${t("word.page.title")}`} lang={lang}>
<article>
<h1>{word.word}</h1>
<p>{word.meaning}</p>
<!-- More content -->
</article>
</Base>
Page Types
Homepage ([lang]/index.astro)
Main landing page with hero, search, and featured words.
/es/ (Spanish)
/en/ (English)
- Fetches all words from content collection
- Filters featured words (or random selection)
- Passes to FeaturedWords component
Search Page ([lang]/buscar.astro)
Client-side search interface for finding words.
/es/buscar/ (Spanish)
/en/buscar/ (English)
- Loads all words as JSON
- Client-side filtering and rendering
- URL query parameter support (
?q=search)
Index Page ([lang]/indice.astro)
Alphabetical listing of all words.
/es/indice/ (Spanish)
/en/indice/ (English)
- Fetches and sorts all words alphabetically
- Groups by first letter
- Displays in grid layout
Word Detail Page ([lang]/palabras/[slug].astro)
Individual word definition with examples, synonyms, and related words.
/es/palabras/{slug}/ (Spanish)
/en/palabras/{slug}/ (English)
- Fetches specific word entry from content collection
- Renders markdown content if available
- Finds related words by category
- Generates SEO metadata
Accessing Route Parameters
In any page component, access params via Astro.params:
---
const { lang, slug } = Astro.params;
// Use params to fetch data
const collectionName = lang === "es" ? "words-es" : "words-en";
const entry = await getEntry(collectionName, slug);
---
URL Construction
Build URLs dynamically based on language:
---
const { lang } = Astro.params;
const prefix = lang === "en" ? "/en" : "/es";
---
<nav>
<a href={`${prefix}/`}>Home</a>
<a href={`${prefix}/buscar/`}>Search</a>
<a href={`${prefix}/indice/`}>Index</a>
</nav>
Prerendering Strategy
Static (Default)
Server (SSR)
Most pages use prerender: true for static site generation:---
export const prerender = true;
---
Benefits:
- Faster page loads
- Better SEO
- Lower hosting costs
Only the root redirect uses prerender: false:---
export const prerender = false;
---
Used for:
- Language detection
- Dynamic redirects
i18n Configuration
Astro’s built-in i18n is configured in astro.config.mjs:
export default defineConfig({
i18n: {
defaultLocale: "es",
locales: ["es", "en"],
routing: {
prefixDefaultLocale: true, // Forces /es/ and /en/ prefixes
redirectToDefaultLocale: false, // Allows custom redirect logic
},
},
});
Hreflang for SEO
Every page includes alternate language URLs for SEO:
---
const alternateUrls = {
es: "/es/",
en: "/en/",
xDefault: "/es/", // Default fallback
};
---
<Base alternateUrls={alternateUrls}>
<!-- Content -->
</Base>
The Base layout renders these as <link rel="alternate"> tags:
<link rel="alternate" hreflang="es" href="https://chapinismos.org/es/" />
<link rel="alternate" hreflang="en" href="https://chapinismos.org/en/" />
<link rel="alternate" hreflang="x-default" href="https://chapinismos.org/es/" />
Best Practices
Always validate language params
const validLangs = ["es", "en"];
if (!validLangs.includes(lang)) {
return Astro.redirect("/es/");
}
Use type-safe translations
import { useTranslations } from "../../utils/i18n";
const t = useTranslations(lang);
<h1>{t("home.title")}</h1>
Always use the language prefix:href={`/${lang}/palabras/${slug}/`}
Astro config uses trailingSlash: "always" for consistency:/es/buscar/ ✓
/es/buscar ✗
Next Steps
Components
Learn about reusable components
Layouts
Understand page layouts
Content Collections
Work with word data
Internationalization
Deep dive into i18n