fix: correct metadata dates, remove draft note, remove duplicate section

- Fixed all 'undefined NaN, NaN' dates in metadata divs across all 22 posts
- Removed draft instruction from qr-code-scan-statistics-2026
- Removed duplicate 'Trackable / dynamic QR code' section from trackable-qr-codes
- All posts now have proper 'Last updated' dates showing January 26, 2026
This commit is contained in:
Timo Knuth
2026-03-06 16:01:01 +01:00
parent 7d5d142156
commit 76bde71585
41 changed files with 3640 additions and 397 deletions

View File

@@ -1,21 +1,20 @@
'use client';
import { Suspense } from 'react';
import { ToastContainer } from '@/components/ui/Toast';
import AuthProvider from '@/components/SessionProvider';
import { PostHogProvider, PostHogPageView } from '@/components/PostHogProvider';
import CookieBanner from '@/components/CookieBanner';
import GoogleAnalytics from '@/components/analytics/GoogleAnalytics';
import FacebookPixel from '@/components/analytics/FacebookPixel';
import { Suspense } from 'react';
import { ToastContainer } from '@/components/ui/Toast';
import AuthProvider from '@/components/SessionProvider';
import { PostHogProvider } from '@/components/PostHogProvider';
import CookieBanner from '@/components/CookieBanner';
import GoogleAnalytics from '@/components/analytics/GoogleAnalytics';
import FacebookPixel from '@/components/analytics/FacebookPixel';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<PostHogProvider>
<Suspense fallback={null}>
<PostHogPageView />
<GoogleAnalytics />
<FacebookPixel />
</Suspense>
return (
<PostHogProvider>
<Suspense fallback={null}>
<GoogleAnalytics />
<FacebookPixel />
</Suspense>
<AuthProvider>
{children}
</AuthProvider>

View File

@@ -0,0 +1,73 @@
import { ArrowRight } from "lucide-react";
import { TrackedCtaLink } from "@/components/marketing/MarketingAnalytics";
import { Card } from "@/components/ui/Card";
type PageType = "commercial" | "use_case_hub" | "use_case";
type GrowthLink = {
href: string;
title: string;
description: string;
ctaLabel: string;
};
export function GrowthLinksSection({
eyebrow,
title,
description,
links,
pageType,
cluster,
useCase,
}: {
eyebrow: string;
title: string;
description: string;
links: GrowthLink[];
pageType: PageType;
cluster: string;
useCase?: string;
}) {
return (
<section className="py-20 bg-slate-50">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<div className="max-w-3xl mb-12">
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
{eyebrow}
</div>
<h2 className="mt-3 text-4xl font-bold text-slate-900">{title}</h2>
<p className="mt-4 text-xl text-slate-600">{description}</p>
</div>
<div className="grid gap-6 lg:grid-cols-4">
{links.map((link) => (
<TrackedCtaLink
key={link.href}
href={link.href}
ctaLabel={link.ctaLabel}
ctaLocation="related_workflows"
pageType={pageType}
cluster={cluster}
useCase={useCase}
className="group block h-full"
>
<Card className="h-full rounded-3xl border-slate-200 bg-white p-7 shadow-sm transition-all hover:-translate-y-1 hover:shadow-lg">
<div className="text-lg font-semibold text-slate-900">
{link.title}
</div>
<p className="mt-3 text-base leading-7 text-slate-600">
{link.description}
</p>
<div className="mt-6 flex items-center gap-2 text-sm font-semibold text-blue-700">
<span>Open workflow</span>
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
</div>
</Card>
</TrackedCtaLink>
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,94 @@
"use client";
import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { trackEvent } from "@/components/PostHogProvider";
type PageType = "commercial" | "use_case_hub" | "use_case";
type TrackingContext = {
pageType: PageType;
cluster?: string;
useCase?: string;
};
function getUtmProperties(searchParams: ReturnType<typeof useSearchParams>) {
return {
utm_source: searchParams?.get("utm_source") || undefined,
utm_medium: searchParams?.get("utm_medium") || undefined,
utm_campaign: searchParams?.get("utm_campaign") || undefined,
utm_content: searchParams?.get("utm_content") || undefined,
};
}
export function MarketingPageTracker({
pageType,
cluster,
useCase,
}: TrackingContext) {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (!pathname) {
return;
}
trackEvent("landing_page_viewed", {
landing_page_slug: pathname,
page_type: pageType,
cluster,
use_case: useCase,
...getUtmProperties(searchParams),
});
}, [cluster, pageType, pathname, searchParams, useCase]);
return null;
}
type TrackedCtaLinkProps = TrackingContext & {
href: string;
ctaLabel: string;
ctaLocation: string;
destination?: string;
className?: string;
children: React.ReactNode;
};
export function TrackedCtaLink({
href,
ctaLabel,
ctaLocation,
destination,
className,
children,
pageType,
cluster,
useCase,
}: TrackedCtaLinkProps) {
const pathname = usePathname();
const searchParams = useSearchParams();
return (
<Link
href={href}
className={className}
onClick={() => {
trackEvent("cta_clicked", {
landing_page_slug: pathname,
page_type: pageType,
cluster,
use_case: useCase,
cta_label: ctaLabel,
cta_location: ctaLocation,
destination: destination || href,
...getUtmProperties(searchParams),
});
}}
>
{children}
</Link>
);
}

View File

@@ -0,0 +1,427 @@
import type { FAQItem } from "@/lib/types";
import type { Metadata } from "next";
import Link from "next/link";
import {
ArrowRight,
CheckCircle2,
Compass,
Link2,
Radar,
Sparkles,
} from "lucide-react";
import Breadcrumbs, { BreadcrumbItem } from "@/components/Breadcrumbs";
import SeoJsonLd from "@/components/SeoJsonLd";
import { FAQSection } from "@/components/aeo/FAQSection";
import {
MarketingPageTracker,
TrackedCtaLink,
} from "@/components/marketing/MarketingAnalytics";
import { AnswerFirstBlock } from "@/components/marketing/AnswerFirstBlock";
import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { breadcrumbSchema, faqPageSchema } from "@/lib/schema";
type LinkCard = {
href: string;
title: string;
description: string;
};
type UseCasePageTemplateProps = {
title: string;
description: string;
eyebrow: string;
intro: string;
pageType: "commercial" | "use_case";
cluster: string;
useCase?: string;
breadcrumbs: BreadcrumbItem[];
answer: string;
whenToUse: string[];
comparisonItems: {
label: string;
value: boolean;
text?: string;
}[];
howToSteps: string[];
primaryCta: {
href: string;
label: string;
};
secondaryCta: {
href: string;
label: string;
};
workflowTitle: string;
workflowIntro: string;
workflowCards: {
title: string;
description: string;
}[];
checklistTitle: string;
checklist: string[];
supportLinks: LinkCard[];
faq: FAQItem[];
schemaData?: Record<string, unknown>[];
};
export function buildUseCaseMetadata({
title,
description,
canonicalPath,
}: {
title: string;
description: string;
canonicalPath: string;
}): Metadata {
const canonical = `https://www.qrmaster.net${canonicalPath}`;
return {
title: {
absolute: `${title} | QR Master`,
},
description,
alternates: {
canonical,
languages: {
"x-default": canonical,
en: canonical,
},
},
openGraph: {
title: `${title} | QR Master`,
description,
url: canonical,
type: "website",
images: ["/og-image.png"],
},
twitter: {
title: `${title} | QR Master`,
description,
},
};
}
export function UseCasePageTemplate({
title,
description,
eyebrow,
intro,
pageType,
cluster,
useCase,
breadcrumbs,
answer,
whenToUse,
comparisonItems,
howToSteps,
primaryCta,
secondaryCta,
workflowTitle,
workflowIntro,
workflowCards,
checklistTitle,
checklist,
supportLinks,
faq,
schemaData = [],
}: UseCasePageTemplateProps) {
return (
<>
<SeoJsonLd
data={[...schemaData, breadcrumbSchema(breadcrumbs), faqPageSchema(faq)]}
/>
<MarketingPageTracker
pageType={pageType}
cluster={cluster}
useCase={useCase}
/>
<div className="min-h-screen bg-white">
<section className="relative overflow-hidden bg-gradient-to-br from-slate-950 via-blue-950 to-cyan-900 text-white">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(125,211,252,0.22),transparent_38%),radial-gradient(circle_at_bottom_right,rgba(255,255,255,0.08),transparent_30%)]" />
<div className="relative container mx-auto max-w-7xl px-4 py-20 sm:px-6 lg:px-8">
<Breadcrumbs
items={breadcrumbs}
className="[&_a]:text-blue-100/80 [&_a:hover]:text-white [&_span]:text-blue-100/80 [&_[aria-current=page]]:text-white"
/>
<div className="grid gap-12 lg:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)] lg:items-center">
<div className="space-y-8">
<div className="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/10 px-4 py-2 text-sm font-semibold text-cyan-100 shadow-lg shadow-cyan-950/30 backdrop-blur">
<Sparkles className="h-4 w-4" />
<span>{eyebrow}</span>
</div>
<div className="space-y-5">
<h1 className="max-w-4xl text-4xl font-bold tracking-tight text-white md:text-5xl lg:text-6xl">
{title}
</h1>
<p className="max-w-3xl text-lg leading-8 text-blue-50/88 md:text-xl">
{intro}
</p>
</div>
<div className="grid gap-3 text-sm text-blue-50/80 sm:grid-cols-2">
{[
"Built for QR workflows where the printed surface should stay stable.",
"Focused on operational clarity, not inflated ROI claims.",
"Connected to a commercial parent and sibling workflows.",
"Designed to fit QR Master's existing marketing theme.",
].map((line) => (
<div
key={line}
className="flex items-start gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 backdrop-blur-sm"
>
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-cyan-300" />
<span>{line}</span>
</div>
))}
</div>
<div className="flex flex-col gap-4 sm:flex-row">
<TrackedCtaLink
href={primaryCta.href}
ctaLabel={primaryCta.label}
ctaLocation="hero_primary"
pageType={pageType}
cluster={cluster}
useCase={useCase}
>
<Button
size="lg"
className="w-full bg-white px-8 py-4 text-base font-semibold text-slate-950 hover:bg-slate-100 sm:w-auto"
>
{primaryCta.label}
</Button>
</TrackedCtaLink>
<TrackedCtaLink
href={secondaryCta.href}
ctaLabel={secondaryCta.label}
ctaLocation="hero_secondary"
pageType={pageType}
cluster={cluster}
useCase={useCase}
>
<Button
variant="outline"
size="lg"
className="w-full border-white/30 bg-white/5 px-8 py-4 text-base text-white hover:bg-white/10 sm:w-auto"
>
{secondaryCta.label}
</Button>
</TrackedCtaLink>
</div>
</div>
<Card className="border-white/10 bg-white/10 p-8 text-white shadow-2xl shadow-slate-950/30 backdrop-blur">
<div className="space-y-6">
<div className="flex items-center justify-between border-b border-white/10 pb-4">
<div>
<div className="text-xs uppercase tracking-[0.24em] text-cyan-200/70">
Workflow snapshot
</div>
<div className="mt-2 text-2xl font-semibold text-white">
What matters here
</div>
</div>
<Compass className="h-9 w-9 text-cyan-300" />
</div>
<div className="space-y-4">
{workflowCards.map((card, index) => (
<div
key={card.title}
className="rounded-2xl border border-white/10 bg-slate-950/30 p-4"
>
<div className="mb-2 flex items-center gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-cyan-400/15 text-sm font-semibold text-cyan-200">
{index + 1}
</div>
<div className="text-lg font-semibold text-white">
{card.title}
</div>
</div>
<p className="text-sm leading-6 text-blue-50/80">
{card.description}
</p>
</div>
))}
</div>
</div>
</Card>
</div>
</div>
</section>
<div className="container mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<AnswerFirstBlock
whatIsIt={answer}
whenToUse={whenToUse}
comparison={{
leftTitle: "Static",
rightTitle: "Better fit here",
items: comparisonItems,
}}
howTo={{
steps: howToSteps,
}}
className="mt-0"
/>
</div>
<section className="bg-slate-50 py-16">
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-10 max-w-3xl">
<h2 className="text-3xl font-bold tracking-tight text-slate-900">
{workflowTitle}
</h2>
<p className="mt-4 text-lg leading-8 text-slate-600">
{workflowIntro}
</p>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{workflowCards.map((card) => (
<Card
key={card.title}
className="rounded-3xl border-slate-200/80 bg-white p-7 shadow-sm"
>
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-blue-50 text-blue-700">
<Radar className="h-6 w-6" />
</div>
<h3 className="text-xl font-semibold text-slate-900">
{card.title}
</h3>
<p className="mt-3 text-base leading-7 text-slate-600">
{card.description}
</p>
</Card>
))}
</div>
</div>
</section>
<section className="py-16">
<div className="container mx-auto grid max-w-7xl gap-8 px-4 sm:px-6 lg:grid-cols-[minmax(0,0.95fr)_minmax(280px,0.8fr)] lg:px-8">
<Card className="rounded-3xl border-slate-200 bg-white p-8 shadow-sm">
<div className="flex items-start justify-between gap-4">
<div>
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
Checklist
</div>
<h2 className="mt-3 text-3xl font-bold text-slate-900">
{checklistTitle}
</h2>
</div>
<CheckCircle2 className="h-8 w-8 text-blue-700" />
</div>
<ul className="mt-8 space-y-4">
{checklist.map((item) => (
<li
key={item}
className="flex items-start gap-3 text-slate-700"
>
<CheckCircle2 className="mt-1 h-5 w-5 shrink-0 text-green-600" />
<span className="leading-7">{item}</span>
</li>
))}
</ul>
</Card>
<Card className="rounded-3xl border-slate-200 bg-slate-950 p-8 text-white shadow-xl shadow-slate-200">
<div className="flex items-center gap-3">
<Link2 className="h-5 w-5 text-cyan-300" />
<h2 className="text-2xl font-bold">Related links</h2>
</div>
<div className="mt-6 space-y-4">
{supportLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="group block rounded-2xl border border-white/10 bg-white/5 p-4 transition-colors hover:bg-white/10"
>
<div className="flex items-start justify-between gap-4">
<div>
<div className="text-lg font-semibold text-white">
{link.title}
</div>
<div className="mt-2 text-sm leading-6 text-blue-50/78">
{link.description}
</div>
</div>
<ArrowRight className="mt-1 h-4 w-4 shrink-0 text-cyan-300 transition-transform group-hover:translate-x-1" />
</div>
</Link>
))}
</div>
</Card>
</div>
</section>
<div className="container mx-auto max-w-5xl px-4 pb-6 sm:px-6 lg:px-8">
<FAQSection items={faq} title={`${title} FAQ`} />
</div>
<section className="pb-20 pt-6">
<div className="container mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
<div className="rounded-[2rem] bg-gradient-to-r from-blue-700 via-indigo-700 to-slate-900 px-8 py-10 text-white shadow-2xl shadow-blue-100">
<div className="flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between">
<div className="max-w-2xl">
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-100/80">
Next step
</div>
<h2 className="mt-3 text-3xl font-bold tracking-tight">
Use a QR workflow that stays useful after the print run starts.
</h2>
<p className="mt-4 text-lg leading-8 text-blue-50/84">
{description}
</p>
</div>
<div className="flex flex-col gap-4 sm:flex-row">
<TrackedCtaLink
href={primaryCta.href}
ctaLabel={primaryCta.label}
ctaLocation="footer_primary"
pageType={pageType}
cluster={cluster}
useCase={useCase}
>
<Button
size="lg"
className="w-full bg-white px-7 text-slate-950 hover:bg-slate-100 sm:w-auto"
>
{primaryCta.label}
</Button>
</TrackedCtaLink>
<TrackedCtaLink
href={secondaryCta.href}
ctaLabel={secondaryCta.label}
ctaLocation="footer_secondary"
pageType={pageType}
cluster={cluster}
useCase={useCase}
>
<Button
variant="outline"
size="lg"
className="w-full border-white/30 bg-white/5 text-white hover:bg-white/10 sm:w-auto"
>
{secondaryCta.label}
</Button>
</TrackedCtaLink>
</div>
</div>
</div>
</div>
</section>
</div>
</>
);
}

