SEO blogpost V2

This commit is contained in:
2026-08-05 20:17:01 +02:00
parent 529a95cba2
commit b423c4502a
26 changed files with 173 additions and 26 deletions

View File

@@ -1,8 +1,11 @@
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
import Nav from "@/components/nav";
import Footer from "@/components/footer";
import ContextualGuideLink from "@/components/contextual-guide-link";
import RelatedGuideArticles from "@/components/related-guide-articles";
import { getGuide, GUIDES } from "@/lib/guides";
import { TOOL_PAGES } from "@/lib/tool-pages";
@@ -27,9 +30,16 @@ export async function generateMetadata({ params }: PageProps<"/ratgeber/[slug]">
url: `${SITE_URL}/ratgeber/${guide.slug}`,
title: guide.title,
description: guide.description,
images: [{ url: guide.image.socialSrc, alt: guide.image.alt, width: 1200, height: 630 }],
publishedTime: "2026-08-03T08:00:00+02:00",
modifiedTime: "2026-08-03T08:00:00+02:00",
},
twitter: {
card: "summary_large_image",
title: guide.title,
description: guide.description,
images: [{ url: guide.image.socialSrc, alt: guide.image.alt }],
},
};
}
@@ -44,6 +54,8 @@ export default async function GuidePage({ params }: PageProps<"/ratgeber/[slug]"
.filter((tool): tool is (typeof TOOL_PAGES)[number] => Boolean(tool));
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "Article",
headline: guide.title,
description: guide.description,
@@ -52,7 +64,23 @@ export default async function GuidePage({ params }: PageProps<"/ratgeber/[slug]"
inLanguage: "de-DE",
mainEntityOfPage: url,
author: { "@type": "Person", name: "Timo Knuth" },
publisher: { "@type": "Organization", name: "Entscheidomat", url: SITE_URL },
publisher: {
"@type": "Organization",
name: "Entscheidomat",
url: SITE_URL,
logo: { "@type": "ImageObject", url: `${SITE_URL}/icon.svg`, width: 512, height: 512 },
},
image: { "@type": "ImageObject", url: `${SITE_URL}${guide.image.src}`, width: 1536, height: 1024 },
},
{
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "Entscheidomat", item: SITE_URL },
{ "@type": "ListItem", position: 2, name: "Ratgeber", item: `${SITE_URL}/ratgeber` },
{ "@type": "ListItem", position: 3, name: guide.title, item: url },
],
},
],
};
return (
@@ -72,6 +100,9 @@ export default async function GuidePage({ params }: PageProps<"/ratgeber/[slug]"
<p className="mt-6 text-lg leading-relaxed text-ink-soft">{guide.description}</p>
<p className="mt-6 text-sm text-ink-soft">Von Timo Knuth · Veröffentlicht und aktualisiert am {guide.updated}</p>
</header>
<figure className="mt-10 overflow-hidden rounded-3xl border border-line bg-surface shadow-soft">
<Image src={guide.image.src} alt={guide.image.alt} width={1536} height={1024} priority sizes="(min-width: 1024px) 768px, 100vw" className="h-auto w-full object-cover" />
</figure>
<div className="mt-10 space-y-10 text-[1.0625rem] leading-8 text-ink-soft">
{guide.sections.map((section) => (
<section key={section.heading}>
@@ -84,9 +115,11 @@ export default async function GuidePage({ params }: PageProps<"/ratgeber/[slug]"
{section.bullets.map((bullet) => <li key={bullet}>{bullet}</li>)}
</ul>
)}
<ContextualGuideLink guide={guide} sectionHeading={section.heading} />
</section>
))}
</div>
<RelatedGuideArticles guide={guide} />
<aside className="mt-12 rounded-3xl border border-emerald-line bg-emerald-soft p-6 text-ink">
<h2 className="font-display text-2xl font-semibold">Passendes Tool auswählen</h2>
<p className="mt-2 leading-relaxed text-ink-soft">Für spielerische, folgenarme Entscheidungen kannst du die kostenlosen Tools direkt im Browser nutzen ohne Anmeldung.</p>

View File

@@ -1,4 +1,5 @@
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import Nav from "@/components/nav";
import Footer from "@/components/footer";
@@ -28,7 +29,11 @@ export default function GuideIndexPage() {
</header>
<section className="mt-14 grid gap-5 md:grid-cols-3" aria-label="Ratgeberartikel">
{GUIDES.map((guide) => (
<article key={guide.slug} className="flex flex-col rounded-3xl border border-line bg-surface p-6 shadow-soft transition-transform duration-200 hover:-translate-y-1">
<article key={guide.slug} className="flex flex-col overflow-hidden rounded-3xl border border-line bg-surface shadow-soft transition-transform duration-200 hover:-translate-y-1">
<Link href={`/ratgeber/${guide.slug}`} className="relative block aspect-[3/2] overflow-hidden" tabIndex={-1} aria-hidden="true">
<Image src={guide.image.src} alt="" fill sizes="(min-width: 768px) 33vw, 100vw" className="object-cover transition-transform duration-500 hover:scale-105" />
</Link>
<div className="flex flex-1 flex-col p-6">
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[#0d7a57]">{guide.readingTime}</p>
<h2 className="mt-4 font-display text-3xl font-semibold leading-tight text-ink">{guide.title}</h2>
<p className="mt-4 flex-1 leading-relaxed text-ink-soft">{guide.excerpt}</p>
@@ -36,6 +41,7 @@ export default function GuideIndexPage() {
<Link href={`/ratgeber/${guide.slug}`} className="mt-5 font-semibold text-[#0d7a57] underline decoration-[#0d7a57]/30 underline-offset-4 transition-colors hover:text-[#095c41]">
Artikel lesen <span aria-hidden></span>
</Link>
</div>
</article>
))}
</section>

View File

@@ -0,0 +1,19 @@
import Link from "next/link";
import type { Guide } from "@/lib/guides";
import { getGuide } from "@/lib/guides";
export default function ContextualGuideLink({ guide, sectionHeading }: { guide: Guide; sectionHeading: string }) {
if (guide.contextualLink.afterSection !== sectionHeading) return null;
const relatedGuide = getGuide(guide.contextualLink.guideSlug);
if (!relatedGuide) return null;
return (
<p className="mt-5 border-l-2 border-[#0d7a57]/30 pl-4 text-base leading-relaxed text-ink-soft">
{guide.contextualLink.lead}{" "}
<Link href={`/ratgeber/${relatedGuide.slug}`} className="font-semibold text-[#0d7a57] underline decoration-[#0d7a57]/30 underline-offset-4 hover:text-[#095c41]">
{relatedGuide.title}
</Link>
</p>
);
}

View File

@@ -0,0 +1,31 @@
import Link from "next/link";
import type { Guide } from "@/lib/guides";
import { getGuide } from "@/lib/guides";
export default function RelatedGuideArticles({ guide }: { guide: Guide }) {
const relatedGuides = guide.relatedGuides
.map((slug) => getGuide(slug))
.filter((related): related is Guide => Boolean(related));
if (relatedGuides.length === 0) return null;
return (
<aside className="mt-12 rounded-3xl border border-line bg-surface p-6 shadow-soft">
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[#0d7a57]">Weiterlesen</p>
<h2 className="mt-3 font-display text-2xl font-semibold text-ink">Das könnte dich auch interessieren</h2>
<div className="mt-5 grid gap-4 sm:grid-cols-2">
{relatedGuides.map((related) => (
<Link
key={related.slug}
href={`/ratgeber/${related.slug}`}
className="rounded-2xl border border-line p-4 transition-colors hover:border-[#0d7a57]/40 hover:bg-[#e6f7f0]/40"
>
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-[#0d7a57]">{related.readingTime}</p>
<h3 className="mt-2 font-display text-xl font-semibold leading-tight text-ink">{related.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-ink-soft">{related.excerpt}</p>
</Link>
))}
</div>
</aside>
);
}

View File

@@ -6,8 +6,19 @@ export type Guide = {
published: string;
updated: string;
readingTime: string;
image: {
src: string;
socialSrc: string;
alt: string;
};
/** Slugs der thematisch passenden Tool-Seiten, für kontextbezogenes internes Linking. */
relatedTools: string[];
relatedGuides: string[];
contextualLink: {
afterSection: string;
lead: string;
guideSlug: string;
};
sections: Array<{
heading: string;
paragraphs: string[];
@@ -25,8 +36,11 @@ export const GUIDES: Guide[] = [
"Ein Ja-Nein-Generator ersetzt kein Nachdenken. Aber er löst genau die Fragen, bei denen Nachdenken längst nichts mehr bringt.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "6 Min. Lesezeit",
readingTime: "8 Min. Lesezeit",
image: { src: "/images/guides/ja-oder-nein-entscheiden.avif", socialSrc: "/images/guides/social/ja-oder-nein-entscheiden.jpg", alt: "Eine Münze in der Luft als Symbol für eine Ja-Nein-Entscheidung" },
relatedTools: ["ja-nein-generator", "muenze-werfen", "entweder-oder"],
relatedGuides: ["kopf-oder-zahl-muenzwurf-online", "entweder-oder-entscheiden"],
contextualLink: { afterSection: "Ja-Nein-Generator, Münze oder Magic 8-Ball?", lead: "Wenn du dich nicht zwischen Ja und Nein, sondern zwischen zwei konkreten Möglichkeiten entscheidest, hilft dir dieser Ratgeber:", guideSlug: "entweder-oder-entscheiden" },
sections: [
{
heading: "Warum „ja oder nein“ so oft hängen bleibt",
@@ -131,8 +145,11 @@ export const GUIDES: Guide[] = [
"„Nenn mir eine Zahl zwischen 1 und 10“ klingt trivial. Hinter der Antwort steckt trotzdem eine überraschend spannende Technik.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "5 Min. Lesezeit",
readingTime: "6 Min. Lesezeit",
image: { src: "/images/guides/zufallszahl-zwischen-1-und-10.avif", socialSrc: "/images/guides/social/zufallszahl-zwischen-1-und-10.jpg", alt: "Zahlenspielsteine und ein Würfel als Symbol für Zufallszahlen" },
relatedTools: ["zufallszahl-generator", "wuerfel-online"],
relatedGuides: ["gluecksrad-online-drehen", "zufallsgenerator-richtig-nutzen"],
contextualLink: { afterSection: "Zahl, Würfel oder Rad was passt wann?", lead: "Für die grundsätzliche Wahl zwischen Zahl, Würfel, Rad oder Namensliste findest du hier eine praktische Übersicht:", guideSlug: "zufallsgenerator-richtig-nutzen" },
sections: [
{
heading: "Der häufigste Fall: eine Zahl aus einem Bereich",
@@ -215,8 +232,11 @@ export const GUIDES: Guide[] = [
"Zettel falten, Hut suchen, Namen entziffern: Eine Online-Ziehung spart nicht nur Zeit, sie ist auch leichter überprüfbar.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "5 Min. Lesezeit",
readingTime: "6 Min. Lesezeit",
image: { src: "/images/guides/lose-ziehen-online.avif", socialSrc: "/images/guides/social/lose-ziehen-online.jpg", alt: "Gefaltete Lose in einer Glasschale" },
relatedTools: ["namen-auslosen", "gluecksrad"],
relatedGuides: ["namen-fair-auslosen-teams-gewinner", "faire-auslosung-namen-liste"],
contextualLink: { afterSection: "Die Vorbereitung entscheidet über die Fairness", lead: "Eine vollständige Checkliste für Teilnehmerlisten und nachvollziehbare Regeln findest du hier:", guideSlug: "faire-auslosung-namen-liste" },
sections: [
{
heading: "Wofür sich eine Online-Ziehung anbietet",
@@ -294,8 +314,11 @@ export const GUIDES: Guide[] = [
"Eine echte Münze ist nicht so fair, wie du denkst. Ein digitaler Münzwurf löst gleich mehrere Probleme auf einmal.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "5 Min. Lesezeit",
readingTime: "6 Min. Lesezeit",
image: { src: "/images/guides/kopf-oder-zahl-muenzwurf-online.avif", socialSrc: "/images/guides/social/kopf-oder-zahl-muenzwurf-online.jpg", alt: "Eine drehende Münze für einen digitalen Münzwurf" },
relatedTools: ["muenze-werfen", "ja-nein-generator", "entweder-oder"],
relatedGuides: ["ja-oder-nein-entscheiden", "entweder-oder-entscheiden"],
contextualLink: { afterSection: "Wann eine Münze nicht das richtige Werkzeug ist", lead: "Für zwei Optionen, die etwas mehr Abwägung brauchen, passt dieser Entscheidungsratgeber besser:", guideSlug: "entweder-oder-entscheiden" },
sections: [
{
heading: "Kopf oder Zahl die schnellste Entscheidung der Welt",
@@ -374,8 +397,11 @@ export const GUIDES: Guide[] = [
"„Entweder oder“ klingt nach einer Zwickmühle. Meistens ist es aber nur eine Frage, die noch nicht scharf genug gestellt wurde.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "5 Min. Lesezeit",
readingTime: "6 Min. Lesezeit",
image: { src: "/images/guides/entweder-oder-entscheiden.avif", socialSrc: "/images/guides/social/entweder-oder-entscheiden.jpg", alt: "Zwei Wege als Symbol für eine Entscheidung zwischen Optionen" },
relatedTools: ["entweder-oder", "muenze-werfen", "gluecksrad"],
relatedGuides: ["entscheidung-treffen-wenn-zwei-optionen-gleich-gut-sind", "kopf-oder-zahl-muenzwurf-online"],
contextualLink: { afterSection: "Gleichstand ist ein Ergebnis, kein Problem", lead: "Wenn beide Optionen wirklich gleich gut sind, helfen dir diese konkreten Schritte weiter:", guideSlug: "entscheidung-treffen-wenn-zwei-optionen-gleich-gut-sind" },
sections: [
{
heading: "Erst die Optionen schärfen",
@@ -463,8 +489,11 @@ export const GUIDES: Guide[] = [
"Zwei gleich starke Teams und ein akzeptiertes Ergebnis: Beides hängt weniger vom Zufall ab als von der Regel davor.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "4 Min. Lesezeit",
readingTime: "6 Min. Lesezeit",
image: { src: "/images/guides/namen-fair-auslosen-teams-gewinner.avif", socialSrc: "/images/guides/social/namen-fair-auslosen-teams-gewinner.jpg", alt: "Ordentlich angeordnete Namenskarten für eine faire Auslosung" },
relatedTools: ["namen-auslosen", "gluecksrad"],
relatedGuides: ["faire-auslosung-namen-liste", "lose-ziehen-online"],
contextualLink: { afterSection: "Die Liste ist wichtiger als der Generator", lead: "So bereitest du die Namen und Regeln für eine nachprüfbare Auslosung vor:", guideSlug: "faire-auslosung-namen-liste" },
sections: [
{
heading: "Drei Aufgaben, die oft verwechselt werden",
@@ -551,8 +580,11 @@ export const GUIDES: Guide[] = [
"Ein Glücksrad macht Zufall sichtbar. Genau das ist sein Vorteil, und der Grund, warum es ab einer gewissen Länge aufhört zu funktionieren.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "4 Min. Lesezeit",
readingTime: "6 Min. Lesezeit",
image: { src: "/images/guides/gluecksrad-online-drehen.avif", socialSrc: "/images/guides/social/gluecksrad-online-drehen.jpg", alt: "Ein kleines farbiges Glücksrad" },
relatedTools: ["gluecksrad", "namen-auslosen", "entweder-oder"],
relatedGuides: ["zufallsgenerator-richtig-nutzen", "namen-fair-auslosen-teams-gewinner"],
contextualLink: { afterSection: "Wo ein Rad im Alltag besser funktioniert als eine Liste", lead: "Bei längeren Namenslisten oder mehreren Ergebnissen ist diese Anleitung die bessere Ergänzung:", guideSlug: "faire-auslosung-namen-liste" },
sections: [
{
heading: "Der eigentliche Vorteil ist nicht der Zufall",
@@ -645,8 +677,11 @@ export const GUIDES: Guide[] = [
"Nicht jede Entscheidung braucht eine lange Pro-und-Contra-Liste. So löst du kleine Pattsituationen, ohne den Zufall mit Verantwortung zu verwechseln.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "4 Min. Lesezeit",
readingTime: "5 Min. Lesezeit",
image: { src: "/images/guides/entscheidung-treffen-wenn-zwei-optionen-gleich-gut-sind.avif", socialSrc: "/images/guides/social/entscheidung-treffen-wenn-zwei-optionen-gleich-gut-sind.jpg", alt: "Zwei gleichwertige Optionen auf einer Waage" },
relatedTools: ["entweder-oder", "muenze-werfen", "ja-nein-generator"],
relatedGuides: ["entweder-oder-entscheiden", "ja-oder-nein-entscheiden"],
contextualLink: { afterSection: "Zufall als Spiegel, nicht als Ausrede", lead: "Wie du die spontane Reaktion auf ein Ergebnis für eine Ja-Nein-Frage nutzt, liest du hier:", guideSlug: "ja-oder-nein-entscheiden" },
sections: [
{
heading: "Warum sich gleich gute Optionen so schwer anfühlen",
@@ -720,8 +755,11 @@ export const GUIDES: Guide[] = [
"Fair wird eine Auslosung nicht durch Konfetti, sondern durch klare Regeln. Diese Checkliste hilft bei Namen, Teams und kleinen Gruppenaktionen.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "4 Min. Lesezeit",
readingTime: "5 Min. Lesezeit",
image: { src: "/images/guides/faire-auslosung-namen-liste.avif", socialSrc: "/images/guides/social/faire-auslosung-namen-liste.jpg", alt: "Eine vorbereitete Namensliste und Lose für eine faire Auslosung" },
relatedTools: ["namen-auslosen", "gluecksrad", "zufallszahl-generator"],
relatedGuides: ["namen-fair-auslosen-teams-gewinner", "lose-ziehen-online"],
contextualLink: { afterSection: "Glücksrad oder Namen-Auslosung?", lead: "Für Teams, Gewinner und Aufgaben zeigt dieser Ratgeber die passende Vorgehensweise:", guideSlug: "namen-fair-auslosen-teams-gewinner" },
sections: [
{
heading: "Fairness beginnt vor dem ersten Klick",
@@ -808,8 +846,11 @@ export const GUIDES: Guide[] = [
"Ein Tool für jede Lage ist selten die beste Wahl. Dieser Überblick zeigt, wann Münze, Rad, Würfel oder Liste sinnvoll sind.",
published: "3. August 2026",
updated: "3. August 2026",
readingTime: "4 Min. Lesezeit",
readingTime: "5 Min. Lesezeit",
image: { src: "/images/guides/zufallsgenerator-richtig-nutzen.avif", socialSrc: "/images/guides/social/zufallsgenerator-richtig-nutzen.jpg", alt: "Münze, Würfel, Glücksrad und Namenskarten als Auswahl an Zufallstools" },
relatedTools: ["gluecksrad", "wuerfel-online", "zufallszahl-generator"],
relatedGuides: ["gluecksrad-online-drehen", "zufallszahl-zwischen-1-und-10"],
contextualLink: { afterSection: "Die Werkzeuge im Überblick", lead: "Wann ein Glücksrad die beste Wahl ist und wann nicht, erklärt dieser Artikel:", guideSlug: "gluecksrad-online-drehen" },
sections: [
{
heading: "Erst die Anzahl der Optionen zählen",

View File

@@ -2,6 +2,23 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",
images: {
formats: ["image/avif", "image/webp"],
minimumCacheTTL: 2678400,
},
async headers() {
return [
{
source: "/images/guides/:path*",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
];
},
};
export default nextConfig;

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB