03. Routing, pagine ed endpoint
← 02 - Progetto, componenti e stili · Indice · successivo → 04 - Contenuti - Markdown, MDX e content collections
3.1 File-based routing
Nessun file di configurazione delle rotte. Il filesystem è la configurazione.
src/pages/index.astro → /
src/pages/chi-sono.astro → /chi-sono
src/pages/guida/index.astro → /guida
src/pages/guida/setup.md → /guida/setup
src/pages/rss.xml.ts → /rss.xml
Estensioni valide in src/pages/: .astro, .md, .mdx (con l’integrazione),
.html, .js/.ts (endpoint).
Escludere un file dal routing: prefisso _. src/pages/_bozza.astro e tutta
la cartella src/pages/_parti/ non generano rotte né finiscono in dist/. Comodo
per tenere componenti accanto alle pagine che li usano.
Pagine speciali:
src/pages/404.astro→ genera404.html.src/pages/500.astro→ solo per rotte on-demand. Riceve una properror. Attenzione a non stampare lo stack in produzione.
---
// src/pages/500.astro
interface Props { error: unknown }
const { error } = Astro.props;
---
<p>{import.meta.env.DEV && error instanceof Error ? error.message : 'Errore interno'}</p>📖 Routing
3.2 Rotte dinamiche
src/pages/blog/[slug].astro → /blog/qualcosa
src/pages/[lang]/[versione].astro → /it/v1
src/pages/docs/[...path].astro → /docs/a, /docs/a/b/c, e anche /docs
[...rest] cattura più segmenti, / inclusi, e può anche essere vuoto.
Quando serve [...rest] e non [slug]: quando gli identificatori contengono
uno slash. È esattamente il caso delle content collections con file in
sottocartelle (avanzato/annidato è un id valido). Se usi [id] semplice, le
pagine annidate semplicemente non vengono generate e ti chiedi perché mancano.
Ordine di priorità
Quando due pattern potrebbero matchare lo stesso URL, vince in ordine:
- rotte riservate di Astro (
_astro/,_actions/,_server_islands/) - rotte con più segmenti
- rotte statiche > rotte dinamiche
- parametro nominato
[x]> rest[...x] - rotte dinamiche prerenderizzate > rotte dinamiche on-demand
- endpoint > pagine
- rotte da file > redirect
Quindi pages/posts/crea.astro vince su pages/posts/[id].astro per /posts/crea.
3.3 getStaticPaths()
Su una rotta dinamica prerenderizzata, Astro deve sapere a build time quali
URL generare. Glielo dici con getStaticPaths().
---
// src/pages/blog/[slug].astro
import type { GetStaticPaths } from 'astro';
export const getStaticPaths: GetStaticPaths = async () => {
const risposta = await fetch('https://api.example.com/post');
const post = await risposta.json();
return post.map((p) => ({
params: { slug: p.slug }, // finisce nell'URL → solo stringhe
props: { post: p }, // NON finisce nell'URL → qualunque tipo
}));
};
const { slug } = Astro.params;
const { post } = Astro.props;
---
<h1>{post.titolo}</h1>Regole che contano:
paramsaccetta solo stringhe (oundefinedper un rest vuoto). Dalla v6 passare un numero è un errore, non più una conversione silenziosa.propsè il canale per i dati: usalo, così non rifai il fetch nel template.- La funzione gira in uno scope isolato: può usare solo gli import del file, non variabili definite altrove nel frontmatter.
- L’oggetto
Astroè deprecato qui dentro (dalla v6): usaimport.meta.env.SITEal posto diAstro.site. - I
paramsnon vengono più decodificati automaticamente (dalla v5): se hai caratteri percent-encoded,decodeURI()a mano.
Su una rotta on-demand getStaticPaths() non va usata: la rotta risponde a
qualunque URL che matcha il pattern, e i dati li recuperi nel frontmatter.
---
export const prerender = false;
const { slug } = Astro.params;
const post = await trovaPost(slug);
if (!post) return Astro.redirect('/404');
---3.4 Paginazione
paginate() arriva come argomento delle opzioni di getStaticPaths().
---
// src/pages/articoli/[page].astro ✅ verificato su Astro 7.2.3
import type { GetStaticPathsOptions } from 'astro';
import { getCollection } from 'astro:content';
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
const articoli = await getCollection('blog');
return paginate(articoli, { pageSize: 10 });
}
const { page } = Astro.props;
---
<ul>
{page.data.map((a) => <li><a href={`/blog/${a.id}/`}>{a.data.title}</a></li>)}
</ul>
<nav>
{page.url.first && <a href={page.url.first}>« Prima</a>}
{page.url.prev && <a href={page.url.prev}>‹ Precedente</a>}
<span>{page.currentPage} / {page.lastPage}</span>
{page.url.next && <a href={page.url.next}>Successiva ›</a>}
{page.url.last && <a href={page.url.last}>Ultima »</a>}
</nav>Nome file: [page].astro genera /articoli/1, /articoli/2, …
[...page].astro genera /articoli, /articoli/2, … (la prima pagina senza numero:
di solito è quello che vuoi).
L’oggetto page contiene: data, start, end, total, currentPage, size,
lastPage, url.{current,prev,next,first,last}.
Cambiato da Astro 4
page.url.*include già ilbaseconfigurato. Se in un vecchio progetto prefissaviimport.meta.env.BASE_URLa mano, ora ottieni un doppio prefisso.
Novità 7.1: format per personalizzare gli URL generati:
return paginate(dati, { pageSize: 10, format: (url) => `${url}.html` });Serve per gli host che servono solo URL con estensione. Non è documentato se la
trasformazione si propaghi anche a page.url.prev/next: se ti serve, verifica in
un progetto reale prima di appoggiarti al comportamento.
Paginazione annidata (per tag, per categoria): restituisci un array di
paginate(), uno per gruppo, passando params:
---
export const getStaticPaths = (({ paginate }) => {
const tags = ['astro', 'css', 'note'];
return tags.flatMap((tag) =>
paginate(postConTag(tag), { params: { tag }, pageSize: 10 })
);
}) satisfies GetStaticPaths;
---Con src/pages/[tag]/[page].astro ottieni /astro/1, /css/1, …
3.5 Endpoint
Un file .ts/.js in src/pages/ che esporta funzioni con il nome del metodo HTTP.
L’estensione del file finale va nel nome: data.json.ts → /data.json.
// src/pages/api/ricerca.json.ts
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
export const GET: APIRoute = async () => {
const pagine = await getCollection('guide');
const indice = pagine.map((p) => ({
id: p.id,
titolo: p.data.title,
testo: p.body?.slice(0, 500) ?? '',
}));
return new Response(JSON.stringify(indice), {
headers: { 'Content-Type': 'application/json' },
});
};Questo è, per esempio, il modo di costruirsi un indice di ricerca statico per una guida: generi il JSON a build time e lo interroghi lato client con Fuse.js o MiniSearch. Nessun server, nessun servizio esterno.
Altri metodi: POST, PUT, DELETE, e ALL come catch-all. Se esporti GET ma
non HEAD, Astro gestisce HEAD da solo.
Gli endpoint sono prerenderizzati per default come tutto il resto: per farli
rispondere a runtime serve export const prerender = false e un adapter.
Cambiato in Astro 6
Un endpoint il cui nome finisce con un’estensione (
/sitemap.xml) è raggiungibile solo senza trailing slash, qualunque sia la configtrailingSlash.
3.6 Redirect e rewrite
Redirect statici, in configurazione:
export default defineConfig({
redirects: {
'/vecchio': '/nuovo',
'/blog/[...slug]': '/articoli/[...slug]',
'/altrove': { status: 302, destination: 'https://example.com' },
},
});In build statico senza adapter Astro emette un HTML con <meta http-equiv="refresh">
(gli status code non si applicano). Con un adapter scrive il file di configurazione
redirect dell’host, che è molto meglio.
Redirect a runtime: return Astro.redirect('/login'), deve essere ritornato
da una pagina, non da un componente figlio (a quel punto gli header sono già partiti).
Rewrite: serve il contenuto di un’altra rotta senza cambiare l’URL.
---
const { articolo } = Astro.params;
if (!esiste(articolo)) return Astro.rewrite('/404');
---Differenza da tenere a mente: rewrite() fa ripartire un ciclo di rendering
completo (rieseguendo il middleware); next(url) dentro il middleware riscrive
in loco senza rieseguirlo.
3.7 Internazionalizzazione
export default defineConfig({
i18n: {
locales: ['it', 'en'],
defaultLocale: 'it',
routing: {
prefixDefaultLocale: false, // /chi-sono e /en/chi-sono
},
fallback: { en: 'it' },
},
});Le cartelle in src/pages/ devono chiamarsi esattamente come i locale.
Con prefixDefaultLocale: false la lingua di default sta nella root
(src/pages/chi-sono.astro) e le altre in sottocartelle (src/pages/en/chi-sono.astro).
Helper da astro:i18n: getRelativeLocaleUrl(), getAbsoluteLocaleUrl(),
getRelativeLocaleUrlList(), getPathByLocale(), getLocaleByPath().
---
import { getRelativeLocaleUrl } from 'astro:i18n';
---
<a href={getRelativeLocaleUrl('en', 'chi-sono')}>English</a>Utili nel render context: Astro.currentLocale (funziona anche in statico),
Astro.preferredLocale e Astro.preferredLocaleList (solo on-demand, leggono Accept-Language).
Cambiato in Astro 6
i18n.routing.redirectToDefaultLocaleha defaultfalse(prima eratrue) e si può usare solo conprefixDefaultLocale: true. La combinazione vecchia poteva generare loop di redirect infiniti.
3.8 L’oggetto Astro. le proprietà che userai davvero
| Proprietà | A cosa serve |
|---|---|
Astro.props | le props del componente / da getStaticPaths() |
Astro.params | i segmenti dinamici dell’URL |
Astro.url | URL della richiesta corrente |
Astro.site | l’URL dal campo site della config |
Astro.redirect() / Astro.rewrite() | vedi 3.6 |
Astro.currentLocale | lingua dedotta dall’URL |
Astro.routePattern | il pattern della rotta, es. /blog/[slug] |
Astro.isPrerendered | utile nel middleware |
Astro.cookies, Astro.request, Astro.response, Astro.session | solo on-demand → modulo 6 |
Esercizi
- Crea
src/pages/guida/[...id].astroe genera tre pagine, di cui una in una sottocartella. Poi cambia[...id]in[id]e osserva quale sparisce: è la dimostrazione pratica del punto 3.2. - Aggiungi la paginazione a una lista di 25 elementi finti, con
pageSize: 10e navigazione prima/precedente/successiva/ultima. - Scrivi un endpoint
/indice.jsonche espone titolo e id di tutte le tue pagine. Aprilo nel browser dopoastro build && astro preview. - Aggiungi una 404 personalizzata e verifica che
astro previewla serva.