Loading theme
~/saverio

Ottimizzazione SEO

9 feb 2025· agg. 21 feb 2026· 4 min
seositemaprobotsjson-ldgoogle-search-console

Ottimizzazione SEO

Cosa otterrai: una sitemap, robots.txt, URL canonical, dati strutturati JSON-LD e una checklist per Google Search Console. Più come evitare che Cloudflare blocchi Googlebot.

La tua app deve avere NEXT_PUBLIC_SITE_URL impostata in Coolify (es. https://your-domain.com).

Sitemap e robots.txt

Next.js le genera in automatico da app/sitemap.ts e app/robots.ts.

sitemap.ts

Includi solo gli URL finali, non i redirect. Per i capitoli di writing che redirigono alla prima pagina, elenca gli URL della pagina reale (es. /writing/01-chapter/00-introduction), non lo slug del capitolo da solo. Usa lastModified dal contenuto quando disponibile.

import type { MetadataRoute } from "next";
import { getBlogPosts, getWritingPosts } from "@/lib/content";

const baseUrl =
  process.env.NEXT_PUBLIC_SITE_URL || "https://your-domain.com";

export default function sitemap(): MetadataRoute.Sitemap {
  const posts = getBlogPosts();
  const writingPosts = getWritingPosts();

  const postUrls: MetadataRoute.Sitemap = posts.map((post) => ({
    url: `${baseUrl}/engineering/${post.slug}`,
    lastModified: post.meta.updated
      ? new Date(post.meta.updated)
      : post.meta.date
        ? new Date(post.meta.date)
        : new Date(),
    changeFrequency: "monthly" as const,
    priority: 0.8,
  }));

  const writingUrls: MetadataRoute.Sitemap = writingPosts
    .filter((post) => post.slug.includes("/"))
    .map((post) => ({
      url: `${baseUrl}/writing/${post.slug}`,
      lastModified: post.updated
        ? new Date(post.updated)
        : post.date
          ? new Date(post.date)
          : new Date(),
      changeFrequency: "weekly" as const,
      priority: 0.7,
    }));

  return [
    { url: baseUrl, lastModified: new Date(), changeFrequency: "weekly" as const, priority: 1 },
    { url: `${baseUrl}/engineering`, lastModified: new Date(), changeFrequency: "weekly" as const, priority: 0.9 },
    { url: `${baseUrl}/writing`, lastModified: new Date(), changeFrequency: "weekly" as const, priority: 0.9 },
    ...postUrls,
    ...writingUrls,
  ];
}

robots.ts

import type { MetadataRoute } from "next";

const baseUrl =
  process.env.NEXT_PUBLIC_SITE_URL || "https://your-domain.com";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: { userAgent: "*", allow: "/" },
    sitemap: `${baseUrl}/sitemap.xml`,
    host: baseUrl,
  };
}

Check: dopo il deploy, apri https://your-domain.com/sitemap.xml e https://your-domain.com/robots.txt. Devono caricarsi entrambi.


URL canonical e meta

Gli URL canonical dicono ai motori di ricerca qual è l'URL "principale" di una pagina, evitando i problemi di duplicate content.

Metadata di layout (app/layout.tsx)

export const metadata: Metadata = {
  metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || "https://your-domain.com"),
  title: { default: "Your Site", template: "%s | Your Site" },
  description: "Your description.",
  openGraph: { type: "website", locale: "en_US", siteName: "Your Site" },
  twitter: { card: "summary_large_image" },
  robots: { index: true, follow: true },
};

Metadata per-pagina (es. blog post)

return {
  title: post.meta.title,
  description: post.meta.description,
  alternates: { canonical: `${baseUrl}/engineering/${post.slug}` },
  openGraph: {
    title: post.meta.title,
    description: post.meta.description,
    type: "article",
    publishedTime: post.meta.date,
    modifiedTime: post.meta.updated,
    url: `${baseUrl}/engineering/${post.slug}`,
  },
  twitter: { card: "summary_large_image", title: post.meta.title, description: post.meta.description },
};

Dati strutturati JSON-LD

I dati strutturati aiutano Google a capire i tuoi contenuti e possono abilitare i rich result.

Schema WebSite (homepage)

const jsonLd = {
  "@context": "https://schema.org",
  "@type": "WebSite",
  name: "Your Site",
  description: "Your description.",
  url: baseUrl,
  publisher: { "@type": "Person", name: "Your Name" },
};

// In JSX:
<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>

Schema Article (blog post e pagine writing)

Usa la stessa struttura per i post engineering (/engineering/slug) e le pagine writing (/writing/chapter/page). Includi authors, keywords (dai tag), e openGraph.publishedTime / modifiedTime nei metadata.