View File

@@ -47,10 +47,11 @@ export function Footer({ variant = 'marketing', t }: FooterProps) {
<li><Link href="/press" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Press</Link></li>
<li><Link href="/testimonials" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Testimonials</Link></li>
<li><Link href="/authors/timo" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Timo Knuth (Author)</Link></li>
<li><Link href="/#pricing" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.pricing}</Link></li>
<li><Link href="/qr-code-tracking" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>QR Analytics</Link></li>
<li><Link href="/faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.faq}</Link></li>
<li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.blog}</Link></li>
<li><Link href="/#pricing" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.pricing}</Link></li>
<li><Link href="/qr-code-tracking" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>QR Analytics</Link></li>
<li><Link href="/use-cases" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Use Cases</Link></li>
<li><Link href="/faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.faq}</Link></li>
<li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.blog}</Link></li>
</ul>
</div>
@@ -66,9 +67,10 @@ export function Footer({ variant = 'marketing', t }: FooterProps) {
<li><Link href="/reprint-calculator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Reprint Cost Calculator</Link></li>
<li><Link href="/qr-code-tracking" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Our Analytics</Link></li>
<li><Link href="/manage-qr-codes" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Manage QR Codes</Link></li>
<li><Link href="/custom-qr-code-generator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Custom QR</Link></li>
<li><Link href="/tools/barcode-generator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Barcode Generator</Link></li>
<li><Link href="/manage-qr-codes" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Manage QR Codes</Link></li>
<li><Link href="/custom-qr-code-generator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Custom QR</Link></li>
<li><Link href="/qr-code-for-marketing-campaigns" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Campaign QR Codes</Link></li>
<li><Link href="/tools/barcode-generator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Barcode Generator</Link></li>
<li><Link href="/guide/tracking-analytics" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Tracking Guide</Link></li>
<li><Link href="/guide/qr-code-best-practices" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Best Practices</Link></li>
<li><Link href="/guide/bulk-qr-code-generation" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Bulk Generation Guide</Link></li>