From faebc775c614c8212774b4e7b500437fba89b896 Mon Sep 17 00:00:00 2001 From: "Alfred (Manuels Agent)" Date: Mon, 6 Jul 2026 18:20:35 +0200 Subject: [PATCH] fix(seo): summary_large_image card + home og:image, drop title em-dash Social share cards rendered as the tiny bild-less "summary" thumbnail because twitter:card was never set and no og:image existed on the home page. Add a generated 1200x630 card (next/og) as the default og:image in the root layout and set twitter:card=summary_large_image. twitter.images is omitted so X falls back to og:image, letting destination pages keep their own /api/og/destination card without touching twitter. Also drop the em-dash from the SEO-visible title override in the marketing landing page and from meta.title/meta.ogTitle in all four locales (en/de/es/fr), per the no-em-dash copy rule. Co-Authored-By: Claude --- src/app/(marketing)/page.tsx | 2 +- src/app/api/og/route.tsx | 135 +++++++++++++++++++++++++++++++++++ src/app/layout.tsx | 18 +++++ src/messages/de.json | 6 +- src/messages/en.json | 6 +- src/messages/es.json | 8 +-- src/messages/fr.json | 8 +-- 7 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 src/app/api/og/route.tsx diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index c6336b3..2a1772a 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -29,7 +29,7 @@ const webAppJsonLd = { }; export const metadata: Metadata = { - title: "PackedPlaces.com — See How Crowded Travel Destinations Really Get", + title: "PackedPlaces.com: See How Crowded Travel Destinations Really Get", alternates: { canonical: "https://packedplaces.com" }, }; diff --git a/src/app/api/og/route.tsx b/src/app/api/og/route.tsx new file mode 100644 index 0000000..b56942a --- /dev/null +++ b/src/app/api/og/route.tsx @@ -0,0 +1,135 @@ +import { ImageResponse } from "next/og"; +import { crowdColor } from "@/lib/crowd-palette"; + +/** + * Default Open Graph card for the site (1200x630), used by the home page and + * any page that does not supply its own image. Lives under /api so it stays + * dynamic while the marketing pages remain static; the root layout references + * it via openGraph.images. Destination pages set their own card at + * /api/og/destination/[slug]. + */ + +export const runtime = "nodejs"; + +const SIZE = { width: 1200, height: 630 }; + +const FRAUNCES_URL = + "https://fonts.gstatic.com/s/fraunces/v38/6NVf8FyLNQOQZAnv9ZwNjucMHVn85Ni7emAe9lKqZTnbB-gzTK0K1ChJdt9vIVYX9G37lod_sPEKsxx664UJf1iVSv7W.ttf"; + +// A left-to-right crowd ramp (quiet to packed) built from the shared palette, +// so the card reads in the same visual language as the map and the charts. +const RAMP = Array.from({ length: 24 }, (_, i) => i / 23); + +let frauncesPromise: Promise | null = null; + +function loadFraunces(): Promise { + frauncesPromise ??= fetch(FRAUNCES_URL) + .then((res) => (res.ok ? res.arrayBuffer() : null)) + .catch(() => null); + return frauncesPromise; +} + +export async function GET() { + const fraunces = await loadFraunces(); + + return new ImageResponse( + ( +
+
+ 127 REGIONS · 700+ DESTINATIONS · 100+ COUNTRIES +
+ +
+
+ PackedPlaces +
+
+ See how crowded travel destinations really get, week by week. +
+
+ +
+
+ {RAMP.map((score, i) => ( +
+ ))} +
+
+
QUIET
+
PACKED
+
+
+ PACKEDPLACES.COM +
+
+
+ ), + { + ...SIZE, + headers: { + "Cache-Control": "public, max-age=86400", + }, + fonts: fraunces + ? [{ name: "Fraunces", data: fraunces, weight: 600 as const }] + : undefined, + }, + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index bc94ac1..b52049c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -48,6 +48,24 @@ export async function generateMetadata(): Promise { url: "https://packedplaces.com", siteName: "PackedPlaces.com", type: "website", + images: [ + { + // Generated 1200x630 card (next/og), resolved against metadataBase. + url: "/api/og", + width: 1200, + height: 630, + alt: t("ogTitle"), + }, + ], + }, + twitter: { + // Large-image card so shared links render a full-width preview instead of + // the tiny bild-less "summary" thumbnail. twitter.images is omitted on + // purpose: with it absent, X falls back to og:image, so destination pages + // that set their own og:image get the correct card without touching twitter. + card: "summary_large_image", + title: t("ogTitle"), + description: t("ogDescription"), }, // No hreflang alternates: the site serves every language from the same URL // (locale is negotiated, not path-prefixed), so per-language alternates would diff --git a/src/messages/de.json b/src/messages/de.json index 575483b..3225fcf 100644 --- a/src/messages/de.json +++ b/src/messages/de.json @@ -200,7 +200,7 @@ "contactTitle": "Kontakt", "contactText": "Bei Fragen zu dieser Datenschutzerklärung oder zur Ausübung Ihrer DSGVO-Rechte nutzen Sie bitte unser Kontaktformular.", "metaTitle": "Datenschutzerklärung — PackedPlaces.com", - "metaDescription": "Datenschutzerklärung von PackedPlaces.com — wie wir mit Ihren Daten, Cookies und Analysen umgehen." + "metaDescription": "Datenschutzerklärung von PackedPlaces.com: wie wir mit Ihren Daten, Cookies und Analysen umgehen." }, "contact": { "title": "Kontakt", @@ -465,9 +465,9 @@ "system": "System" }, "meta": { - "title": "PackedPlaces.com — Sehen Sie, wie voll Reiseziele wirklich werden", + "title": "PackedPlaces.com: Sehen Sie, wie voll Reiseziele wirklich werden", "description": "PackedPlaces.com visualisiert die Auslastung von über 700 Reisezielen weltweit, Woche für Woche, basierend auf Schulferien-Daten aus über 100 Ländern.", - "ogTitle": "PackedPlaces.com — Sehen Sie, wie voll Reiseziele wirklich werden", + "ogTitle": "PackedPlaces.com: Sehen Sie, wie voll Reiseziele wirklich werden", "ogDescription": "PackedPlaces.com visualisiert die Auslastung von über 700 Reisezielen weltweit, Woche für Woche, basierend auf Schulferien-Daten aus über 100 Ländern." }, "bestTime": { diff --git a/src/messages/en.json b/src/messages/en.json index 996dd9a..877b879 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -200,7 +200,7 @@ "contactTitle": "Contact Us", "contactText": "If you have questions about this privacy policy or wish to exercise your GDPR rights, please reach out via our contact form.", "metaTitle": "Privacy Policy — PackedPlaces.com", - "metaDescription": "Privacy policy for PackedPlaces.com — how we handle your data, cookies, and analytics." + "metaDescription": "Privacy policy for PackedPlaces.com: how we handle your data, cookies, and analytics." }, "contact": { "title": "Contact Us", @@ -465,9 +465,9 @@ "system": "System" }, "meta": { - "title": "PackedPlaces.com — See How Crowded Travel Destinations Really Get", + "title": "PackedPlaces.com: See How Crowded Travel Destinations Really Get", "description": "PackedPlaces.com visualizes tourist crowdedness for 700+ destinations worldwide, week by week, powered by school holiday data from 100+ countries.", - "ogTitle": "PackedPlaces.com — See How Crowded Travel Destinations Really Get", + "ogTitle": "PackedPlaces.com: See How Crowded Travel Destinations Really Get", "ogDescription": "PackedPlaces.com visualizes tourist crowdedness for 700+ destinations worldwide, week by week, powered by school holiday data from 100+ countries." }, "bestTime": { diff --git a/src/messages/es.json b/src/messages/es.json index 5ca7a44..5aed9fc 100644 --- a/src/messages/es.json +++ b/src/messages/es.json @@ -200,7 +200,7 @@ "contactTitle": "Contacto", "contactText": "Si tiene preguntas sobre esta política de privacidad o desea ejercer sus derechos RGPD, contáctenos a través de nuestro formulario de contacto.", "metaTitle": "Política de privacidad — PackedPlaces.com", - "metaDescription": "Política de privacidad de PackedPlaces.com — cómo tratamos sus datos, cookies y analíticas." + "metaDescription": "Política de privacidad de PackedPlaces.com: cómo tratamos sus datos, cookies y analíticas." }, "contact": { "title": "Contacto", @@ -223,7 +223,7 @@ "errorServer": "Algo salió mal. Por favor, inténtelo de nuevo más tarde.", "errorTurnstile": "La verificación captcha falló. Por favor, inténtelo de nuevo.", "metaTitle": "Contacto — PackedPlaces.com", - "metaDescription": "Póngase en contacto con el equipo de PackedPlaces.com — preguntas, comentarios y sugerencias son bienvenidos." + "metaDescription": "Póngase en contacto con el equipo de PackedPlaces.com: preguntas, comentarios y sugerencias son bienvenidos." }, "featurePages": { "shell": { @@ -465,9 +465,9 @@ "system": "Sistema" }, "meta": { - "title": "PackedPlaces.com — Descubra lo masificados que están realmente los destinos turísticos", + "title": "PackedPlaces.com: Descubra lo masificados que están realmente los destinos turísticos", "description": "PackedPlaces.com visualiza la masificación turística de más de 700 destinos en todo el mundo, semana a semana, basándose en datos de vacaciones escolares de más de 100 países.", - "ogTitle": "PackedPlaces.com — Descubra lo masificados que están realmente los destinos turísticos", + "ogTitle": "PackedPlaces.com: Descubra lo masificados que están realmente los destinos turísticos", "ogDescription": "PackedPlaces.com visualiza la masificación turística de más de 700 destinos en todo el mundo, semana a semana, basándose en datos de vacaciones escolares de más de 100 países." }, "bestTime": { diff --git a/src/messages/fr.json b/src/messages/fr.json index 66dd0cf..974d240 100644 --- a/src/messages/fr.json +++ b/src/messages/fr.json @@ -200,7 +200,7 @@ "contactTitle": "Nous contacter", "contactText": "Si vous avez des questions sur cette politique de confidentialité ou souhaitez exercer vos droits RGPD, contactez-nous via notre formulaire de contact.", "metaTitle": "Politique de confidentialité — PackedPlaces.com", - "metaDescription": "Politique de confidentialité de PackedPlaces.com — comment nous traitons vos données, cookies et analyses." + "metaDescription": "Politique de confidentialité de PackedPlaces.com: comment nous traitons vos données, cookies et analyses." }, "contact": { "title": "Contact", @@ -223,7 +223,7 @@ "errorServer": "Une erreur est survenue. Veuillez réessayer plus tard.", "errorTurnstile": "La vérification captcha a échoué. Veuillez réessayer.", "metaTitle": "Contact — PackedPlaces.com", - "metaDescription": "Contactez l'équipe de PackedPlaces.com — questions, retours et suggestions bienvenus." + "metaDescription": "Contactez l'équipe de PackedPlaces.com: questions, retours et suggestions bienvenus." }, "featurePages": { "shell": { @@ -465,9 +465,9 @@ "system": "Système" }, "meta": { - "title": "PackedPlaces.com — Découvrez l'affluence réelle des destinations touristiques", + "title": "PackedPlaces.com: Découvrez l'affluence réelle des destinations touristiques", "description": "PackedPlaces.com visualise l'affluence touristique de plus de 700 destinations dans le monde, semaine par semaine, à partir des données de vacances scolaires de plus de 100 pays.", - "ogTitle": "PackedPlaces.com — Découvrez l'affluence réelle des destinations touristiques", + "ogTitle": "PackedPlaces.com: Découvrez l'affluence réelle des destinations touristiques", "ogDescription": "PackedPlaces.com visualise l'affluence touristique de plus de 700 destinations dans le monde, semaine par semaine, à partir des données de vacances scolaires de plus de 100 pays." }, "bestTime": {