const jsonLd = {
  "@context": "https://schema.org",
  "@type": "Article",
  headline: post.meta.title,
  description: post.meta.description ?? undefined,
  datePublished: post.meta.date ?? undefined,
  dateModified: post.meta.updated ?? post.meta.date ?? undefined,
  author: { "@type": "Person", name: "Your Name" },
  publisher: { "@type": "Organization", name: "Your Site" },
  url: canonicalUrl,
  mainEntityOfPage: { "@type": "WebPage", "@id": canonicalUrl },
};

Validare: validator.schema.org, incolla l'URL di una pagina per controllarla.


Google Search Console

Step 1: aggiungi la property

  1. Vai su search.google.com/search-console
  2. Add property → scegli Domain (non URL prefix)
  3. Inserisci your-domain.com
  4. Verifica via DNS: aggiungi il record TXT che Cloudflare mostra al DNS del tuo dominio (Cloudflare → DNS → Add record → TXT)

Step 2: invia la sitemap

  1. IndexingSitemaps
  2. Inserisci https://your-domain.com/sitemap.xml
  3. Click su Submit

Step 3: controlla lo stato

  • Status: Success: Google ha preso la sitemap. "Discovered pages" mostra quanti URL ha trovato.
  • Status: Couldn't fetch: Google non riesce a raggiungere la sitemap. Vedi la sezione Cloudflare qui sotto.
  • Temporary processing error: spesso si risolve in 24-48 ore. Prova Resubmit dal menu della sitemap.

URL Inspection: usa la barra di ricerca per ispezionare un URL. "Test live URL" controlla se Google riesce a prenderlo. "Request indexing" chiede a Google di crawlarlo.


Cloudflare e Googlebot

Se Search Console mostra "Couldn't fetch" sulla sitemap ma l'URL funziona dal browser, Cloudflare potrebbe star bloccando o sfidando Googlebot.

Security → Bots → Settings

  1. Bot Fight Mode: se è ON, può sfidare i bot. Assicurati che i bot verificati (incluso Googlebot) siano permessi, oppure disabilitalo temporaneamente per testare.
  2. Block AI bots: edita la regola e assicurati che Googlebot non sia nella lista di blocco. I crawler di training AI (GPTBot, ecc.) sono diversi da Googlebot.
  3. Managed robots.txt (AI Crawl Control): se Cloudflare gestisce il tuo robots.txt, può sovrascrivere quello dell'app. Controlla che il robots.txt servito includa Sitemap: https://your-domain.com/sitemap.xml. Se no, aggiungilo in Cloudflare oppure disabilita Managed robots.txt così la tua app Next.js serve il proprio.

Errore Content-Signal: il Managed robots.txt di Cloudflare aggiunge una riga non standard Content-Signal: search=yes, ai-train=no. Google e PageSpeed Insights la segnalano come "Unknown directive" e riportano "robots.txt non è valido". Fix: disabilita Managed robots.txt in AI Crawl Control → Overview. La tua app Next.js servirà allora un robots.txt valido con solo direttive standard.

X-Robots-Tag noindex: se Search Console riporta "Page is not indexed: Excluded by 'noindex' tag" con "'noindex' detected in 'X-Robots-Tag' http header", la direttiva arriva da Cloudflare, non dalla tua app. La tua app Next.js può mandare index, follow, ma Cloudflare lo può sovrascrivere. Controlla RulesTransform RulesModify Response Header per qualunque regola che imposti X-Robots-Tag: noindex. Controlla SecurityBotsCrawl Control (o AI Crawl Control) e disabilita le opzioni che aggiungono noindex. Verifica con curl -I -A "Googlebot" https://your-domain.com/ e conferma che la risposta abbia x-robots-tag: index, follow.

Verifica: apri https://your-domain.com/robots.txt e conferma che contenga la riga Sitemap e nessuna riga Content-Signal.


Tool di verifica

ToolURLUso
Google Search Consolesearch.google.com/search-consoleIndicizzazione, sitemap, Core Web Vitals
LighthouseChrome DevTools → F12 → tab LighthouseAudit SEO, performance, accessibilità
PageSpeed Insightspagespeed.web.devCore Web Vitals, performance mobile
Schema Validatorvalidator.schema.orgValidazione JSON-LD

Check rapido: esegui Lighthouse sulla tua homepage. La categoria SEO deve passare su document title, meta description, link crawlabili e robots.txt.


Checklist

  • NEXT_PUBLIC_SITE_URL impostato in Coolify
  • /sitemap.xml e /robots.txt ritornano 200
  • URL canonical su tutte le pagine
  • JSON-LD su homepage e pagine articolo
  • Property Google Search Console verificata
  • Sitemap inviata, stato Success
  • Cloudflare non blocca Googlebot
  • Score SEO Lighthouse accettabile