# Next.js Blog Template: Markdown, Live-Demos und JSON-LD

> So haben wir ein Next.js Blog Template auf Markdown gebaut: zod-Frontmatter, Live-Demos, Sticky-Inhaltsverzeichnis, JSON-LD, Feeds und ein strenger Validator.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 16. Okt. 2026
- Kategorie: [Praxis: Screens und Apps](https://wingo-ui.com/de/blog/category/build)
- Lesezeit: 10 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/nextjs-blog-template
- Englische Fassung: https://wingo-ui.com/blog/nextjs-blog-template

## Kurz gesagt

Unser Next.js-Blog besteht aus einfachen Markdown-Dateien mit YAML-Frontmatter, das zod prüft. Gerendert wird beim Build über die Vorlagen Blog Page und Blog Post Page, und Live-Demos von Komponenten stehen als Bild-Links im Text. Jede Beitragsseite liefert den vollen Text im Server-HTML, dazu JSON-LD für BlogPosting, FAQPage und Breadcrumbs, eine .md-Version sowie RSS- und JSON-Feeds. Ein Validator-Skript lässt CI scheitern, wenn ein Keyword doppelt vergeben ist, ein Link auf einen Entwurf zeigt oder eine interne Route nicht existiert.

Ein typisches Next.js Blog Template bringt einen Ordner mit Markdown-Dateien, eine `[slug]`-Route und Tailwind Typography mit. Damit hast du eine Seite. Du hast damit aber keine strukturierten Daten, keine Feeds, kein Inhaltsverzeichnis, das auf dem Smartphone funktioniert, keine Entwürfe, die aus der Produktion draußen bleiben, und keine Prüfung, die verhindert, dass zwei Beiträge um dieselbe Suche konkurrieren. Dieses Build-Log zeigt, wie wir den Blog von Wingo UI mit Next.js 16 und unseren eigenen Vorlagen gebaut haben, welche Entscheidungen dahinterstehen und welche Regeln ein Skript jetzt bei jedem Beitrag durchsetzt, auch bei diesem.

## Was braucht ein Next.js Blog Template außer einer [slug]-Route?

Es braucht die Teile, die einen Beitrag überall auffindbar und lesbar machen: strukturierte Daten, Feeds, ein Inhaltsverzeichnis, das auf dem Smartphone funktioniert, und Prüfungen, die laufen, bevor ein Beitrag live geht. Die üblichen Starter haben sie nicht. Das [Blog Starter Kit von Vercel](https://vercel.com/templates/next.js/blog-starter-kit) (Vorlagenseite geprüft am 16. Oktober 2026) liest Markdown aus einem `_posts`-Ordner, parst das Frontmatter mit `gray-matter`, macht aus Markdown mit `remark` und `remark-html` HTML und stylt es mit Tailwind CSS. Auf der Vorlagenseite stehen weder Inhaltsverzeichnis noch JSON-LD, RSS oder Sitemap. Es ist ein sauberer Ausgangspunkt, und wenn du bis heute Abend einen persönlichen Blog willst, bist du damit schneller.

Unsere Liste war länger, denn der Blog ist zugleich eine Verkaufsdemo für unsere [Vorlage Blog Page](https://wingo-ui.com/templates/blog-page) und die [Vorlage Blog Post Page](https://wingo-ui.com/templates/blog-post-page):

- Der komplette Artikel im Server-HTML: Titel, TL;DR, Text, FAQ, Autor und Datum, lesbar mit `curl`.
- Interaktive Live-Demos von Komponenten mitten im Text.
- Ein Inhaltsverzeichnis, das auf dem Desktop neben dem Artikel klebt und auf dem Smartphone zum Bottom Sheet wird.
- JSON-LD für `BlogPosting`, `FAQPage` und `BreadcrumbList`, Open-Graph-Bilder, eine Sitemap.
- RSS, ein JSON Feed und eine Markdown-Version jedes Beitrags für KI-Agenten.
- Ein Validator, der CI scheitern lässt, wenn ein Beitrag gegen die Regeln verstößt.

## Warum einfache Markdown-Dateien statt MDX?

Weil jeder Beitrag an vier Stellen als Text existieren muss: in der HTML-Seite, im RSS-Eintrag, in der `.md`-Version und in `llms-full.txt`. Bei den meisten Setups für Next.js Blog MDX darf ein Beitrag jede React-Komponente importieren. Beliebiges JSX hat aber keine Textform, also bräuchte jeder Import an drei dieser vier Stellen einen handgeschriebenen Ersatz.

Wir haben deshalb einen Next.js Markdown Blog im strengen Sinn gebaut, ohne JSX in Beiträgen. Beiträge sind Dateien unter `content/blog/posts/<slug>.md`, der Dateiname ist der Slug, und das Frontmatter läuft beim Laden der Beiträge durch ein zod-Schema. Ein fehlerhaftes Feld lässt den Build mit Dateiname und Feld scheitern, statt in der Produktion eine leere Beschreibung zu rendern. Das hier ist eine gekürzte Kopie des echten Schemas:

```ts
import { z } from "zod";

const text = z.string().trim().min(1);
const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "erwartet YYYY-MM-DD");
// yaml macht aus einem 2026-10-16 ohne Anführungszeichen ein Date; zurück zum reinen Tag
const dateField = z.preprocess((value) => (value instanceof Date ? value.toISOString().slice(0, 10) : value), isoDate);

export const postFrontmatterSchema = z
  .object({
    title: text.min(30).max(60),
    description: text.min(140).max(160),
    date: dateField,
    updated: dateField.optional(),
    author: text,
    keyword: text,
    secondary: z.array(text).min(2).max(6),
    tldr: text,
    faq: z.array(z.object({ q: text, a: text })).min(3).max(6),
    draft: z.boolean().optional(),
  })
  .strict()
  .superRefine((post, ctx) => {
    if (!post.title.toLowerCase().includes(post.keyword.toLowerCase())) {
      ctx.addIssue({ code: "custom", path: ["title"], message: "muss das Keyword enthalten" });
    }
  });
```

Zwei Details sind wichtig. YAML macht aus einem `2026-10-16` ohne Anführungszeichen ein JavaScript-`Date`, also wandelt `dateField` es zurück in den reinen Tag, bevor der Regex läuft. Ohne das scheitert jedes Datum ohne Anführungszeichen mit „expected string“. Und `.strict()` lehnt unbekannte Schlüssel ab. Das fängt Tippfehler wie `tdlr`, die sonst still verschwinden würden. Wir parsen mit dem Paket `yaml` und einem kleinen Regex für die `---`-Begrenzungen, deshalb brauchen wir `gray-matter` nicht.

## Wie funktionieren Live-Demos von Komponenten in einem Markdown-Beitrag?

Embeds nutzen die einzige Markdown-Syntax, die ohnehin auf externe Inhalte zeigt: das Bild. Ein Beitrag enthält `> Live-Demo (Table Of Contents): Caption. Probier sie unter [Table Of Contents](https://wingo-ui.com/components/table-of-contents) aus und installier sie mit `npx wingo-ui@latest add table-of-contents`.`, und der Loader sammelt jeden solchen Link ein (Code-Blöcke überspringt er) und prüft, ob der Slug in der Registry existiert. Optionale Playground-Werte reisen als URL-kodiertes JSON in `?values=` mit, und `&h=` reserviert die Höhe des Rahmens, begrenzt auf 160 bis 900px. Ohne diesen Wert bekommt eine Komponenten-Demo 360px und ein Block oder eine Vorlage 640px. So verschiebt sich nichts, wenn die Demo lädt.

Die Vorlage Blog Post Page hat eine Prop `figures`, die eine Quelle `#figure-<name>` auf einen beliebigen React-Knoten abbildet. Die Website füllt diese Map für jeden Link mit einer Demo-Abbildung: die echte Demo aus der Registry, die Bildunterschrift und eine Installationsleiste mit dem CLI-Befehl, einem Free- oder Pro-Badge und einem Link zur Doku. Ein Absatz, der nur eine Abbildung enthält, wird als `div` gerendert, denn eine Demo in einem `<p>` ist ungültiges HTML, und React warnt bei der Hydration davor.

Jede Demo wird erst gemountet, wenn ihre Box bis auf 400px an den Viewport herankommt. Das erledigt ein `IntersectionObserver`. Ein Beitrag mit drei Demos lädt also einen Chunk nach dem anderen. Blöcke und Vorlagen rendern stattdessen in einem Same-Origin-Iframe von `/preview/<slug>`. So bleiben ihre Sticky Header aus dem Artikel heraus, und ihre Media Queries richten sich nach der Breite des Rahmens statt nach dem Fenster. Hier läuft die Vorlage für die Beitragsseite selbst, mitten in diesem Beitrag:

> Live-Demo (Blog Post Page): Scroll durch den Artikel: Der Balken oben füllt sich beim Lesen. Dieser Rahmen ist schmaler als 1024px, also sitzt das Inhaltsverzeichnis als Pill unter dem Header; tipp darauf, um alle Abschnitte in einem Bottom Sheet zu öffnen, und spring zu einem. Probier sie unter [Blog Post Page](https://wingo-ui.com/templates/blog-post-page) aus und installier sie mit `npx wingo-ui@latest add blog-post-page`.

Der Text um eine Demo herum muss für sich allein verständlich sein. Crawler, Feed-Reader und KI-Assistenten führen kein JavaScript aus. Deshalb ersetzt die `.md`-Version jede Abbildung durch eine Zeile, die die Komponente nennt, auf ihre Doku verlinkt und den Installationsbefehl angibt.

## Wie baust du in einen Next.js-Blog ein Inhaltsverzeichnis ein, das auf dem Smartphone funktioniert?

Erzeuge das Inhaltsverzeichnis und die IDs der Überschriften mit derselben Slug-Funktion, und rendere die Desktop-Liste und die Pill fürs Smartphone auf dem Server. Der klassische Fehler, wenn du ein Next.js Blog Inhaltsverzeichnis baust, ist ein Link auf eine ID, die die gerenderte Überschrift gar nicht hat. Deshalb nutzen wir an beiden Stellen eine einzige Funktion. Die [Komponente Markdown](https://wingo-ui.com/components/markdown) hat eine Prop `headingIds`, die jeder Überschrift eine ID aus `slugifyHeading` gibt, und `markdownToc(source)` aus der Vorlage liest die h2- und h3-Zeilen derselben Quelle mit derselben Funktion und hängt bei Duplikaten `-2` an. Die Website hängt die FAQ aus dem Frontmatter vor dem Rendern als Abschnitt `## FAQ` an den Text, damit sie auch im Inhaltsverzeichnis auftaucht.

Die Liste geht an die [Komponente Table Of Contents](https://wingo-ui.com/components/table-of-contents). Auf dem Desktop klebt sie neben dem Artikel und markiert den Abschnitt, den du gerade liest. Unterhalb von `collapseBelow` wird sie zu einer Pill, die unter dem Header festsitzt und ein Bottom Sheet mit 48px hohen Zeilen öffnet. Beide Layouts stehen im Server-HTML, und der Wechsel ist reines CSS. Ein Smartphone sieht also nie zuerst kurz die Desktop-Liste aufblitzen. So sieht die Verdrahtung in einer Client-Komponente aus:

```tsx
"use client";

import { TableOfContents } from "@/components/ui/table-of-contents";
import { Markdown } from "@/components/ui/markdown";
import { markdownToc } from "@/components/templates/blog-post-page";

export function Article({ body }: { body: string }) {
  const toc = markdownToc(body);
  return (
    <div className="mx-auto max-w-[1200px] px-4 lg:grid lg:grid-cols-[minmax(0,1fr)_220px] lg:gap-16">
      <TableOfContents items={toc} showProgress updateHash={false} collapseBelow="lg" className="mb-8 lg:order-2 lg:mb-0" />
      <div className="min-w-0 lg:order-1">
        <Markdown size="lg" headingIds className="max-w-[720px] [&_h2]:scroll-mt-24 [&_h3]:scroll-mt-24">
          {body}
        </Markdown>
      </div>
    </div>
  );
}
```

Der Sticky-Abstand kommt aus der CSS-Variable `--topbar-offset`. Der Header unserer Website ist 56px hoch, also setzt die Beitragsansicht sie auf `calc(56px + env(safe-area-inset-top))`. So landen angesprungene Überschriften auf einem iPhone mit Notch direkt unter dem Header.

> Live-Demo (Table Of Contents): Dieser Rahmen ist schmaler als 1024px, also ist das Inhaltsverzeichnis eine Pill: Scroll durch den Vertrag, dann siehst du, wie sich der Ring füllt und der Abschnittsname wechselt; tipp danach auf die Pill und wähl einen Abschnitt im Sheet. Probier sie unter [Table Of Contents](https://wingo-ui.com/components/table-of-contents) aus und installier sie mit `npx wingo-ui@latest add table-of-contents`.

## Wie sieht Next.js Blog SEO auf jeder Beitragsseite aus?

Jede Beitragsseite ist statisches HTML mit vollständigen Metadaten und einem JSON-LD-Graphen. Der größte Teil von Next.js Blog SEO besteht darin, diese langweiligen Teile auf jeder Seite ohne Ausnahme zu erledigen. Die Route ist statisch: `generateStaticParams` gibt die veröffentlichten Slugs zurück, und `dynamicParams = false` macht aus jedem anderen Slug einen 404. `generateMetadata` liefert Titel, Beschreibung, eine Canonical-URL, Open Graph `type: article` mit Veröffentlichungs- und Änderungszeit, eine Twitter Card `summary_large_image` und einen Alternate-Link auf die `.md`-Version.

Beim JSON-LD folgen wir dem [JSON-LD-Guide von Next.js](https://nextjs.org/docs/app/guides/json-ld): ein `<script type="application/ld+json">` in der Seite, bei dem `<` durch `\u003c` ersetzt wird, damit ein Beitragstitel den Script-Tag nie schließen kann. Ein Graph pro Beitrag enthält ein `BlogPosting` (Headline, Datum, Autor als `Person` mit Link auf die Autorenseite, `keywords`, `wordCount` und `mentions`, mit jeder Komponente als `SoftwareSourceCode`, die auf ihre Doku zeigt) und ein `FAQPage` aus derselben FAQ, die auch auf der Seite steht. Die `BreadcrumbList` kommt aus der sichtbaren Breadcrumb-Komponente, also können Markup und Seite nicht auseinanderlaufen. Google zeigt laut seinem [Search-Central-Changelog](https://developers.google.com/search/updates) (geprüft am 16. Oktober 2026) seit dem 7. Mai 2026 keine FAQ-Rich-Results mehr. Wir haben die FAQ trotzdem behalten: Diesen Teil eines Beitrags zitiert ein Assistent am ehesten.

Ein Stolperstein wird leicht übersehen. Unter einer Route Group hängt Next.js an eine `opengraph-image`-Route ein Suffix: die ersten sechs Base-36-Zeichen eines djb2-Hashs des Elternpfads. Die `<meta>`-Tags bekommen die richtige URL automatisch, das `image` im JSON-LD aber nicht. Deshalb berechnet `seo.ts` denselben Hash, um eine passende URL zu bauen. Wenn du die Route verschiebst, ändern sich beide gemeinsam.

## Wie entstehen Feeds und Markdown-Versionen aus denselben Dateien?

Alles läuft über einen einzigen Loader, `getPosts()`, der nach Datum sortiert und Entwürfe in Produktions-Builds weglässt. Der RSS-2.0-Feed unter `/blog/rss.xml` und der JSON Feed 1.1 unter `/blog/feed.json` listen die neuesten 50 Beiträge, und die neuesten 20 enthalten den kompletten Artikel als HTML, gerendert mit `remark-gfm` und `remark-rehype` aus demselben Markdown. Die Beiträge 21 bis 50 bekommen trotzdem einen Eintrag mit ihrer Zusammenfassung. Erscheinen viele Beiträge am selben Tag, fällt so keiner aus dem Feed.

Jeder Beitrag ist außerdem als Markdown unter seiner URL plus `.md` erreichbar, über ein Rewrite von `/blog/:slug.md` auf einen Route Handler. Das ist dasselbe Muster wie in [unserem Tutorial zu Next.js llms.txt](https://wingo-ui.com/de/blog/nextjs-llms-txt). `llms.txt` listet jeden Beitrag mit seinem `.md`-Link, und `llms-full.txt` hängt die Beiträge vollständig an, mit Überschriften, die um zwei Ebenen nach unten verschoben sind, damit sie unter einem Abschnitt `## Blog` stehen.

## Wie verhindert der Validator Keyword-Kannibalisierung?

Er lässt den Build scheitern, wenn zwei Beiträge dasselbe primäre `keyword` angeben. `npm run blog:check` läuft in CI nach dem Registry-Check, und auch der Loader wirft bei einem Duplikat einen Fehler. Zwei Seiten, die auf dieselbe Suche zielen, teilen sich nämlich die Klicks und die Links. Außerdem verlangt der Validator das Keyword im Titel, in der Beschreibung, im Slug und in den ersten 100 Wörtern.

Die übrigen Fehler fangen die Patzer ab, die in einem Markdown-Repo leicht passieren:

- Weniger als 3 Links auf `/components/*`-Seiten oder kein Link hoch zur Kategorieseite oder zum Pillar-Beitrag.
- Ein interner Link auf eine Route, die es nicht gibt, ein veröffentlichter Beitrag, der auf einen Entwurf verlinkt, oder ein Link auf `/components/<template>`, der weiterleitet (der Check verlangt `/templates/<slug>`).
- Ein `h1` im Text, ein Bild ohne Alt-Text, ein unbekannter Komponenten-Slug, irgendwo ein Geviertstrich.
- Eine veraltete `published.generated.ts`. Client-Komponenten können `content/blog` nicht lesen, also schreibt ein Skript `HAS_PUBLISHED_POSTS` in eine Datei, die der Header liest. Der Blog-Link bleibt versteckt, bis es einen echten Beitrag gibt.

Warnungen gibt es für die Wortzahl pro Beitragstyp, eine fehlende Demo, ein sekundäres Keyword, das im Text fehlt, und ein `updated`-Datum, das älter als 12 Monate ist.

## Welche Kompromisse hat ein Markdown-Blog in Next.js?

Ein Markdown-Blog hat zwei echte Kosten: kein eigenes React außerhalb der Abbildungs-Syntax und eine harte Trennung zwischen Server und Client. Die Regel „nur Markdown“ heißt, dass ein Beitrag keine einmalige Komponente enthalten kann. Wenn wir ein Diagramm wollen, muss es zuerst eine Registry-Demo werden, und das ist mehr Arbeit als ein MDX-Import. Wir nehmen das hin, weil dann jedes Embed eine dokumentierte, installierbare Komponente ist und die `.md`-Version ehrlich bleibt.

Die Client-Grenze war der andere Preis. Die Vorlage nimmt Funktionen als Props an, etwa `onSubscribe` für das Newsletter-Formular, und ihre `figures`-Map enthält Client-Demos. Funktionen können nicht von einer Server-Komponente in eine Client-Komponente wandern. Deshalb ist die Beitragsseite eine Server-Route, die den Beitrag lädt, das JSON-LD und die Breadcrumb rendert und reine Daten an eine kleine Client-Ansicht weitergibt. Jede Doku-Seite listet außerdem die Beiträge, die ihre Komponente nutzen, serverseitig gerendert. Das verlinkt Blog und Katalog in beide Richtungen. Weitere Screens aus dieser Kategorie findest du unter [Praxis: Screens und Apps](https://wingo-ui.com/de/blog/category/build) und im [Tutorial zum Next.js Admin Dashboard](https://wingo-ui.com/de/blog/nextjs-admin-dashboard-tutorial), das fünf Admin-Screens aus Blöcken von Wingo UI baut.

## Wie starte ich meinen eigenen Blog mit diesen Vorlagen?

Installier die Beitragsseite mit der CLI. Sie bringt die Blog-Übersicht, die [Komponente Breadcrumb](https://wingo-ui.com/components/breadcrumb) und das Inhaltsverzeichnis mit:

```bash
npx wingo-ui@latest add blog-post-page
```

`BlogPostPage` nimmt Funktionen und eine Router-Link-Komponente an, gehört also in eine Client-Komponente. Die Server-Route liest das Markdown und reicht reine Daten nach unten:

```tsx
// app/blog/[slug]/post-view.tsx
"use client";

import Link from "next/link";
import { BlogPostPage } from "@/components/templates/blog-post-page";
import type { BlogArticle } from "@/components/templates/blog-post-page-data";

const CATEGORIES = [{ value: "engineering", label: "Technik" }];

export function PostView({ article }: { article: BlogArticle }) {
  return (
    <BlogPostPage
      article={article}
      categories={CATEGORIES}
      showRelated={false}
      linkComponent={Link}
      onSubscribe={(email) => fetch("/api/newsletter", { method: "POST", body: JSON.stringify({ email }) })}
    />
  );
}
```

```tsx
// app/blog/[slug]/page.tsx
import { readdir, readFile } from "node:fs/promises";
import { PostView } from "./post-view";

export const dynamicParams = false;

export async function generateStaticParams() {
  const files = await readdir("content/posts");
  return files.filter((file) => file.endsWith(".md")).map((file) => ({ slug: file.slice(0, -3) }));
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const body = await readFile(`content/posts/${slug}.md`, "utf8");
  return (
    <PostView
      article={{
        // in einer echten App kommen diese Felder aus dem Frontmatter
        slug,
        title: "Notizen zu unserer CI-Pipeline",
        excerpt: "Was wir an unserer CI-Pipeline geändert haben und was wir beim nächsten Mal weglassen würden.",
        category: "engineering",
        date: "2026-10-16",
        readingTime: 6,
        author: { name: "Emma Carter", role: "Staff Engineer" },
        body,
      }}
    />
  );
}
```

Drei Standardwerte sorgen bei der ersten Installation für Überraschungen. `relatedPosts` und `categories` nutzen standardmäßig die Acme-Beispieldaten. Ein echter Beitrag zeigt also drei erfundene verwandte Karten, bis du eigene übergibst oder sie abschaltest. Ohne `onSubscribe` wartet das Newsletter-Formular 900 ms und meldet Erfolg, ohne etwas zu senden. Und die Vorlage zeichnet ihren eigenen Marketing-Header und -Footer. Setz also `header={false}` und `footer={false}`, wenn die Seite im Layout deiner Website sitzt, so wie bei uns.

Die drei Blog-Bausteine gehören zu Pro, siehe [Preise](https://wingo-ui.com/de/pricing). Wenn du mit Claude Code oder Cursor baust, kann der [MCP-Server von Wingo UI](https://wingo-ui.com/docs/mcp) die Props für deinen Agenten nachschlagen, während er die Seiten verdrahtet.

## Komponenten in diesem Artikel

- [Blog Page](https://wingo-ui.com/templates/blog-page) (Pro): A React blog page template: a featured post, category chips, live search, a grid or list of post cards, pagination and a newsletter band. Installation: `npx wingo-ui@latest add blog-page`
- [Blog Post Page](https://wingo-ui.com/templates/blog-post-page) (Pro): A React blog post template: byline, share actions, a drawn cover, a Markdown body with a sticky table of contents and reading progress. Installation: `npx wingo-ui@latest add blog-post-page`
- [Table Of Contents](https://wingo-ui.com/components/table-of-contents) (Pro): A React table of contents that lists a page's sections, highlights the one being read and jumps to any, with a pill and sheet on phones. Installation: `npx wingo-ui@latest add table-of-contents`

## Häufige Fragen

### Sollte ein Next.js-Blog MDX oder einfaches Markdown nutzen?

Nimm MDX, wenn Autorinnen und Autoren beliebiges React in Beiträgen brauchen. Nimm einfaches Markdown mit einer kleinen Konvention für Embeds, wenn Beiträge als Text lesbar bleiben sollen, für Feeds, .md-Seiten und KI-Agenten. Deshalb haben wir uns für Markdown entschieden.

### Wie baue ich ein Inhaltsverzeichnis in einen Next.js-Markdown-Blog ein?

Lies die h2- und h3-Zeilen aus dem Markdown, gib jeder dieselbe Slug-ID, die dein Renderer auf die Überschrift setzt, und reich die Liste an eine Inhaltsverzeichnis-Komponente weiter. Die Blog Post Page von Wingo UI macht das mit markdownToc und der Markdown-Prop headingIds, deshalb passen die IDs immer.

### Braucht ein Next.js-Blog JSON-LD?

Es ist optional, aber ein BlogPosting-Knoten mit Headline, Datum, Autor und Bild beschreibt den Artikel für Suchmaschinen und andere Leser an einer Stelle. Der JSON-LD-Guide von Next.js empfiehlt ein Script-Tag in der Seite und rät, das Zeichen < zu escapen, damit die Daten den Tag nie schließen können.

### Lohnt sich FAQPage-Markup noch?

Google zeigt laut dem Search-Central-Changelog seit dem 7. Mai 2026 keine FAQ-Rich-Results mehr. Wir rendern trotzdem eine sichtbare FAQ und ihren FAQPage-Knoten, weil die Antworten zum Zitieren geschrieben sind und das Markup nur einen Funktionsaufruf kostet.

### Sind die Blog-Vorlagen von Wingo UI kostenlos?

Blog Page, Blog Post Page und Table Of Contents gehören Stand Oktober 2026 zu Pro. Installier sie mit npx wingo-ui@latest add blog-post-page, das holt auch die Blog Page und das Inhaltsverzeichnis mit.

---

Quelle: https://wingo-ui.com/de/blog/nextjs-blog-template
