Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dd42e35bc | ||
|
|
73c070f11e | ||
|
|
a8990a872a |
No files matched your search
@@ -25,6 +25,43 @@ const nextConfig: NextConfig = {
|
|||||||
// werden."
|
// werden."
|
||||||
serverExternalPackages: ["pdfkit", "ffmpeg-static"],
|
serverExternalPackages: ["pdfkit", "ffmpeg-static"],
|
||||||
|
|
||||||
|
images: {
|
||||||
|
// AVIF vor WebP. Next.js liefert das erste Format, das der Browser laut
|
||||||
|
// Accept-Header unterstuetzt; Browser ohne AVIF bekommen unveraendert
|
||||||
|
// WebP, ganz alte das Original.
|
||||||
|
//
|
||||||
|
// Am Hero-Bild der Startseite nachgemessen (1920x1280), mit den
|
||||||
|
// Einstellungen aus next/dist/server/image-optimizer.js — WebP q75 gegen
|
||||||
|
// AVIF q47 (Next.js skaliert die Qualitaet mit 50/80) bei effort 3:
|
||||||
|
// 640 px -43 %, 828 px -42 %, 1080 px -40 %, 1920 px -37 %.
|
||||||
|
//
|
||||||
|
// Die Kodierdauer spricht nicht dagegen: bei effort 3 lag AVIF in
|
||||||
|
// derselben Messung gleichauf oder schneller als WebP (1920 px: 60 ms
|
||||||
|
// gegen 122 ms). Die frueher hier vermutete teure Erstkodierung gibt es
|
||||||
|
// in dieser Einstellung nicht.
|
||||||
|
formats: ["image/avif", "image/webp"],
|
||||||
|
|
||||||
|
// Ein Jahr statt der Next.js-Vorgabe von vier Wochen. Hochgeladene Bilder
|
||||||
|
// sind unveraenderlich: lib/uploads.ts legt jede Datei unter einem eigenen
|
||||||
|
// Namen ab, ein ausgetauschtes Produktbild bekommt also eine neue URL.
|
||||||
|
// Ein kurzes TTL wuerde hier nur brauchbare Ergebnisse wegwerfen.
|
||||||
|
minimumCacheTTL: 60 * 60 * 24 * 365,
|
||||||
|
|
||||||
|
// Entspricht der Vorgabe von Next.js 16, die andere Werte bereits ablehnt
|
||||||
|
// (gegen die laufende Seite geprueft: ?q=42 antwortet mit 400) — aendert
|
||||||
|
// das Verhalten also nicht. Ausgeschrieben, damit eine spaetere
|
||||||
|
// Erweiterung der Liste eine bewusste Entscheidung ist: jeder zusaetzlich
|
||||||
|
// erlaubte Wert oeffnet einen eigenen Cache-Eintrag und eine eigene
|
||||||
|
// Kodierung je Bild, von aussen ueber die URL ausloesbar.
|
||||||
|
qualities: [75],
|
||||||
|
|
||||||
|
// Ebenfalls die Vorgabe, ausdruecklich festgehalten: ein SVG ist ein
|
||||||
|
// Dokument, das Skripte enthalten kann — same-origin ausgeliefert waere
|
||||||
|
// das ein XSS-Vektor. Diese Zeile soll verhindern, dass der Schalter
|
||||||
|
// spaeter beilaeufig umgelegt wird.
|
||||||
|
dangerouslyAllowSVG: false,
|
||||||
|
},
|
||||||
|
|
||||||
// Turbopacks Dateisystem-Caches (.next/dev/cache & .next/cache/turbopack)
|
// Turbopacks Dateisystem-Caches (.next/dev/cache & .next/cache/turbopack)
|
||||||
// kollidieren in diesem OneDrive-synchronisierten Arbeitsverzeichnis
|
// kollidieren in diesem OneDrive-synchronisierten Arbeitsverzeichnis
|
||||||
// wiederholt mit der Cloud-Synchronisierung ("Failed to open database" /
|
// wiederholt mit der Cloud-Synchronisierung ("Failed to open database" /
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { noIndexPrivate } from "@/lib/seo";
|
||||||
import { getCurrentCustomer } from "@/lib/auth";
|
import { getCurrentCustomer } from "@/lib/auth";
|
||||||
import { getAddressesForCustomer } from "@/lib/account-data";
|
import { getAddressesForCustomer } from "@/lib/account-data";
|
||||||
import { getShippingTiers, getShippingRegion } from "@/lib/shipping";
|
import { getShippingTiers, getShippingRegion } from "@/lib/shipping";
|
||||||
@@ -16,7 +17,9 @@ export const dynamic = "force-dynamic";
|
|||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
return { title: t.checkoutPage.eyebrow };
|
// Die Kasse traegt Adress- und Zahlungsdaten und soll nie als
|
||||||
|
// Suchtreffer erscheinen.
|
||||||
|
return { title: t.checkoutPage.eyebrow, robots: noIndexPrivate };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function CheckoutPage() {
|
export default async function CheckoutPage() {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { noIndexPrivate } from "@/lib/seo";
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
return { title: t.checkoutSuccessPage.successEyebrow };
|
// Die Bestaetigungsseite zeigt Bestelldaten und ist ueber eine
|
||||||
|
// Bestell-ID in der URL erreichbar — nicht indexieren.
|
||||||
|
return { title: t.checkoutSuccessPage.successEyebrow, robots: noIndexPrivate };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CheckoutSuccessLayout({ children }: { children: React.ReactNode }) {
|
export default function CheckoutSuccessLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import { getCompanyInfo } from "@/lib/company-info";
|
|||||||
import { getCurrentCustomer } from "@/lib/auth";
|
import { getCurrentCustomer } from "@/lib/auth";
|
||||||
import { getApprovedReviews, getReviewSummary, getCustomerReview, hasCustomerPurchased } from "@/lib/reviews";
|
import { getApprovedReviews, getReviewSummary, getCustomerReview, hasCustomerPurchased } from "@/lib/reviews";
|
||||||
import { isProductWishlisted } from "@/lib/wishlist";
|
import { isProductWishlisted } from "@/lib/wishlist";
|
||||||
import { getShippingTiers } from "@/lib/shipping";
|
import { getShippingTiers, getShippingRegion } from "@/lib/shipping";
|
||||||
|
import { allowedCountryCodes } from "@/lib/shipping-region";
|
||||||
import { resolveShippingCostCents } from "@/lib/shipping-calc";
|
import { resolveShippingCostCents } from "@/lib/shipping-calc";
|
||||||
import { submitReviewAction, toggleWishlistAction, subscribeStockNotificationAction } from "../actions";
|
import { submitReviewAction, toggleWishlistAction, subscribeStockNotificationAction } from "../actions";
|
||||||
import { TurnstileField } from "@/components/turnstile-widget";
|
import { TurnstileField } from "@/components/turnstile-widget";
|
||||||
@@ -38,10 +39,29 @@ export async function generateMetadata(
|
|||||||
const product = await getProductBySlug(slug);
|
const product = await getProductBySlug(slug);
|
||||||
if (!product) return {};
|
if (!product) return {};
|
||||||
|
|
||||||
|
const description = `${product.name} — ${product.tasteTags.join(", ")}. ${product.weightGrams} g aus eigener Imkerei in Altenmünster.`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: product.name,
|
title: product.name,
|
||||||
description: `${product.name} — ${product.tasteTags.join(", ")}. ${product.weightGrams} g aus eigener Imkerei in Altenmünster.`,
|
description,
|
||||||
alternates: { canonical: `/honig/${slug}` },
|
alternates: { canonical: `/honig/${slug}` },
|
||||||
|
// Ohne diesen Block erbt die Produktseite den openGraph-Block aus dem
|
||||||
|
// Root-Layout unveraendert — jeder geteilte Produktlink zeigte in WhatsApp,
|
||||||
|
// Facebook oder Slack also Titel und Text der STARTSEITE, egal welcher
|
||||||
|
// Honig verlinkt wurde. Metadata erbt openGraph feldweise vom Layout;
|
||||||
|
// ein hier gesetzter Titel ueberschreibt nur diesen einen Wert.
|
||||||
|
//
|
||||||
|
// "images" fehlt absichtlich: das produktspezifische Vorschaubild liefert
|
||||||
|
// bereits opengraph-image.tsx in diesem Verzeichnis. Next.js traegt es
|
||||||
|
// automatisch nach, SOLANGE hier kein eigenes images-Feld steht — ein
|
||||||
|
// Eintrag hier wuerde die gestaltete Karte durch das rohe Produktfoto
|
||||||
|
// ersetzen.
|
||||||
|
openGraph: {
|
||||||
|
type: "website",
|
||||||
|
title: product.name,
|
||||||
|
description,
|
||||||
|
url: `/honig/${slug}`,
|
||||||
|
},
|
||||||
// Deaktivierte Produkte bleiben erreichbar (die Bestellhistorie und die
|
// Deaktivierte Produkte bleiben erreichbar (die Bestellhistorie und die
|
||||||
// Sendungsverfolgung verlinken hierher zum Bewerten), gehoeren aber nicht
|
// Sendungsverfolgung verlinken hierher zum Bewerten), gehoeren aber nicht
|
||||||
// in den Suchindex: wer ueber Google kaeme, faende ein Produkt, das nicht
|
// in den Suchindex: wer ueber Google kaeme, faende ein Produkt, das nicht
|
||||||
@@ -91,6 +111,8 @@ export default async function ProductDetailPage(
|
|||||||
getCompanyInfo(),
|
getCompanyInfo(),
|
||||||
getShippingTiers(),
|
getShippingTiers(),
|
||||||
]);
|
]);
|
||||||
|
const shippingRegion = await getShippingRegion();
|
||||||
|
const shippingCountries = allowedCountryCodes(shippingRegion);
|
||||||
// "Ab"-Preis fuer eine einzelne Einheit — die Staffel, die beim Kauf genau
|
// "Ab"-Preis fuer eine einzelne Einheit — die Staffel, die beim Kauf genau
|
||||||
// dieses einen Produkts greifen wuerde. Macht Versandkosten schon auf der
|
// dieses einen Produkts greifen wuerde. Macht Versandkosten schon auf der
|
||||||
// Produktseite sichtbar statt erst im Warenkorb/Checkout (Kaufentscheidung
|
// Produktseite sichtbar statt erst im Warenkorb/Checkout (Kaufentscheidung
|
||||||
@@ -114,12 +136,55 @@ export default async function ProductDetailPage(
|
|||||||
...(product.images.length > 0
|
...(product.images.length > 0
|
||||||
? { image: product.images.map((src) => `${base}${src}`) }
|
? { image: product.images.map((src) => `${base}${src}`) }
|
||||||
: {}),
|
: {}),
|
||||||
|
// Fuellmenge als strukturierte Angabe — bei Lebensmitteln ist das der
|
||||||
|
// Wert, ueber den Kaeufer Angebote vergleichen ("500 g" statt "Glas").
|
||||||
|
weight: { "@type": "QuantitativeValue", value: product.weightGrams, unitCode: "GRM" },
|
||||||
offers: {
|
offers: {
|
||||||
"@type": "Offer",
|
"@type": "Offer",
|
||||||
priceCurrency: product.currency,
|
priceCurrency: product.currency,
|
||||||
price: (product.priceCents / 100).toFixed(2),
|
price: (product.priceCents / 100).toFixed(2),
|
||||||
availability: isPurchasable ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
|
availability: isPurchasable ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
|
||||||
|
itemCondition: "https://schema.org/NewCondition",
|
||||||
url: `${base}/honig/${product.slug}`,
|
url: `${base}/honig/${product.slug}`,
|
||||||
|
// Verweist auf das Store-Schema aus dem Root-Layout, statt Name und
|
||||||
|
// Anschrift hier ein zweites Mal auszuschreiben: so bleibt fuer Google
|
||||||
|
// erkennbar, dass Verkaeufer und Betrieb dieselbe Einheit sind.
|
||||||
|
seller: { "@id": `${base}/#business` },
|
||||||
|
// Ohne priceValidUntil stuft Google den Preis nach einiger Zeit als
|
||||||
|
// moeglicherweise veraltet ein und blendet ihn im Suchergebnis aus.
|
||||||
|
// Jahresende des Folgejahres statt "heute + 365 Tage": ein fester
|
||||||
|
// Stichtag bleibt ueber viele Seitenaufrufe derselbe Wert.
|
||||||
|
priceValidUntil: `${new Date().getFullYear() + 1}-12-31`,
|
||||||
|
// Versandkosten im Suchergebnis: Google zeigt sie direkt neben dem Preis
|
||||||
|
// an. Fehlen sie, rechnet Google im Zweifel mit dem hoechsten Satz oder
|
||||||
|
// laesst die Angabe weg — beides kostet Klicks. Der Wert ist die Staffel
|
||||||
|
// fuer genau ein Glas, also derselbe "ab"-Preis wie auf der Seite.
|
||||||
|
shippingDetails: {
|
||||||
|
"@type": "OfferShippingDetails",
|
||||||
|
shippingRate: {
|
||||||
|
"@type": "MonetaryAmount",
|
||||||
|
value: (baseShippingCents / 100).toFixed(2),
|
||||||
|
currency: product.currency,
|
||||||
|
},
|
||||||
|
shippingDestination: {
|
||||||
|
"@type": "DefinedRegion",
|
||||||
|
addressCountry: shippingCountries,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Gesetzliches Widerrufsrecht (siehe Widerrufsbelehrung): vierzehn Tage
|
||||||
|
// ab Erhalt der Ware, Rueckgabe per Versand.
|
||||||
|
//
|
||||||
|
// "returnFees" fehlt hier bewusst: wer die Kosten der Ruecksendung
|
||||||
|
// traegt, ist in der Widerrufsbelehrung nicht geregelt. Eine Annahme an
|
||||||
|
// dieser Stelle waere eine Preisangabe gegenueber Google, die der Shop
|
||||||
|
// nirgends zusagt.
|
||||||
|
hasMerchantReturnPolicy: {
|
||||||
|
"@type": "MerchantReturnPolicy",
|
||||||
|
applicableCountry: shippingCountries,
|
||||||
|
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
|
||||||
|
merchantReturnDays: 14,
|
||||||
|
returnMethod: "https://schema.org/ReturnByMail",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
...(reviewSummary.count > 0
|
...(reviewSummary.count > 0
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { noIndexPrivate } from "@/lib/seo";
|
||||||
|
|
||||||
|
// Der Kontobereich enthaelt ausschliesslich personenbezogene Seiten
|
||||||
|
// (Bestellungen, Adressen, Wunschliste, Zwei-Faktor-Einrichtung) sowie die
|
||||||
|
// Anmeldung selbst. Nichts davon gehoert in den Suchindex.
|
||||||
|
//
|
||||||
|
// Gesetzt einmal hier im Layout statt in jeder Unterseite: /konto hat aktuell
|
||||||
|
// zehn Routen, und die naechste vergisst man sonst garantiert. Next.js
|
||||||
|
// vererbt robots aus dem Layout an alle Unterseiten; setzt eine Unterseite
|
||||||
|
// eigene Metadata (z.B. nur einen title), bleibt der geerbte robots-Wert
|
||||||
|
// erhalten, weil Metadata feldweise und nicht als Ganzes ueberschrieben wird.
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
robots: noIndexPrivate,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function KontoLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { noIndexPrivate } from "@/lib/seo";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { Container } from "@/components/ui";
|
import { Container } from "@/components/ui";
|
||||||
import { BackLink } from "@/components/back-link";
|
import { BackLink } from "@/components/back-link";
|
||||||
@@ -10,7 +11,9 @@ import { invoiceVatBreakdown } from "@/lib/vat";
|
|||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
|
|
||||||
export const metadata: Metadata = { title: "Rechnung" };
|
// Rechnungen enthalten Name, Anschrift und Kaufhistorie. noIndexPrivate
|
||||||
|
// unterbindet zusaetzlich Cache-Kopie und Snippet (siehe lib/seo.ts).
|
||||||
|
export const metadata: Metadata = { title: "Rechnung", robots: noIndexPrivate };
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function InvoicePage(props: PageProps<"/rechnung/[invoiceNumber]">) {
|
export default async function InvoicePage(props: PageProps<"/rechnung/[invoiceNumber]">) {
|
||||||
|
|||||||
+31
-1
@@ -9,15 +9,45 @@ import { getSiteUrl } from "@/lib/url";
|
|||||||
// egal unter welcher Domain die Seite spaeter tatsaechlich lief.
|
// egal unter welcher Domain die Seite spaeter tatsaechlich lief.
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
// Nur Pfade, die ein Crawler ueberhaupt nicht ABRUFEN soll. Das ist eine
|
||||||
|
// bewusst kurze Liste, denn "Disallow" und "noindex" schliessen einander aus:
|
||||||
|
// eine per robots.txt gesperrte Seite wird nie geladen, also sieht Google das
|
||||||
|
// dort gesetzte noindex nie — und nimmt die URL bei einem eingehenden Link
|
||||||
|
// trotzdem als inhaltslosen Treffer in den Index auf.
|
||||||
|
//
|
||||||
|
// Deshalb stehen /checkout, /warenkorb, /konto und /rechnung hier NICHT mehr
|
||||||
|
// (frueher taten sie es). Sie tragen stattdessen ein noindex-Meta-Tag (siehe
|
||||||
|
// lib/seo.ts) und duerfen dafuer gecrawlt werden — nur so faellt eine bereits
|
||||||
|
// indexierte URL wieder aus dem Index. Der zusaetzliche Crawl-Aufwand ist bei
|
||||||
|
// einer Handvoll Seiten vernachlaessigbar.
|
||||||
|
//
|
||||||
|
// Was bleibt:
|
||||||
|
// - /api: Maschinenschnittstellen ohne jeden Nutzen in Suchergebnissen.
|
||||||
|
// - /admin: liefert einem anonymen Crawler ohnehin nur eine Weiterleitung auf
|
||||||
|
// die Anmeldung; jeder Abruf ist reine Last.
|
||||||
|
// - /uploads ist bewusst NICHT gesperrt — dort liegen die Produktbilder, die
|
||||||
|
// in der Google-Bildersuche durchaus erwuenscht sind.
|
||||||
|
const disallow = ["/api/", "/admin"];
|
||||||
|
|
||||||
export default function robots(): MetadataRoute.Robots {
|
export default function robots(): MetadataRoute.Robots {
|
||||||
return {
|
return {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
userAgent: "*",
|
userAgent: "*",
|
||||||
allow: "/",
|
allow: "/",
|
||||||
disallow: ["/admin", "/checkout", "/warenkorb", "/api", "/konto", "/rechnung"],
|
disallow,
|
||||||
|
},
|
||||||
|
// GPTBot & Co. duerfen die oeffentlichen Inhalte lesen (Sichtbarkeit in
|
||||||
|
// KI-Antworten ist erwuenscht), aber ohne die internen Bereiche.
|
||||||
|
// Separat aufgefuehrt, damit eine spaetere Entscheidung dagegen nur
|
||||||
|
// diesen Block betrifft und nicht die Suchmaschinen mit einschliesst.
|
||||||
|
{
|
||||||
|
userAgent: ["GPTBot", "ClaudeBot", "PerplexityBot", "Google-Extended"],
|
||||||
|
allow: "/",
|
||||||
|
disallow,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
sitemap: `${getSiteUrl()}/sitemap.xml`,
|
sitemap: `${getSiteUrl()}/sitemap.xml`,
|
||||||
|
host: getSiteUrl(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { noIndex } from "@/lib/seo";
|
||||||
import { getShippingTiers } from "@/lib/shipping";
|
import { getShippingTiers } from "@/lib/shipping";
|
||||||
import { getActiveProducts } from "@/lib/data";
|
import { getActiveProducts } from "@/lib/data";
|
||||||
import { getBulkDiscountTiers } from "@/lib/bulk-discount";
|
import { getBulkDiscountTiers } from "@/lib/bulk-discount";
|
||||||
@@ -12,7 +13,9 @@ export const dynamic = "force-dynamic";
|
|||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
return { title: t.cart.title };
|
// Ein Warenkorb ist pro Besucher verschieden und als Suchtreffer wertlos —
|
||||||
|
// siehe Begruendung in lib/seo.ts, warum robots.ts allein nicht genuegt.
|
||||||
|
return { title: t.cart.title, robots: noIndex };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function WarenkorbPage() {
|
export default async function WarenkorbPage() {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata-Bausteine fuer Seiten, die NICHT in den Suchindex gehoeren.
|
||||||
|
*
|
||||||
|
* Warum reicht der Eintrag in robots.ts nicht?
|
||||||
|
* "Disallow" verbietet nur das CRAWLEN, nicht das INDEXIEREN. Verweist
|
||||||
|
* irgendwo ein Link auf /warenkorb oder /konto/bestellungen, nimmt Google die
|
||||||
|
* URL trotzdem in den Index auf — nur eben ohne Inhalt, als Treffer mit dem
|
||||||
|
* Hinweis "Fuer diese Seite sind keine Informationen verfuegbar". Schlimmer:
|
||||||
|
* weil der Crawler die Seite nicht lesen darf, sieht er ein dort gesetztes
|
||||||
|
* noindex NIE. Deshalb muessen beide Mechanismen zusammenspielen — robots.ts
|
||||||
|
* haelt den Crawler von den nutzlosen Seiten fern, dieses Meta-Tag sorgt
|
||||||
|
* dafuer, dass eine trotzdem entdeckte URL wieder aus dem Index faellt.
|
||||||
|
*
|
||||||
|
* follow: true statt false ist Absicht. Links auf diesen Seiten (Footer,
|
||||||
|
* Navigation, Produktlinks im Warenkorb) sollen ihre Linkkraft weiterhin an
|
||||||
|
* die oeffentlichen Seiten weitergeben; nur die Seite selbst soll nicht als
|
||||||
|
* Suchtreffer erscheinen.
|
||||||
|
*/
|
||||||
|
export const noIndex: Metadata["robots"] = {
|
||||||
|
index: false,
|
||||||
|
follow: true,
|
||||||
|
googleBot: { index: false, follow: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wie noIndex, zusaetzlich ohne Cache-Kopie und ohne Textausschnitt in den
|
||||||
|
* Suchergebnissen. Fuer Seiten mit personenbezogenen Daten (Rechnungen,
|
||||||
|
* Bestellungen, Kontodaten): selbst wenn eine URL versehentlich oeffentlich
|
||||||
|
* erreichbar waere, soll kein Inhalt in einem Suchmaschinen-Snippet oder
|
||||||
|
* -Cache landen.
|
||||||
|
*/
|
||||||
|
export const noIndexPrivate: Metadata["robots"] = {
|
||||||
|
index: false,
|
||||||
|
follow: false,
|
||||||
|
nocache: true,
|
||||||
|
googleBot: { index: false, follow: false, noimageindex: true, "max-snippet": 0 },
|
||||||
|
};
|
||||||
Reference in new issue
Block a user