Docs/Tutorial
// tutorial
Creare una pagina statica
Una pagina statica è una pagina che non dipende dall'utente: chi siamo, una guida, i termini di servizio. Niente login, niente query al database, niente "use client". In ZeroToShip ne hai già due, vere e funzionanti: app/tos/page.tsx e app/privacy-policy/page.tsx. Il modo più veloce di farne una nuova è copiare una di quelle.
1. Cartella = URL
Con l'App Router il percorso della cartella dentro app/ diventa l'URL, e il file si chiama sempre page.tsx. Vuoi /chi-siamo? Crea app/chi-siamo/page.tsx. Non devi registrare la rotta da nessuna parte.
mkdir -p app/chi-siamo
touch app/chi-siamo/page.tsx2. La pagina minima
Nessuna direttiva in cima al file: su Next 15 un componente è un server component di default, e per una pagina statica va benissimo. Esporti una funzione di default, restituisci JSX, fine.
export default function ChiSiamo() {
return (
<main
className="min-h-screen"
style={{ background: "var(--cream)", color: "var(--ink)" }}
>
<div className="max-w-[720px] mx-auto px-6 md:px-8 py-10 md:py-16">
<h1 className="headline-serif text-5xl md:text-6xl mb-4">
Chi <span className="squiggle">siamo</span>
</h1>
<div className="divider-drawn mb-10" />
<p className="leading-relaxed">
Due paragrafi su di te e sul motivo per cui esiste il prodotto.
</p>
</div>
</main>
);
}--cream e --ink sono i token colore del tema, .headline-serif, .squiggle e .divider-drawn sono utility, tutti definiti in app/globals.css. Sono esattamente gli stessi che usano /tos e /privacy-policy: se li riusi, la pagina nuova sembra parte del sito senza che tu scriva un pixel di CSS.
3. Aggiungi i tag SEO
Il layout root (app/layout.tsx) chiama già getSEOTags() senza argomenti, quindi ogni pagina eredita title e description da config.appName e config.appDescription. Per una pagina vera conviene sovrascriverli esportando un metadata tuo:
import { getSEOTags } from "@/libs/seo";
import config from "@/config";
export const metadata = getSEOTags({
title: `Chi siamo | ${config.appName}`,
description: "La storia dietro il progetto, in due paragrafi.",
canonicalUrlRelative: "/chi-siamo",
});getSEOTags (in libs/seo.tsx) accetta i campi standard di Metadata di Next — title, description, keywords, openGraph — più due extra suoi: canonicalUrlRelative, che diventa il canonical della pagina, ed extraTags, che ti lascia spalmare qualsiasi altra chiave nel metadata. Il metadataBase lo calcola da solo: http://localhost:3000/ in sviluppo, il tuo config.domainName in produzione — così anche gli URL relativi delle immagini Open Graph diventano assoluti. Il dettaglio è in SEO e sitemap.
4. Il modello: la pagina dei Termini
Quando la pagina è un muro di testo, app/tos/page.tsx è la struttura da copiare: link “Back” in alto, headline serif, divider disegnato, e il contenuto dentro un <pre> con whitespace-pre-wrap e font-sans. Così il testo va a capo da solo e mantiene i paragrafi senza che tu debba spezzarlo in cinquanta <p>.
import Link from "next/link";
import { getSEOTags } from "@/libs/seo";
import config from "@/config";
export const metadata = getSEOTags({
title: `Terms and Conditions | ${config.appName}`,
canonicalUrlRelative: "/tos",
});
const TOS = () => {
return (
<main
className="min-h-screen"
style={{ background: "var(--cream)", color: "var(--ink)" }}
>
<div className="max-w-[720px] mx-auto px-6 md:px-8 py-10 md:py-16">
<Link href="/" className="btn-brutal inline-flex items-center gap-2 text-sm mb-10">
Back
</Link>
<h1 className="headline-serif text-5xl md:text-6xl mb-4">
Terms & <span className="squiggle">Conditions</span>
</h1>
<div className="divider-drawn mb-10" />
<pre
className="leading-relaxed whitespace-pre-wrap font-sans text-base"
style={{ color: "var(--ink)" }}
>
{`Last Updated: September 26, 2023
Welcome to ZeroToShip!
...`}
</pre>
</div>
</main>
);
};
export default TOS;app/privacy-policy/page.tsx è la gemella identica, con canonicalUrlRelative: "/privacy-policy". Attenzione a una cosa sola: il testo sta dentro un template literal, quindi se ci incolli dentro un backtick o la sequenza dollaro-graffa il build si rompe. Toglili, o mettici davanti un backslash.
5. Linkala dal Footer
Una pagina che nessuno raggiunge non serve a niente. components/Footer.tsx ha due colonne di link, LINK e LEGALE. Aggiungi il tuo <Link> nella colonna giusta:
<Link href="/#pricing" className="link link-hover">
Prezzi
</Link>
<Link href="/blog" className="link link-hover">
Blog
</Link>
<Link href="/chi-siamo" className="link link-hover">
Chi siamo
</Link>Nella colonna LEGALE ci sono già /tos e /privacy-policy: lasciali lì, Stripe vuole che siano raggiungibili prima di approvare un account.
6. Il sitemap si aggiorna da solo
Non devi registrare la pagina da nessuna parte. Lo script postbuild in package.json lancia next-sitemap dopo ogni build, che scansiona le rotte generate e produce sitemap.xml e robots.txt. La configurazione è in next-sitemap.config.js: se una pagina non deve finire in indice, aggiungila all'array exclude.
module.exports = {
siteUrl: process.env.SITE_URL || "https://zerotoship.com",
generateRobotsTxt: true,
exclude: ["/twitter-image.*", "/opengraph-image.*", "/icon.*"],
};