import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { ArrowRight, Check, Lock, RefreshCw } from "lucide-react";
import Breadcrumbs, { type BreadcrumbItem } from "@/components/Breadcrumbs";
import SeoJsonLd from "@/components/SeoJsonLd";
import { getComparisonPage } from "@/lib/comparison-pages";
import type { ComparisonPage } from "@/lib/pseo-page-types";
import {
isPublishedComparisonSlug,
publishedComparisonPages,
} from "@/lib/pseo-published-pages";
import { articleSchema, breadcrumbSchema, faqPageSchema } from "@/lib/schema";
const SITE_URL = "https://www.qrmaster.net";
const LAST_MODIFIED = "2026-06-22";
type PageProps = {
params: {
slug: string;
};
};
export const dynamicParams = false;
export function generateStaticParams() {
return publishedComparisonPages.map((page) => ({ slug: page.slug }));
}
function getPublishedPage(slug: string): ComparisonPage {
if (!isPublishedComparisonSlug(slug)) {
notFound();
}
const page = getComparisonPage(slug);
if (!page) {
notFound();
}
return page;
}
function titleFor(page: ComparisonPage) {
if (page.slug === "dynamic-vs-static-qr-codes") {
return "Dynamic or Static QR Codes: Which to Choose for Your Campaign";
}
if (page.slug === "free-vs-paid-qr-code-generator") {
return "Free vs Paid QR Code Generator";
}
return page.title;
}
function descriptionFor(page: ComparisonPage) {
if (page.slug === "dynamic-vs-static-qr-codes") {
return "Decide between dynamic and static QR codes for your next campaign: a buyer-focused comparison of reprint risk, tracking, and cost before you commit to print.";
}
if (page.slug === "free-vs-paid-qr-code-generator") {
return "See when a free QR generator is enough and when paid dynamic QR features, tracking, branding, and reprint savings are worth it.";
}
return page.description;
}
function intentLabel(intentStage: ComparisonPage["intentStage"]) {
if (intentStage === "bottom") return "Commercial investigation";
if (intentStage === "middle") return "Comparison research";
return "Educational comparison";
}
export function generateMetadata({ params }: PageProps): Metadata {
const page = getPublishedPage(params.slug);
const title = titleFor(page);
const description = descriptionFor(page);
const canonical = `${SITE_URL}${page.canonicalPath}`;
return {
title: {
absolute: title,
},
description,
alternates: {
canonical,
languages: {
"x-default": canonical,
en: canonical,
},
},
robots: {
index: true,
follow: true,
},
icons: {
icon: [
{ url: "/favicon.svg", type: "image/svg+xml" },
{ url: "/favicon.ico", sizes: "16x16 32x32", type: "image/x-icon" },
],
shortcut: "/favicon.ico",
apple: "/logo.svg",
},
openGraph: {
title,
description,
url: canonical,
type: "article",
images: ["/og-image.png"],
},
twitter: {
title,
description,
},
};
}
function comparisonItemListSchema(page: ComparisonPage) {
return {
"@context": "https://schema.org",
"@type": "ItemList",
"@id": `${SITE_URL}${page.canonicalPath}#comparison`,
name: `${page.primaryEntity} compared with ${page.secondaryEntity}`,
numberOfItems: page.scorecard.length,
itemListElement: page.scorecard.map((row, index) => ({
"@type": "ListItem",
position: index + 1,
name: row.topic,
description: `${page.primaryEntity}: ${row.qrMaster}. ${page.secondaryEntity}: ${row.alternative}.`,
})),
};
}
function breadcrumbsFor(page: ComparisonPage): BreadcrumbItem[] {
return [
{ name: "Home", url: "/" },
{ name: page.title, url: page.canonicalPath },
];
}
function DynamicFlowVisual() {
return (
{Array.from({ length: 25 }).map((_, index) => (
))}
Printed QR
The physical code stays in place.
Current destination
The URL can change after print.
);
}
export default function ComparisonPageRoute({ params }: PageProps) {
const page = getPublishedPage(params.slug);
const title = titleFor(page);
const description = descriptionFor(page);
const breadcrumbs = breadcrumbsFor(page);
const canonical = `${SITE_URL}${page.canonicalPath}`;
const structuredData = [
articleSchema({
headline: title,
description,
datePublished: LAST_MODIFIED,
dateModified: LAST_MODIFIED,
author: "QR Master",
url: canonical,
image: `${SITE_URL}/og-image.png`,
}),
faqPageSchema(page.faq),
breadcrumbSchema(breadcrumbs),
comparisonItemListSchema(page),
];
return (
<>
{intentLabel(page.intentStage)}
{title}
{page.quickAnswer}
{page.primaryCta.label}
{page.secondaryCta.label}
The short answer
{page.intro} {page.quickAnswer}
Comparison of {page.primaryEntity} and {page.secondaryEntity}
Decision point
{page.secondaryEntity}
{page.primaryEntity}
{page.scorecard.map((row) => (
{row.topic}
{row.alternative}
{row.qrMaster}
))}
Which option should you choose?
Use this page as a buying decision, not as a feature glossary.
The right answer depends on whether the printed asset must
survive a future destination change.
Choose the simpler option when...
{page.bestForAlternative.map((item) => (
- {item}
))}
Choose QR Master when...
{page.bestForQrMaster.map((item) => (
- {item}
))}
Migration steps
If this comparison changes your decision, move the highest-risk
printed assets first. That keeps the work focused on real
reprint and tracking value.
{page.migrationSteps.map((step, index) => (
{index + 1}
{step}
))}
Related QR Master workflows
These links connect the comparison to creation, analytics,
pricing, and operational guidance.
{page.relatedLinks.map((link) => (
{link.title}
{link.description}
Open page
))}
Common questions
{page.faq.map((item) => (
{item.question}
+
{item.answer}
))}
>
);
}