ZeroToShip

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.

terminal
bash
mkdir -p app/chi-siamo
touch app/chi-siamo/page.tsx

2. 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.

app/chi-siamo/page.tsx
tsx
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:

app/chi-siamo/page.tsx
tsx
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>.

app/tos/page.tsx
tsx
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 &amp; <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:

components/Footer.tsx
tsx
<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.

next-sitemap.config.js
js
module.exports = {
  siteUrl: process.env.SITE_URL || "https://zerotoship.com",
  generateRobotsTxt: true,
  exclude: ["/twitter-image.*", "/opengraph-image.*", "/icon.*"],
};