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:
@@ -70,12 +70,13 @@ export default function MarketingLayout({
|
||||
<nav aria-label="Site Map">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><Link href="/pricing">{t.nav.pricing}</Link></li>
|
||||
<li><Link href="/blog">{t.nav.blog}</Link></li>
|
||||
<li><Link href="/learn">{t.nav.learn}</Link></li>
|
||||
<li><Link href="/faq">{t.nav.faq}</Link></li>
|
||||
<li><Link href="/about">{t.nav.about}</Link></li>
|
||||
<li><Link href="/contact">{t.nav.contact}</Link></li>
|
||||
<li><Link href="/pricing">{t.nav.pricing}</Link></li>
|
||||
<li><Link href="/blog">{t.nav.blog}</Link></li>
|
||||
<li><Link href="/learn">{t.nav.learn}</Link></li>
|
||||
<li><Link href="/use-cases">Use Cases</Link></li>
|
||||
<li><Link href="/faq">{t.nav.faq}</Link></li>
|
||||
<li><Link href="/about">{t.nav.about}</Link></li>
|
||||
<li><Link href="/contact">{t.nav.contact}</Link></li>
|
||||
<li><Link href="/login">{t.nav.login}</Link></li>
|
||||
<li><Link href="/signup">{t.nav.signup || "Sign Up"}</Link></li>
|
||||
{/* Tools */}
|
||||
@@ -175,12 +176,15 @@ export default function MarketingLayout({
|
||||
<Link href="/about" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.about}
|
||||
</Link>
|
||||
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.blog}
|
||||
</Link>
|
||||
<Link href="/learn" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.learn}
|
||||
</Link>
|
||||
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.blog}
|
||||
</Link>
|
||||
<Link href="/use-cases" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
Use Cases
|
||||
</Link>
|
||||
<Link href="/learn" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.learn}
|
||||
</Link>
|
||||
<Link href="/#faq" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||
{t.nav.faq}
|
||||
</Link>
|
||||
@@ -265,9 +269,10 @@ export default function MarketingLayout({
|
||||
|
||||
<Link href="/#features" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.features}</Link>
|
||||
<Link href="/#pricing" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.pricing}</Link>
|
||||
<Link href="/about" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.about}</Link>
|
||||
<Link href="/blog" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.blog}</Link>
|
||||
<Link href="/learn" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.learn}</Link>
|
||||
<Link href="/about" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.about}</Link>
|
||||
<Link href="/blog" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.blog}</Link>
|
||||
<Link href="/use-cases" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>Use Cases</Link>
|
||||
<Link href="/learn" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.learn}</Link>
|
||||
<Link href="/#faq" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.faq}</Link>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
|
||||
@@ -112,11 +112,11 @@ export default function AboutPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 text-center">
|
||||
<Link href="/create-qr">
|
||||
<Button size="lg">Create QR Code</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="mt-12 text-center">
|
||||
<Link href="/dynamic-qr-code-generator">
|
||||
<Button size="lg">Create QR Code</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -6,14 +6,20 @@ import { getAuthorBySlug, getPostsByAuthor } from "@/lib/content";
|
||||
import { authors } from "@/lib/author-data";
|
||||
import { authorPageSchema } from "@/lib/schema";
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const author = getAuthorBySlug(params.slug);
|
||||
if (!author) return {};
|
||||
return {
|
||||
title: `${author.name} - ${author.role} | QR Master`,
|
||||
description: author.bio
|
||||
};
|
||||
}
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const author = getAuthorBySlug(params.slug);
|
||||
if (!author) return {};
|
||||
return {
|
||||
title: `${author.name} - ${author.role} | QR Master`,
|
||||
description: author.bio,
|
||||
alternates: {
|
||||
canonical: `https://www.qrmaster.net/authors/${author.slug}`,
|
||||
},
|
||||
openGraph: {
|
||||
url: `https://www.qrmaster.net/authors/${author.slug}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return authors.map((author) => ({
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { MarketingPageTracker } from '@/components/marketing/MarketingAnalytics';
|
||||
import { featuredUseCases } from '@/lib/growth-pages';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
absolute: 'Bulk QR Code Generator - Create Bulk QR Codes from Excel',
|
||||
},
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding. Perfect for products and events.',
|
||||
keywords: 'bulk qr code generator, batch qr code, qr code from excel, csv qr code generator, mass qr code generation, bulk vcard qr code, bulk qr codes free',
|
||||
@@ -21,14 +24,14 @@ export const metadata: Metadata = {
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
title: 'Bulk QR Code Generator - Create Bulk QR Codes from Excel',
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
||||
url: 'https://www.qrmaster.net/bulk-qr-code-generator',
|
||||
type: 'website',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
twitter: {
|
||||
title: 'Bulk QR Code Generator - Create 1000s from Excel',
|
||||
title: 'Bulk QR Code Generator - Create Bulk QR Codes from Excel',
|
||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
||||
},
|
||||
};
|
||||
@@ -280,15 +283,43 @@ export default function BulkQRCodeGeneratorPage() {
|
||||
],
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Bulk QR Code Generator', url: '/bulk-qr-code-generator' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Bulk QR Code Generator', url: '/bulk-qr-code-generator' },
|
||||
];
|
||||
|
||||
const relatedUseCaseLinks = [
|
||||
{
|
||||
href: '/qr-code-for-marketing-campaigns',
|
||||
title: 'QR Codes for Marketing Campaigns',
|
||||
description: 'Use bulk generation when campaign placement or print distribution needs multiple trackable codes.',
|
||||
ctaLabel: 'Create a trackable campaign QR',
|
||||
},
|
||||
{
|
||||
href: featuredUseCases[2].href,
|
||||
title: featuredUseCases[2].title,
|
||||
description: featuredUseCases[2].summary,
|
||||
ctaLabel: featuredUseCases[2].ctaLabel,
|
||||
},
|
||||
{
|
||||
href: '/use-cases',
|
||||
title: 'Explore the use-case hub',
|
||||
description: 'See where bulk creation fits into broader QR workflows and commercial parents.',
|
||||
ctaLabel: 'Explore QR code use cases',
|
||||
},
|
||||
{
|
||||
href: '/custom-qr-code-generator',
|
||||
title: 'Custom QR Code Generator',
|
||||
description: 'Pair bulk creation with stronger print presentation when brand consistency matters.',
|
||||
ctaLabel: 'Design a custom QR code',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="commercial" cluster="bulk-qr" />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-green-50 via-white to-blue-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
@@ -297,7 +328,7 @@ export default function BulkQRCodeGeneratorPage() {
|
||||
<div className="space-y-8">
|
||||
<div className="inline-flex items-center space-x-2 bg-green-100 text-green-800 px-4 py-2 rounded-full text-sm font-semibold">
|
||||
<span>⚡</span>
|
||||
<span>Generate 1000s in Minutes</span>
|
||||
<span>CSV and Excel workflows</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||
@@ -347,7 +378,7 @@ export default function BulkQRCodeGeneratorPage() {
|
||||
<div className="bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg p-8 text-center mb-4">
|
||||
<div className="text-4xl mb-2">📊</div>
|
||||
<p className="text-gray-600 font-medium mb-1">products.xlsx</p>
|
||||
<p className="text-sm text-gray-500">1,247 rows ready</p>
|
||||
<p className="text-sm text-gray-500">Bulk-ready file</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -362,11 +393,11 @@ export default function BulkQRCodeGeneratorPage() {
|
||||
))}
|
||||
</div>
|
||||
<p className="text-center text-sm text-gray-600 mt-4">
|
||||
+ 1,239 more codes
|
||||
Continue with a full batch import
|
||||
</p>
|
||||
</Card>
|
||||
<div className="absolute -top-4 -right-4 bg-green-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg">
|
||||
1000s at Once!
|
||||
Bulk import
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -631,14 +662,23 @@ Product C,https://example.com/product-c,Budget Widget,electronics,sale`}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-green-600 to-blue-600 text-white">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GrowthLinksSection
|
||||
eyebrow="Bulk-ready workflows"
|
||||
title="Where bulk QR creation becomes operationally useful"
|
||||
description="Bulk generation is strongest when one spreadsheet feeds a real deployment workflow across campaigns, events, labels, or repeated placements."
|
||||
links={relatedUseCaseLinks}
|
||||
pageType="commercial"
|
||||
cluster="bulk-qr"
|
||||
/>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-green-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Generate 1000s of QR Codes in Minutes
|
||||
Generate bulk QR codes without one-by-one setup
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-green-100">
|
||||
Save hours of manual work. Upload your file and get all QR codes ready instantly.
|
||||
|
||||
@@ -4,9 +4,12 @@ import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { MarketingPageTracker } from '@/components/marketing/MarketingAnalytics';
|
||||
import { featuredUseCases } from '@/lib/growth-pages';
|
||||
import {
|
||||
Palette,
|
||||
Upload,
|
||||
@@ -287,15 +290,43 @@ export default function CustomQRCodeGeneratorPage() {
|
||||
})),
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Custom QR Code Generator', url: '/custom-qr-code-generator' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Custom QR Code Generator', url: '/custom-qr-code-generator' },
|
||||
];
|
||||
|
||||
const relatedUseCaseLinks = [
|
||||
{
|
||||
href: '/qr-code-for-marketing-campaigns',
|
||||
title: 'QR Codes for Marketing Campaigns',
|
||||
description: 'Connect branded print QR codes to campaign-specific destinations and measurement.',
|
||||
ctaLabel: 'Create a trackable campaign QR',
|
||||
},
|
||||
{
|
||||
href: featuredUseCases[0].href,
|
||||
title: featuredUseCases[0].title,
|
||||
description: featuredUseCases[0].summary,
|
||||
ctaLabel: featuredUseCases[0].ctaLabel,
|
||||
},
|
||||
{
|
||||
href: featuredUseCases[2].href,
|
||||
title: featuredUseCases[2].title,
|
||||
description: featuredUseCases[2].summary,
|
||||
ctaLabel: featuredUseCases[2].ctaLabel,
|
||||
},
|
||||
{
|
||||
href: '/use-cases',
|
||||
title: 'Explore the use-case hub',
|
||||
description: 'Browse QR workflows where design, routing, and measurement need to work together.',
|
||||
ctaLabel: 'Explore QR code use cases',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="commercial" cluster="custom-qr" />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
@@ -621,11 +652,20 @@ export default function CustomQRCodeGeneratorPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GrowthLinksSection
|
||||
eyebrow="Branded workflows"
|
||||
title="Where custom QR design supports the workflow"
|
||||
description="Custom QR codes work best when they support a real business journey, not when they are only decoration. These are the strongest adjacent workflows."
|
||||
links={relatedUseCaseLinks}
|
||||
pageType="commercial"
|
||||
cluster="custom-qr"
|
||||
/>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Creating Branded QR Codes Today
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
||||
import { featuredUseCases } from '@/lib/growth-pages';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
@@ -230,15 +232,43 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||
],
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Dynamic QR Code Generator', url: '/dynamic-qr-code-generator' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Dynamic QR Code Generator', url: '/dynamic-qr-code-generator' },
|
||||
];
|
||||
|
||||
const relatedUseCaseLinks = [
|
||||
{
|
||||
href: featuredUseCases[0].href,
|
||||
title: featuredUseCases[0].title,
|
||||
description: featuredUseCases[0].summary,
|
||||
ctaLabel: featuredUseCases[0].ctaLabel,
|
||||
},
|
||||
{
|
||||
href: featuredUseCases[1].href,
|
||||
title: featuredUseCases[1].title,
|
||||
description: featuredUseCases[1].summary,
|
||||
ctaLabel: featuredUseCases[1].ctaLabel,
|
||||
},
|
||||
{
|
||||
href: featuredUseCases[2].href,
|
||||
title: featuredUseCases[2].title,
|
||||
description: featuredUseCases[2].summary,
|
||||
ctaLabel: featuredUseCases[2].ctaLabel,
|
||||
},
|
||||
{
|
||||
href: '/use-cases',
|
||||
title: 'Explore the use-case hub',
|
||||
description: 'See how dynamic QR workflows connect to commercial pages, tools, and support content.',
|
||||
ctaLabel: 'Explore QR code use cases',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="commercial" cluster="dynamic-qr" />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
@@ -274,21 +304,21 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||
<span className="text-gray-700">{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Create Dynamic QR Code
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
View Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<TrackedCtaLink href="/signup" ctaLabel="Create Dynamic QR Code" ctaLocation="hero_primary" pageType="commercial" cluster="dynamic-qr">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Create Dynamic QR Code
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
<TrackedCtaLink href="/pricing" ctaLabel="View Pricing" ctaLocation="hero_secondary" pageType="commercial" cluster="dynamic-qr">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
View Pricing
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Visual Demo */}
|
||||
<div className="relative">
|
||||
@@ -514,32 +544,41 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Creating Dynamic QR Codes Today
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-purple-100">
|
||||
Join thousands of businesses who never worry about reprinting QR codes again
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-purple-600 hover:bg-gray-100">
|
||||
Get Started Free
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/signup">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||
Create QR Code Now
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GrowthLinksSection
|
||||
eyebrow="Best next workflows"
|
||||
title="See where dynamic QR becomes most useful"
|
||||
description="These are the strongest first workflows for dynamic QR because the printed asset stays the same while the destination or campaign context keeps moving."
|
||||
links={relatedUseCaseLinks}
|
||||
pageType="commercial"
|
||||
cluster="dynamic-qr"
|
||||
/>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Creating Dynamic QR Codes Today
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-purple-100">
|
||||
Use one QR code that can keep working even when the destination behind it needs to change.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<TrackedCtaLink href="/signup" ctaLabel="Get Started Free" ctaLocation="footer_primary" pageType="commercial" cluster="dynamic-qr">
|
||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-purple-600 hover:bg-gray-100">
|
||||
Get Started Free
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
<TrackedCtaLink href="/signup" ctaLabel="Create QR Code Now" ctaLocation="footer_secondary" pageType="commercial" cluster="dynamic-qr">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||
Create QR Code Now
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import type { Metadata } from 'next';
|
||||
import AdBanner from '@/components/ads/AdBanner';
|
||||
import '@/styles/globals.css';
|
||||
import { Providers } from '@/components/Providers';
|
||||
import MarketingLayout from './MarketingLayout';
|
||||
// Import schema functions from library
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||
import type { Metadata } from 'next';
|
||||
import '@/styles/globals.css';
|
||||
import MarketingLayout from './MarketingLayout';
|
||||
// Import schema functions from library
|
||||
import { organizationSchema } from '@/lib/schema';
|
||||
|
||||
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
||||
|
||||
@@ -54,29 +51,20 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function MarketingGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
export default function MarketingGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<MarketingLayout>
|
||||
{children}
|
||||
</MarketingLayout>
|
||||
{/* Global Marketing Ad - Exclusions handled in AdBanner */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
||||
<AdBanner
|
||||
dataAdSlot="2607110637"
|
||||
dataAdFormat="auto"
|
||||
fullWidthResponsive={true}
|
||||
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<MarketingLayout>
|
||||
{children}
|
||||
</MarketingLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,14 +8,20 @@ import { pillarPageSchema, faqPageSchema } from "@/lib/schema";
|
||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||
import { AnswerBox } from "@/components/aeo/AnswerBox";
|
||||
|
||||
export function generateMetadata({ params }: { params: { pillar: string } }) {
|
||||
const meta = pillarMeta.find(p => p.key === params.pillar);
|
||||
if (!meta) return {};
|
||||
return {
|
||||
title: `${meta.title} - Ultimate Guide | QR Master`,
|
||||
description: meta.description
|
||||
};
|
||||
}
|
||||
export function generateMetadata({ params }: { params: { pillar: string } }) {
|
||||
const meta = pillarMeta.find(p => p.key === params.pillar);
|
||||
if (!meta) return {};
|
||||
return {
|
||||
title: `${meta.title} - Ultimate Guide | QR Master`,
|
||||
description: meta.description,
|
||||
alternates: {
|
||||
canonical: `https://www.qrmaster.net/learn/${meta.key}`,
|
||||
},
|
||||
openGraph: {
|
||||
url: `https://www.qrmaster.net/learn/${meta.key}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return pillarMeta.map((pillar) => ({
|
||||
|
||||
@@ -2,10 +2,16 @@ import Link from "next/link";
|
||||
import { pillarMeta } from "@/lib/pillar-data";
|
||||
import { getPublishedPosts } from "@/lib/content";
|
||||
|
||||
export const metadata = {
|
||||
title: "Learn QR Code Mastery | QR Master Hub",
|
||||
description: "Guides, use cases, tracking deep-dives, and security best practices for dynamic QR codes.",
|
||||
};
|
||||
export const metadata = {
|
||||
title: "Learn QR Code Mastery | QR Master Hub",
|
||||
description: "Guides, use cases, tracking deep-dives, and security best practices for dynamic QR codes.",
|
||||
alternates: {
|
||||
canonical: "https://www.qrmaster.net/learn",
|
||||
},
|
||||
openGraph: {
|
||||
url: "https://www.qrmaster.net/learn",
|
||||
},
|
||||
};
|
||||
|
||||
export default function LearnHubPage() {
|
||||
const posts = getPublishedPosts();
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import {
|
||||
buildUseCaseMetadata,
|
||||
UseCasePageTemplate,
|
||||
} from "@/components/marketing/UseCasePageTemplate";
|
||||
|
||||
export const metadata: Metadata = buildUseCaseMetadata({
|
||||
title: "QR Codes for Marketing Campaigns",
|
||||
description:
|
||||
"Plan QR codes for marketing campaigns around placement tracking, changing destinations, and offline-to-online attribution.",
|
||||
canonicalPath: "/qr-code-for-marketing-campaigns",
|
||||
});
|
||||
|
||||
export default function QRCodeForMarketingCampaignsPage() {
|
||||
return (
|
||||
<UseCasePageTemplate
|
||||
title="QR Codes for Marketing Campaigns"
|
||||
description="Plan QR codes for marketing campaigns around placement tracking, changing destinations, and offline-to-online attribution."
|
||||
eyebrow="Campaign Workflows"
|
||||
intro="Marketing campaign QR codes work best when the code on the printed asset stays stable while the destination and attribution model can evolve with the campaign."
|
||||
pageType="commercial"
|
||||
cluster="marketing-campaigns"
|
||||
useCase="marketing-campaigns"
|
||||
breadcrumbs={[
|
||||
{ name: "Home", url: "/" },
|
||||
{
|
||||
name: "QR Codes for Marketing Campaigns",
|
||||
url: "/qr-code-for-marketing-campaigns",
|
||||
},
|
||||
]}
|
||||
answer="A campaign QR code should do more than open a page. It should help you compare placements, update the destination when the offer changes, and route offline traffic into a measurable funnel."
|
||||
whenToUse={[
|
||||
"You run flyers, posters, packaging inserts, or event signage with campaign-specific CTA copy.",
|
||||
"You want to compare placements or creatives instead of treating every scan as generic traffic.",
|
||||
"Your destination may change during the life of the printed campaign.",
|
||||
]}
|
||||
comparisonItems={[
|
||||
{ label: "Offer updates", text: "New print required", value: true },
|
||||
{ label: "Placement attribution", text: "Often manual", value: true },
|
||||
{ label: "Creative testing", text: "Hard to manage", value: true },
|
||||
]}
|
||||
howToSteps={[
|
||||
"Create campaign QR flows around one clear action and one named placement context.",
|
||||
"Use dynamic destinations or tagged URLs so the print stays usable when the offer changes.",
|
||||
"Measure scans with a clean CTA path into signup, lead capture, or campaign landing pages.",
|
||||
]}
|
||||
primaryCta={{
|
||||
href: "/dynamic-qr-code-generator",
|
||||
label: "Create a trackable campaign QR",
|
||||
}}
|
||||
secondaryCta={{
|
||||
href: "/use-cases",
|
||||
label: "Browse use-case workflows",
|
||||
}}
|
||||
workflowTitle="What strong campaign QR workflows look like"
|
||||
workflowIntro="Campaign QR strategy becomes more useful when creative, placement, and destination are treated as a system rather than a single link printed everywhere."
|
||||
workflowCards={[
|
||||
{
|
||||
title: "Placement-aware routing",
|
||||
description: "Keep banner, flyer, packaging, and in-store placements comparable by using distinct destinations or campaign tags.",
|
||||
},
|
||||
{
|
||||
title: "Post-print flexibility",
|
||||
description: "Adjust the landing page, offer, or CTA destination after print when the campaign learns something or needs a fast update.",
|
||||
},
|
||||
{
|
||||
title: "Measurement-ready handoff",
|
||||
description: "Push campaign scans toward signup, booking, or lead-gen paths so the QR is tied to a business outcome instead of a vanity click.",
|
||||
},
|
||||
]}
|
||||
checklistTitle="Campaign QR checklist"
|
||||
checklist={[
|
||||
"Match each QR code to one campaign purpose and one primary CTA.",
|
||||
"Differentiate placements with clean naming or URL tagging before the assets go to print.",
|
||||
"Use a destination you can update when the promotion, offer, or landing page changes.",
|
||||
"Link the campaign flow back to a measured CTA path instead of stopping at raw scan counts.",
|
||||
]}
|
||||
supportLinks={[
|
||||
{
|
||||
href: "/qr-code-tracking",
|
||||
title: "QR Code Tracking",
|
||||
description: "Use when the real priority is measuring placement and scanner context.",
|
||||
},
|
||||
{
|
||||
href: "/custom-qr-code-generator",
|
||||
title: "Custom QR Code Generator",
|
||||
description: "Useful when brand fit and print creative need more control.",
|
||||
},
|
||||
{
|
||||
href: "/blog/utm-parameter-qr-codes",
|
||||
title: "UTM Parameters with QR Codes",
|
||||
description: "Support article for placement naming and campaign attribution strategy.",
|
||||
},
|
||||
]}
|
||||
faq={[
|
||||
{
|
||||
question: "Why use QR codes in marketing campaigns?",
|
||||
answer: "Campaign QR codes help move offline audiences into a measurable online path. They are most useful when the destination and tracking setup are planned before the assets go live.",
|
||||
},
|
||||
{
|
||||
question: "Should campaign QR codes be dynamic?",
|
||||
answer: "Yes, when the destination, offer, or campaign landing page may change after print. That avoids replacing materials just because the target page changes.",
|
||||
},
|
||||
{
|
||||
question: "How do I track different QR placements in one campaign?",
|
||||
answer: "Use distinct destinations or tagged URLs for each placement so flyers, posters, booth signs, and packaging inserts can be compared cleanly.",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||
import { breadcrumbSchema } from '@/lib/schema';
|
||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
||||
import { featuredUseCases } from '@/lib/growth-pages';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
@@ -164,15 +166,43 @@ export default function QRCodeTrackingPage() {
|
||||
],
|
||||
};
|
||||
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'QR Code Tracking', url: '/qr-code-tracking' },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<div className="min-h-screen bg-white">
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'QR Code Tracking', url: '/qr-code-tracking' },
|
||||
];
|
||||
|
||||
const relatedUseCaseLinks = [
|
||||
{
|
||||
href: featuredUseCases[2].href,
|
||||
title: featuredUseCases[2].title,
|
||||
description: featuredUseCases[2].summary,
|
||||
ctaLabel: featuredUseCases[2].ctaLabel,
|
||||
},
|
||||
{
|
||||
href: '/qr-code-for-marketing-campaigns',
|
||||
title: 'QR Codes for Marketing Campaigns',
|
||||
description: 'Map scans to campaign placements, creative tests, and offline-to-online attribution.',
|
||||
ctaLabel: 'Create a trackable campaign QR',
|
||||
},
|
||||
{
|
||||
href: featuredUseCases[0].href,
|
||||
title: featuredUseCases[0].title,
|
||||
description: featuredUseCases[0].summary,
|
||||
ctaLabel: featuredUseCases[0].ctaLabel,
|
||||
},
|
||||
{
|
||||
href: '/use-cases',
|
||||
title: 'Explore the use-case hub',
|
||||
description: 'Browse the first commercial workflows built around dynamic updates and measurable scans.',
|
||||
ctaLabel: 'Explore QR code use cases',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[softwareSchema, howToSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="commercial" cluster="qr-tracking" />
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
@@ -190,20 +220,20 @@ export default function QRCodeTrackingPage() {
|
||||
|
||||
<p className="text-xl text-gray-600 leading-relaxed">
|
||||
Monitor your QR code performance in real-time. Get detailed insights on location, device, time, and user behavior. Make data-driven decisions with our free tracking software.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Start Tracking Free
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/signup">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Create Trackable QR Code
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<TrackedCtaLink href="/signup" ctaLabel="Start Tracking Free" ctaLocation="hero_primary" pageType="commercial" cluster="qr-tracking">
|
||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Start Tracking Free
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
<TrackedCtaLink href="/signup" ctaLabel="Create Trackable QR Code" ctaLocation="hero_secondary" pageType="commercial" cluster="qr-tracking">
|
||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||
Create Trackable QR Code
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-6 text-sm text-gray-600">
|
||||
<div className="flex items-center space-x-2">
|
||||
@@ -212,13 +242,13 @@ export default function QRCodeTrackingPage() {
|
||||
</svg>
|
||||
<span>No credit card required</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<svg className="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span>Unlimited scans</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<svg className="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span>Placement-ready reports</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Analytics Preview */}
|
||||
@@ -227,20 +257,20 @@ export default function QRCodeTrackingPage() {
|
||||
<h3 className="font-semibold text-lg mb-4">Live Analytics Dashboard</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center pb-3 border-b">
|
||||
<span className="text-gray-600">Total Scans</span>
|
||||
<span className="text-2xl font-bold text-primary-600">12,547</span>
|
||||
<span className="text-gray-600">Placement view</span>
|
||||
<span className="text-base font-semibold text-primary-600">Flyer vs booth vs table card</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pb-3 border-b">
|
||||
<span className="text-gray-600">Unique Users</span>
|
||||
<span className="text-2xl font-bold text-primary-600">8,392</span>
|
||||
<span className="text-gray-600">Time trend</span>
|
||||
<span className="text-base font-semibold text-primary-600">Lunch, event day, or campaign burst</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pb-3 border-b">
|
||||
<span className="text-gray-600">Top Location</span>
|
||||
<span className="font-semibold">🇩🇪 Germany</span>
|
||||
<span className="text-gray-600">Location context</span>
|
||||
<span className="font-semibold">Region and city level view</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-600">Top Device</span>
|
||||
<span className="font-semibold">📱 iPhone</span>
|
||||
<span className="text-gray-600">Device context</span>
|
||||
<span className="font-semibold">Phone, desktop, and scan mix</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -318,9 +348,9 @@ export default function QRCodeTrackingPage() {
|
||||
</section>
|
||||
|
||||
{/* Comparison Table */}
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<section className="py-20 bg-gray-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
QR Master vs Free Tools
|
||||
</h2>
|
||||
@@ -364,33 +394,42 @@ export default function QRCodeTrackingPage() {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-primary-600 to-purple-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Tracking Your QR Codes Today
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-primary-100">
|
||||
Join thousands of businesses using QR Master to track and optimize their QR code campaigns
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href="/signup">
|
||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-primary-600 hover:bg-gray-100">
|
||||
Create Free Account
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||
View Pricing
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GrowthLinksSection
|
||||
eyebrow="Tracking-led workflows"
|
||||
title="Where scan visibility matters most"
|
||||
description="These are the workflows where scan context, placement comparison, and destination flexibility make QR tracking materially more useful."
|
||||
links={relatedUseCaseLinks}
|
||||
pageType="commercial"
|
||||
cluster="qr-tracking"
|
||||
/>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 bg-gradient-to-r from-primary-600 to-purple-600 text-white">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||
<h2 className="text-4xl font-bold mb-6">
|
||||
Start Tracking Your QR Codes Today
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-primary-100">
|
||||
Measure scans with enough context to improve the next placement, campaign, or printed workflow.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<TrackedCtaLink href="/signup" ctaLabel="Create Free Account" ctaLocation="footer_primary" pageType="commercial" cluster="qr-tracking">
|
||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-primary-600 hover:bg-gray-100">
|
||||
Create Free Account
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
<TrackedCtaLink href="/pricing" ctaLabel="View Pricing" ctaLocation="footer_secondary" pageType="commercial" cluster="qr-tracking">
|
||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||
View Pricing
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
78
src/app/(main)/(marketing)/use-cases/[slug]/page.tsx
Normal file
78
src/app/(main)/(marketing)/use-cases/[slug]/page.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import {
|
||||
buildUseCaseMetadata,
|
||||
UseCasePageTemplate,
|
||||
} from "@/components/marketing/UseCasePageTemplate";
|
||||
import {
|
||||
featuredUseCases,
|
||||
getUseCasePage,
|
||||
} from "@/lib/growth-pages";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return featuredUseCases.map((item) => ({
|
||||
slug: item.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
const page = getUseCasePage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return buildUseCaseMetadata({
|
||||
title: page.title,
|
||||
description: page.metaDescription,
|
||||
canonicalPath: page.href,
|
||||
});
|
||||
}
|
||||
|
||||
export default function UseCaseDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}) {
|
||||
const page = getUseCasePage(params.slug);
|
||||
|
||||
if (!page) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<UseCasePageTemplate
|
||||
title={page.title}
|
||||
description={page.metaDescription}
|
||||
eyebrow={page.eyebrow}
|
||||
intro={page.intro}
|
||||
pageType="use_case"
|
||||
cluster={page.cluster}
|
||||
useCase={page.slug}
|
||||
breadcrumbs={[
|
||||
{ name: "Home", url: "/" },
|
||||
{ name: "Use Cases", url: "/use-cases" },
|
||||
{ name: page.title, url: page.href },
|
||||
]}
|
||||
answer={page.answer}
|
||||
whenToUse={page.whenToUse}
|
||||
comparisonItems={page.comparisonItems}
|
||||
howToSteps={page.howToSteps}
|
||||
primaryCta={{
|
||||
href: page.parentHref,
|
||||
label: page.ctaLabel,
|
||||
}}
|
||||
secondaryCta={{
|
||||
href: "/use-cases",
|
||||
label: "Explore more use cases",
|
||||
}}
|
||||
workflowTitle={page.workflowTitle}
|
||||
workflowIntro={page.workflowIntro}
|
||||
workflowCards={page.workflowCards}
|
||||
checklistTitle={page.checklistTitle}
|
||||
checklist={page.checklist}
|
||||
supportLinks={page.supportLinks}
|
||||
faq={page.faq}
|
||||
/>
|
||||
);
|
||||
}
|
||||
303
src/app/(main)/(marketing)/use-cases/page.tsx
Normal file
303
src/app/(main)/(marketing)/use-cases/page.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import Link from "next/link";
|
||||
import {
|
||||
ArrowRight,
|
||||
Compass,
|
||||
LibraryBig,
|
||||
Link2,
|
||||
Route,
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
|
||||
import Breadcrumbs, { BreadcrumbItem } from "@/components/Breadcrumbs";
|
||||
import SeoJsonLd from "@/components/SeoJsonLd";
|
||||
import {
|
||||
MarketingPageTracker,
|
||||
TrackedCtaLink,
|
||||
} from "@/components/marketing/MarketingAnalytics";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Card } from "@/components/ui/Card";
|
||||
import {
|
||||
commercialPages,
|
||||
featuredUseCases,
|
||||
supportResources,
|
||||
upcomingUseCaseIdeas,
|
||||
} from "@/lib/growth-pages";
|
||||
import { breadcrumbSchema } from "@/lib/schema";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
absolute: "QR Code Use Cases for Business | QR Master",
|
||||
},
|
||||
description:
|
||||
"Explore QR code use cases for restaurants, events, business cards, and campaign workflows built around dynamic updates and tracking.",
|
||||
alternates: {
|
||||
canonical: "https://www.qrmaster.net/use-cases",
|
||||
languages: {
|
||||
"x-default": "https://www.qrmaster.net/use-cases",
|
||||
en: "https://www.qrmaster.net/use-cases",
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
title: "QR Code Use Cases for Business | QR Master",
|
||||
description:
|
||||
"Explore QR code use cases for restaurants, events, business cards, and campaign workflows built around dynamic updates and tracking.",
|
||||
url: "https://www.qrmaster.net/use-cases",
|
||||
type: "website",
|
||||
images: ["/og-image.png"],
|
||||
},
|
||||
twitter: {
|
||||
title: "QR Code Use Cases for Business | QR Master",
|
||||
description:
|
||||
"Explore QR code use cases for restaurants, events, business cards, and campaign workflows built around dynamic updates and tracking.",
|
||||
},
|
||||
};
|
||||
|
||||
export default function UseCasesHubPage() {
|
||||
const breadcrumbItems: BreadcrumbItem[] = [
|
||||
{ name: "Home", url: "/" },
|
||||
{ name: "Use Cases", url: "/use-cases" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoJsonLd data={[breadcrumbSchema(breadcrumbItems)]} />
|
||||
<MarketingPageTracker pageType="use_case_hub" cluster="all-use-cases" />
|
||||
|
||||
<div className="min-h-screen bg-white">
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-slate-950 via-blue-950 to-cyan-950 text-white">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(125,211,252,0.18),transparent_34%),radial-gradient(circle_at_right,rgba(255,255,255,0.06),transparent_28%)]" />
|
||||
<div className="relative container mx-auto max-w-7xl px-4 py-20 sm:px-6 lg:px-8">
|
||||
<Breadcrumbs
|
||||
items={breadcrumbItems}
|
||||
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>Commercial use-case hub</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">
|
||||
QR code use cases that fit real business workflows
|
||||
</h1>
|
||||
<p className="max-w-3xl text-lg leading-8 text-blue-50/88 md:text-xl">
|
||||
This hub focuses on workflows where dynamic updates and
|
||||
measurement matter. It is not a list of random QR ideas. It
|
||||
is the commercial layer between QR Master's product pages,
|
||||
tools, and editorial content.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 text-sm text-blue-50/80 sm:grid-cols-2">
|
||||
{[
|
||||
"Use-case pages map back to a clear commercial parent.",
|
||||
"Each workflow is written for practical deployment, not filler traffic.",
|
||||
"Support resources reinforce the wedge around dynamic and trackable QR flows.",
|
||||
"The next cluster expansion will build on measurable routing and internal links.",
|
||||
].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"
|
||||
>
|
||||
<Route 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={featuredUseCases[0].href}
|
||||
ctaLabel="Explore restaurant menu QR codes"
|
||||
ctaLocation="hero_primary"
|
||||
pageType="use_case_hub"
|
||||
cluster="all-use-cases"
|
||||
>
|
||||
<Button size="lg" className="w-full bg-white px-8 py-4 text-slate-950 hover:bg-slate-100 sm:w-auto">
|
||||
Explore featured workflows
|
||||
</Button>
|
||||
</TrackedCtaLink>
|
||||
|
||||
<TrackedCtaLink
|
||||
href="/qr-code-for-marketing-campaigns"
|
||||
ctaLabel="View marketing campaign QR page"
|
||||
ctaLocation="hero_secondary"
|
||||
pageType="use_case_hub"
|
||||
cluster="all-use-cases"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="w-full border-white/30 bg-white/5 px-8 py-4 text-white hover:bg-white/10 sm:w-auto"
|
||||
>
|
||||
See campaign workflows
|
||||
</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-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<Compass className="h-5 w-5 text-cyan-300" />
|
||||
<h2 className="text-2xl font-bold">How to use this hub</h2>
|
||||
</div>
|
||||
<div className="space-y-4 text-sm leading-6 text-blue-50/82">
|
||||
<p>
|
||||
Start with the workflow problem, not the QR format. If the
|
||||
printed code needs to survive destination changes or you
|
||||
need proof of performance, begin with the use case that
|
||||
matches that job.
|
||||
</p>
|
||||
<p>
|
||||
Each page below links back to the best product parent,
|
||||
forward to related workflows, and sideways to educational
|
||||
resources that help you deploy the QR well.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="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">
|
||||
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
|
||||
Featured use cases
|
||||
</div>
|
||||
<h2 className="mt-3 text-3xl font-bold text-slate-900">
|
||||
First workflows in the growth rollout
|
||||
</h2>
|
||||
<p className="mt-4 text-lg leading-8 text-slate-600">
|
||||
These are the first routes worth surfacing because they connect
|
||||
cleanly to QR Master's strongest product angles and existing
|
||||
supporting content.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-3">
|
||||
{featuredUseCases.map((page) => (
|
||||
<Link key={page.slug} href={page.href} className="group block">
|
||||
<Card className="flex h-full flex-col rounded-3xl border-slate-200 bg-white p-7 shadow-sm transition-all hover:-translate-y-1 hover:shadow-lg">
|
||||
<div className="text-sm font-semibold uppercase tracking-[0.18em] text-blue-700">
|
||||
{page.cluster}
|
||||
</div>
|
||||
<h3 className="mt-4 text-2xl font-bold text-slate-900">
|
||||
{page.title}
|
||||
</h3>
|
||||
<p className="mt-4 flex-1 text-base leading-7 text-slate-600">
|
||||
{page.summary}
|
||||
</p>
|
||||
<div className="mt-6 flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 text-sm text-slate-600">
|
||||
<span>Primary parent: {page.parentTitle}</span>
|
||||
<ArrowRight className="h-4 w-4 text-blue-700 transition-transform group-hover:translate-x-1" />
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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="grid gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,0.95fr)]">
|
||||
<Card className="rounded-3xl border-slate-200 bg-white p-8 shadow-sm">
|
||||
<div className="flex items-center gap-3">
|
||||
<LibraryBig className="h-5 w-5 text-blue-700" />
|
||||
<h2 className="text-2xl font-bold text-slate-900">
|
||||
Commercial pages that anchor the hub
|
||||
</h2>
|
||||
</div>
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
||||
{commercialPages.map((page) => (
|
||||
<Link
|
||||
key={page.href}
|
||||
href={page.href}
|
||||
className="rounded-2xl border border-slate-200 p-4 transition-colors hover:border-blue-200 hover:bg-blue-50/60"
|
||||
>
|
||||
<div className={`h-1.5 rounded-full bg-gradient-to-r ${page.accent}`} />
|
||||
<div className="mt-4 text-lg font-semibold text-slate-900">
|
||||
{page.title}
|
||||
</div>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-600">
|
||||
{page.description}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</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">Support resources</h2>
|
||||
</div>
|
||||
<div className="mt-6 space-y-4">
|
||||
{supportResources.map((resource) => (
|
||||
<Link
|
||||
key={resource.href}
|
||||
href={resource.href}
|
||||
className="block rounded-2xl border border-white/10 bg-white/5 p-4 transition-colors hover:bg-white/10"
|
||||
>
|
||||
<div className="text-lg font-semibold text-white">
|
||||
{resource.title}
|
||||
</div>
|
||||
<p className="mt-2 text-sm leading-6 text-blue-50/78">
|
||||
{resource.description}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="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">
|
||||
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
|
||||
Next cluster candidates
|
||||
</div>
|
||||
<h2 className="mt-3 text-3xl font-bold text-slate-900">
|
||||
What follows after the first use-case wave
|
||||
</h2>
|
||||
<p className="mt-4 text-lg leading-8 text-slate-600">
|
||||
These are not published use-case routes yet. They are the next
|
||||
practical cluster expansions once the first hub and CTA layer are
|
||||
established.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
{upcomingUseCaseIdeas.map((item) => (
|
||||
<Card
|
||||
key={item.title}
|
||||
className="rounded-3xl border-dashed border-slate-300 bg-slate-50 p-7"
|
||||
>
|
||||
<div className="text-xl font-semibold text-slate-900">
|
||||
{item.title}
|
||||
</div>
|
||||
<p className="mt-3 text-base leading-7 text-slate-600">
|
||||
{item.description}
|
||||
</p>
|
||||
<div className="mt-5 text-sm font-semibold text-blue-700">
|
||||
Anchored by {item.href.replace("/", "")}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Suspense } from 'react';
|
||||
import '@/styles/globals.css';
|
||||
import { Providers } from '@/components/Providers';
|
||||
import MarketingDeLayout from './MarketingDeLayout';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import AdSenseScript from '@/components/ads/AdSenseScript';
|
||||
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||
import { Suspense } from 'react';
|
||||
import '@/styles/globals.css';
|
||||
import { Providers } from '@/components/Providers';
|
||||
import MarketingDeLayout from './MarketingDeLayout';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
@@ -55,46 +54,33 @@ export const metadata: Metadata = {
|
||||
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
import AdBanner from '@/components/ads/AdBanner'; // Import AdBanner
|
||||
|
||||
export default function MarketingDeGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
};
|
||||
|
||||
export default function MarketingDeGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="de">
|
||||
<body className="font-sans">
|
||||
<Suspense fallback={null}>
|
||||
<Providers>
|
||||
<AdSenseScript />
|
||||
<FacebookPixel />
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
<body className="font-sans">
|
||||
<Suspense fallback={null}>
|
||||
<Providers>
|
||||
<FacebookPixel />
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
||||
/>
|
||||
<MarketingDeLayout>
|
||||
{children}
|
||||
|
||||
{/* Global Marketing Ad - Exclusions handled in AdBanner */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
||||
<AdBanner
|
||||
dataAdSlot="2607110637"
|
||||
dataAdFormat="auto"
|
||||
fullWidthResponsive={true}
|
||||
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
||||
/>
|
||||
</div>
|
||||
</MarketingDeLayout>
|
||||
</Providers>
|
||||
</Suspense>
|
||||
</body>
|
||||
/>
|
||||
<MarketingDeLayout>
|
||||
{children}
|
||||
</MarketingDeLayout>
|
||||
</Providers>
|
||||
</Suspense>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,20 +53,53 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
}));
|
||||
|
||||
// Learn hub and pillar pages
|
||||
const learnPages = [
|
||||
{
|
||||
url: `${baseUrl}/learn`,
|
||||
const learnPages = [
|
||||
{
|
||||
url: `${baseUrl}/learn`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly' as const,
|
||||
priority: 0.9,
|
||||
},
|
||||
...pillarMeta.map((pillar) => ({
|
||||
url: `${baseUrl}/learn/${pillar.key}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
];
|
||||
...pillarMeta.map((pillar) => ({
|
||||
url: `${baseUrl}/learn/${pillar.key}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
];
|
||||
|
||||
const growthUseCasePages = [
|
||||
{
|
||||
url: `${baseUrl}/use-cases`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly' as const,
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/use-cases/restaurant-menu-qr-codes`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/use-cases/business-card-qr-codes`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/use-cases/event-qr-codes`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/qr-code-for-marketing-campaigns`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.85,
|
||||
},
|
||||
];
|
||||
|
||||
// Author pages
|
||||
const authorPages = authors.map((author) => ({
|
||||
@@ -189,10 +222,11 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
},
|
||||
|
||||
|
||||
...toolPages,
|
||||
...blogPages,
|
||||
...learnPages,
|
||||
...authorPages,
|
||||
];
|
||||
}
|
||||
...toolPages,
|
||||
...blogPages,
|
||||
...learnPages,
|
||||
...growthUseCasePages,
|
||||
...authorPages,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
73
src/components/marketing/GrowthLinksSection.tsx
Normal file
73
src/components/marketing/GrowthLinksSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
94
src/components/marketing/MarketingAnalytics.tsx
Normal file
94
src/components/marketing/MarketingAnalytics.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
427
src/components/marketing/UseCasePageTemplate.tsx
Normal file
427
src/components/marketing/UseCasePageTemplate.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -48,7 +48,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -98,7 +98,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -148,7 +148,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -197,7 +197,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -246,7 +246,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> October 18, 2025 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> October 18, 2025 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
<p>Coming soon: How to create trackable QR codes.</p></div>`,
|
||||
@@ -291,7 +291,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> October 17, 2025 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> October 17, 2025 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -339,7 +339,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> October 16, 2025 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> October 16, 2025 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -392,7 +392,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> October 16, 2025 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> October 16, 2025 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -446,7 +446,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> January 29, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> January 29, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
<h2>Free Barcode Generator</h2><p>Content coming soon.</p></div>`
|
||||
@@ -499,7 +499,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 1, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 1, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -609,7 +609,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 4, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 4, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -719,7 +719,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 7, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 7, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -839,7 +839,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 10, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 10, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -867,13 +867,6 @@ export const blogPosts: BlogPost[] = [
|
||||
<li>Best for “permanent” usage like Wi-Fi credentials</li>
|
||||
</ul>
|
||||
|
||||
<h3>Trackable / dynamic QR code</h3>
|
||||
<ul>
|
||||
<li>Encodes a short redirect URL</li>
|
||||
<li>Redirect logs scan events</li>
|
||||
<li>Destination can be updated</li>
|
||||
<li>Perfect for campaigns and printed materials</li>
|
||||
</ul>
|
||||
<h3>Trackable / dynamic QR code</h3>
|
||||
<ul>
|
||||
<li>Encodes a short redirect URL</li>
|
||||
@@ -984,7 +977,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 13, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 13, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1168,7 +1161,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 16, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 16, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1321,7 +1314,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 19, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 19, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1484,7 +1477,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 22, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 22, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
<h2>Design Tips</h2><p>Content coming soon.</p></div>`
|
||||
@@ -1533,7 +1526,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 25, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 25, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1716,7 +1709,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> February 28, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> February 28, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1859,7 +1852,7 @@ export const blogPosts: BlogPost[] = [
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> March 3, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> March 3, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2056,7 +2049,7 @@ Authorization: Bearer YOUR_API_KEY</code></pre>
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> March 6, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> March 6, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2219,7 +2212,7 @@ Authorization: Bearer YOUR_API_KEY</code></pre>
|
||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||
<p class="text-sm text-gray-700">
|
||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||
📅 <strong>Published:</strong> March 9, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
||||
📅 <strong>Published:</strong> March 9, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,7 +17,11 @@ export function getPostBySlug(slug: string): BlogPost | undefined {
|
||||
|
||||
export function getPublishedPostBySlug(slug: string): BlogPost | undefined {
|
||||
const p = getPostBySlug(slug);
|
||||
return p?.published ? p : undefined;
|
||||
if (!p?.published) return undefined;
|
||||
|
||||
const currentDate = new Date();
|
||||
const publishDate = p.datePublished ? new Date(p.datePublished) : new Date(p.date);
|
||||
return publishDate <= currentDate ? p : undefined;
|
||||
}
|
||||
|
||||
export function getPostsByPillar(pillar: PillarKey): BlogPost[] {
|
||||
|
||||
402
src/lib/growth-pages.ts
Normal file
402
src/lib/growth-pages.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
import type { FAQItem } from "@/lib/types";
|
||||
|
||||
export type CommercialPageLink = {
|
||||
href: string;
|
||||
title: string;
|
||||
description: string;
|
||||
accent: string;
|
||||
};
|
||||
|
||||
export type UseCaseLink = {
|
||||
slug: string;
|
||||
href: string;
|
||||
title: string;
|
||||
cluster: string;
|
||||
summary: string;
|
||||
parentHref: string;
|
||||
parentTitle: string;
|
||||
ctaLabel: string;
|
||||
};
|
||||
|
||||
export type SupportResourceLink = {
|
||||
href: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type UseCasePageContent = UseCaseLink & {
|
||||
eyebrow: string;
|
||||
titleSuffix: string;
|
||||
metaDescription: string;
|
||||
intro: string;
|
||||
answer: string;
|
||||
whenToUse: string[];
|
||||
comparisonItems: {
|
||||
label: string;
|
||||
value: boolean;
|
||||
text?: string;
|
||||
}[];
|
||||
howToSteps: string[];
|
||||
workflowTitle: string;
|
||||
workflowIntro: string;
|
||||
workflowCards: {
|
||||
title: string;
|
||||
description: string;
|
||||
}[];
|
||||
checklistTitle: string;
|
||||
checklist: string[];
|
||||
supportLinks: SupportResourceLink[];
|
||||
faq: FAQItem[];
|
||||
};
|
||||
|
||||
export const commercialPages: CommercialPageLink[] = [
|
||||
{
|
||||
href: "/dynamic-qr-code-generator",
|
||||
title: "Dynamic QR Code Generator",
|
||||
description: "Edit the destination after print and keep one QR live across changing campaigns.",
|
||||
accent: "from-blue-600 to-cyan-500",
|
||||
},
|
||||
{
|
||||
href: "/qr-code-tracking",
|
||||
title: "QR Code Tracking",
|
||||
description: "Measure scans by placement, device, and timing when you need proof instead of guesswork.",
|
||||
accent: "from-indigo-600 to-blue-500",
|
||||
},
|
||||
{
|
||||
href: "/custom-qr-code-generator",
|
||||
title: "Custom QR Code Generator",
|
||||
description: "Match printed QR codes to your brand system without losing scannability.",
|
||||
accent: "from-sky-600 to-indigo-500",
|
||||
},
|
||||
{
|
||||
href: "/bulk-qr-code-generator",
|
||||
title: "Bulk QR Code Generator",
|
||||
description: "Create large sets for labels, event materials, and repeatable offline workflows.",
|
||||
accent: "from-cyan-600 to-sky-500",
|
||||
},
|
||||
{
|
||||
href: "/qr-code-for-marketing-campaigns",
|
||||
title: "QR Codes for Marketing Campaigns",
|
||||
description: "Plan campaign QR workflows around attribution, creative testing, and print distribution.",
|
||||
accent: "from-slate-800 to-blue-600",
|
||||
},
|
||||
];
|
||||
|
||||
export const featuredUseCases: UseCaseLink[] = [
|
||||
{
|
||||
slug: "restaurant-menu-qr-codes",
|
||||
href: "/use-cases/restaurant-menu-qr-codes",
|
||||
title: "Restaurant Menu QR Codes",
|
||||
cluster: "restaurants",
|
||||
summary: "Keep printed table cards useful when menu links, prices, specials, or hours change.",
|
||||
parentHref: "/dynamic-qr-code-generator",
|
||||
parentTitle: "Dynamic QR Code Generator",
|
||||
ctaLabel: "Create your restaurant menu QR",
|
||||
},
|
||||
{
|
||||
slug: "business-card-qr-codes",
|
||||
href: "/use-cases/business-card-qr-codes",
|
||||
title: "Business Card QR Codes",
|
||||
cluster: "business-cards",
|
||||
summary: "Send contacts to a current profile, booking page, or vCard without reprinting cards.",
|
||||
parentHref: "/dynamic-qr-code-generator",
|
||||
parentTitle: "Dynamic QR Code Generator",
|
||||
ctaLabel: "Create your business card QR",
|
||||
},
|
||||
{
|
||||
slug: "event-qr-codes",
|
||||
href: "/use-cases/event-qr-codes",
|
||||
title: "Event QR Codes",
|
||||
cluster: "events",
|
||||
summary: "Split operational and campaign QR flows so schedules and tracking stay manageable on event day.",
|
||||
parentHref: "/qr-code-tracking",
|
||||
parentTitle: "QR Code Tracking",
|
||||
ctaLabel: "Create your event QR code",
|
||||
},
|
||||
];
|
||||
|
||||
export const upcomingUseCaseIdeas: SupportResourceLink[] = [
|
||||
{
|
||||
href: "/bulk-qr-code-generator",
|
||||
title: "Packaging and label workflows",
|
||||
description: "Best next cluster for bulk creation, manuals, inserts, and recurring product updates.",
|
||||
},
|
||||
{
|
||||
href: "/qr-code-for-marketing-campaigns",
|
||||
title: "Flyer and brochure campaigns",
|
||||
description: "Strong print-intent cluster once the marketing-campaign commercial parent is live.",
|
||||
},
|
||||
{
|
||||
href: "/qr-code-tracking",
|
||||
title: "Feedback and coupon journeys",
|
||||
description: "Good second-wave pages once tracking and CTA attribution are stable.",
|
||||
},
|
||||
];
|
||||
|
||||
export const supportResources: SupportResourceLink[] = [
|
||||
{
|
||||
href: "/learn/use-cases",
|
||||
title: "Learn: Use Cases",
|
||||
description: "Editorial pillar page for educational browsing and broader QR workflow discovery.",
|
||||
},
|
||||
{
|
||||
href: "/blog/dynamic-vs-static-qr-codes",
|
||||
title: "Dynamic vs Static QR Codes",
|
||||
description: "Explainer for the operational difference between fixed and editable QR destinations.",
|
||||
},
|
||||
{
|
||||
href: "/blog/utm-parameter-qr-codes",
|
||||
title: "UTM Parameters with QR Codes",
|
||||
description: "Reference guide for campaign naming, placement attribution, and offline measurement.",
|
||||
},
|
||||
];
|
||||
|
||||
export const useCasePageContent: Record<string, UseCasePageContent> = {
|
||||
"restaurant-menu-qr-codes": {
|
||||
...featuredUseCases[0],
|
||||
eyebrow: "Restaurants",
|
||||
titleSuffix: "for Restaurants, Cafes, and Changing Menus",
|
||||
metaDescription:
|
||||
"Use restaurant menu QR codes to keep printed table cards useful when menu links, pricing, or specials change.",
|
||||
intro:
|
||||
"Restaurant menu QR codes work best when the printed code stays the same but the menu destination can change as your service changes.",
|
||||
answer:
|
||||
"A restaurant menu QR code should point to a mobile-friendly menu that you can update without replacing every printed card, flyer, or table tent.",
|
||||
whenToUse: [
|
||||
"Your menu link changes seasonally, weekly, or during service.",
|
||||
"You want one printed QR on tables, windows, takeaway inserts, or flyers.",
|
||||
"You need to route customers to the right menu, order flow, or special page without reprinting.",
|
||||
],
|
||||
comparisonItems: [
|
||||
{ label: "Menu destination", text: "Fixed once printed", value: true },
|
||||
{ label: "Last-minute updates", text: "Reprint required", value: true },
|
||||
{ label: "Campaign tracking", text: "Limited", value: true },
|
||||
],
|
||||
howToSteps: [
|
||||
"Create one dynamic menu QR and place it on your printed surfaces.",
|
||||
"Send scanners to your current menu, order page, or daily specials page.",
|
||||
"Update the destination when the menu or campaign changes instead of replacing the code.",
|
||||
],
|
||||
workflowTitle: "What a good restaurant QR setup should handle",
|
||||
workflowIntro:
|
||||
"The QR code is not the product. The workflow behind it is. A good restaurant setup keeps print stable while operations stay flexible.",
|
||||
workflowCards: [
|
||||
{
|
||||
title: "Stable table cards",
|
||||
description: "Keep one printed QR on tables and point it to the current menu, lunch card, or ordering page.",
|
||||
},
|
||||
{
|
||||
title: "Seasonal offers",
|
||||
description: "Swap specials, tasting menus, or holiday landing pages without touching the printed material.",
|
||||
},
|
||||
{
|
||||
title: "Placement tracking",
|
||||
description: "Use different menu or campaign destinations by location so you can compare tables, windows, and takeaway inserts.",
|
||||
},
|
||||
],
|
||||
checklistTitle: "Restaurant QR checklist",
|
||||
checklist: [
|
||||
"Use a mobile-first landing page instead of a hard-to-read PDF where possible.",
|
||||
"Keep the same printed QR on every stable surface you do not want to replace often.",
|
||||
"Use CTA copy like 'Scan for menu' or 'Scan for today's specials' so the scan intent is obvious.",
|
||||
"Pair campaign placements with trackable destinations when you test takeaway or window traffic.",
|
||||
],
|
||||
supportLinks: [
|
||||
{
|
||||
href: "/dynamic-qr-code-generator",
|
||||
title: "Commercial parent: Dynamic QR Code Generator",
|
||||
description: "Best fit when the real need is editing the destination after print.",
|
||||
},
|
||||
{
|
||||
href: "/blog/qr-code-restaurant-menu",
|
||||
title: "Restaurant menu guide",
|
||||
description: "Existing editorial asset with menu placement and implementation basics.",
|
||||
},
|
||||
{
|
||||
href: "/use-cases/business-card-qr-codes",
|
||||
title: "Sibling page: Business Card QR Codes",
|
||||
description: "Useful example of another print-first workflow where the destination changes over time.",
|
||||
},
|
||||
],
|
||||
faq: [
|
||||
{
|
||||
question: "Should a restaurant menu QR code be static or dynamic?",
|
||||
answer: "Use a dynamic QR code when the menu destination may change. That lets you update the landing page without replacing your printed materials.",
|
||||
},
|
||||
{
|
||||
question: "What should a restaurant menu QR code link to?",
|
||||
answer: "Link to a mobile-friendly menu page, ordering page, or a short service hub that helps customers reach the right menu fast.",
|
||||
},
|
||||
{
|
||||
question: "Can I use one restaurant QR code in multiple places?",
|
||||
answer: "Yes. One stable code can be reused across table cards, flyers, and takeaway materials, especially when the destination is managed dynamically.",
|
||||
},
|
||||
],
|
||||
},
|
||||
"business-card-qr-codes": {
|
||||
...featuredUseCases[1],
|
||||
eyebrow: "Business Cards",
|
||||
titleSuffix: "for Contact Sharing, Bookings, and Portfolio Links",
|
||||
metaDescription:
|
||||
"Use business card QR codes to share a current contact page, vCard, booking link, or portfolio without reprinting cards.",
|
||||
intro:
|
||||
"Business card QR codes are most useful when your contact destination changes faster than your print inventory.",
|
||||
answer:
|
||||
"A business card QR code should send people to the best next action today, whether that is saving your contact, opening a booking link, or visiting a current profile page.",
|
||||
whenToUse: [
|
||||
"Your role, booking link, or portfolio changes more often than your printed cards.",
|
||||
"You want one card to work for networking, sales follow-up, and contact saving.",
|
||||
"You need a cleaner handoff than asking people to type a URL from a small card.",
|
||||
],
|
||||
comparisonItems: [
|
||||
{ label: "Destination flexibility", text: "Fixed once printed", value: true },
|
||||
{ label: "Contact updates", text: "New cards needed", value: true },
|
||||
{ label: "Action routing", text: "Single fixed page", value: true },
|
||||
],
|
||||
howToSteps: [
|
||||
"Choose the real action you want after the scan: save contact, book time, or view work.",
|
||||
"Generate a QR code that sends people to that destination or to a vCard-capable landing page.",
|
||||
"Keep the print the same and update the linked destination when your details evolve.",
|
||||
],
|
||||
workflowTitle: "Where business card QR codes pay off",
|
||||
workflowIntro:
|
||||
"Printed cards still work. The problem is that the destination behind them often gets stale first.",
|
||||
workflowCards: [
|
||||
{
|
||||
title: "Current contact flow",
|
||||
description: "Send scanners to a vCard or current contact page so the next step is saving your details, not typing them.",
|
||||
},
|
||||
{
|
||||
title: "Role or profile updates",
|
||||
description: "Update the destination if you change company, title, offer, or booking link while old cards are still in circulation.",
|
||||
},
|
||||
{
|
||||
title: "Context-aware follow-up",
|
||||
description: "Point event-specific cards or team variants to the most relevant landing page instead of one generic homepage.",
|
||||
},
|
||||
],
|
||||
checklistTitle: "Business card QR checklist",
|
||||
checklist: [
|
||||
"Pick one primary post-scan action instead of trying to send every scanner to everything at once.",
|
||||
"Make the landing page useful on mobile because most business-card scans happen on phones.",
|
||||
"Use CTA text such as 'Scan to save my contact' or 'Scan to book a call'.",
|
||||
"Test the print size and contrast before ordering a large run.",
|
||||
],
|
||||
supportLinks: [
|
||||
{
|
||||
href: "/dynamic-qr-code-generator",
|
||||
title: "Commercial parent: Dynamic QR Code Generator",
|
||||
description: "Use when the printed card stays constant but the best destination changes.",
|
||||
},
|
||||
{
|
||||
href: "/tools/vcard-qr-code",
|
||||
title: "vCard QR Code tool",
|
||||
description: "Free tool for contact-saving workflows when a vCard is the best immediate action.",
|
||||
},
|
||||
{
|
||||
href: "/use-cases/event-qr-codes",
|
||||
title: "Sibling page: Event QR Codes",
|
||||
description: "Another workflow where temporary destinations and scan context matter.",
|
||||
},
|
||||
],
|
||||
faq: [
|
||||
{
|
||||
question: "What should a business card QR code link to?",
|
||||
answer: "The best destination is the one next step you want most: a vCard, booking page, contact hub, or current portfolio page.",
|
||||
},
|
||||
{
|
||||
question: "Do business card QR codes need to be dynamic?",
|
||||
answer: "They should be dynamic if your destination may change over the life of the printed card. That keeps old cards useful longer.",
|
||||
},
|
||||
{
|
||||
question: "Can a business card QR code send people straight to contact saving?",
|
||||
answer: "Yes. A vCard QR or a landing page with clear save-contact options is often the simplest and most practical post-scan action.",
|
||||
},
|
||||
],
|
||||
},
|
||||
"event-qr-codes": {
|
||||
...featuredUseCases[2],
|
||||
eyebrow: "Events",
|
||||
titleSuffix: "for Check-In, Schedules, Booths, and Campaign Tracking",
|
||||
metaDescription:
|
||||
"Use event QR codes for schedules, check-in flows, and trackable campaign placements across signs, flyers, and booths.",
|
||||
intro:
|
||||
"Event QR codes work best when you separate operational QR flows from promotional ones and keep your printed placements easy to manage.",
|
||||
answer:
|
||||
"A good event QR setup uses different QR destinations for different jobs: operations, attendee utility, and campaign measurement should not all depend on one code.",
|
||||
whenToUse: [
|
||||
"Your event schedule, map, or resources may change close to event day.",
|
||||
"You want to compare booth, banner, flyer, or badge placements instead of treating every scan as one bucket.",
|
||||
"You need one event QR system that supports both attendee utility and marketing follow-up.",
|
||||
],
|
||||
comparisonItems: [
|
||||
{ label: "Schedule changes", text: "New print may be needed", value: true },
|
||||
{ label: "Placement reporting", text: "Weak by default", value: true },
|
||||
{ label: "Operational vs campaign flows", text: "Often mixed", value: true },
|
||||
],
|
||||
howToSteps: [
|
||||
"Split event QR codes by purpose: check-in, attendee info, and campaign placements.",
|
||||
"Use trackable destinations for banners, booth assets, and flyers where placement performance matters.",
|
||||
"Keep fast-changing resources on destinations you can update without replacing the printed code.",
|
||||
],
|
||||
workflowTitle: "Event workflows worth designing on purpose",
|
||||
workflowIntro:
|
||||
"Events generate scans in very different contexts. Treating them as one generic QR use case leaves both operations and measurement weaker.",
|
||||
workflowCards: [
|
||||
{
|
||||
title: "Operational QR flows",
|
||||
description: "Use dedicated QR paths for check-in, schedules, maps, and attendee resources that may shift before or during the event.",
|
||||
},
|
||||
{
|
||||
title: "Booth and banner tracking",
|
||||
description: "Track scans from distinct placements so you can compare booth creatives, sponsor zones, or call-to-action angles.",
|
||||
},
|
||||
{
|
||||
title: "Post-event follow-up",
|
||||
description: "Route scanners to the most relevant recap, booking, or lead capture page after the event without changing the printed assets.",
|
||||
},
|
||||
],
|
||||
checklistTitle: "Event QR checklist",
|
||||
checklist: [
|
||||
"Do not force one QR code to handle operations, schedule updates, and lead-gen at the same time.",
|
||||
"Use descriptive CTA copy like 'Scan for agenda' or 'Scan for booth resources'.",
|
||||
"Track campaign placements separately so booth banners and flyers are comparable.",
|
||||
"Test glare, print size, and placement distance on the real event materials before the event starts.",
|
||||
],
|
||||
supportLinks: [
|
||||
{
|
||||
href: "/qr-code-tracking",
|
||||
title: "Commercial parent: QR Code Tracking",
|
||||
description: "Best fit when the priority is measuring placement and scan context across the event.",
|
||||
},
|
||||
{
|
||||
href: "/tools/event-qr-code",
|
||||
title: "Event QR Code tool",
|
||||
description: "Useful for save-the-date and calendar workflows that sit alongside broader event QR strategy.",
|
||||
},
|
||||
{
|
||||
href: "/use-cases/restaurant-menu-qr-codes",
|
||||
title: "Sibling page: Restaurant Menu QR Codes",
|
||||
description: "Another example of a printed workflow where the content behind the QR changes often.",
|
||||
},
|
||||
],
|
||||
faq: [
|
||||
{
|
||||
question: "Should an event use one QR code or several?",
|
||||
answer: "Several is usually better. Separate operational QR codes from campaign QR codes so schedules, check-in, and attribution do not compete for one destination.",
|
||||
},
|
||||
{
|
||||
question: "Can event QR codes be updated after print?",
|
||||
answer: "Yes, if the destination is managed dynamically. That is useful for schedules, resource hubs, and post-event follow-up pages.",
|
||||
},
|
||||
{
|
||||
question: "How do I measure which event placement performs best?",
|
||||
answer: "Use distinct destinations or tagged URLs for each placement so banner, booth, badge, and flyer traffic can be compared cleanly.",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function getUseCasePage(slug: string): UseCasePageContent | undefined {
|
||||
return useCasePageContent[slug];
|
||||
}
|
||||
@@ -62,27 +62,27 @@ export function getAllIndexableUrls(): string[] {
|
||||
const blogPages = blogPosts.map(post => `${baseUrl}/blog/${post.slug}`);
|
||||
|
||||
// Main pages (synced with sitemap.ts)
|
||||
const mainPages = [
|
||||
baseUrl,
|
||||
`${baseUrl}/qr-code-erstellen`,
|
||||
`${baseUrl}/qr-code-tracking`,
|
||||
`${baseUrl}/reprint-calculator`,
|
||||
`${baseUrl}/dynamic-qr-code-generator`,
|
||||
`${baseUrl}/bulk-qr-code-generator`,
|
||||
const mainPages = [
|
||||
baseUrl,
|
||||
`${baseUrl}/about`,
|
||||
`${baseUrl}/contact`,
|
||||
`${baseUrl}/press`,
|
||||
`${baseUrl}/testimonials`,
|
||||
`${baseUrl}/qr-code-erstellen`,
|
||||
`${baseUrl}/qr-code-tracking`,
|
||||
`${baseUrl}/reprint-calculator`,
|
||||
`${baseUrl}/dynamic-qr-code-generator`,
|
||||
`${baseUrl}/bulk-qr-code-generator`,
|
||||
`${baseUrl}/custom-qr-code-generator`,
|
||||
`${baseUrl}/manage-qr-codes`,
|
||||
`${baseUrl}/pricing`,
|
||||
`${baseUrl}/tools`,
|
||||
`${baseUrl}/features`,
|
||||
`${baseUrl}/faq`,
|
||||
`${baseUrl}/blog`,
|
||||
`${baseUrl}/signup`,
|
||||
`${baseUrl}/login`,
|
||||
`${baseUrl}/privacy`,
|
||||
`${baseUrl}/guide/tracking-analytics`,
|
||||
`${baseUrl}/guide/bulk-qr-code-generation`,
|
||||
`${baseUrl}/guide/qr-code-best-practices`,
|
||||
];
|
||||
`${baseUrl}/manage-qr-codes`,
|
||||
`${baseUrl}/pricing`,
|
||||
`${baseUrl}/tools`,
|
||||
`${baseUrl}/features`,
|
||||
`${baseUrl}/faq`,
|
||||
`${baseUrl}/blog`,
|
||||
`${baseUrl}/privacy`,
|
||||
`${baseUrl}/newsletter`,
|
||||
];
|
||||
|
||||
// Learn hub and pillar pages
|
||||
const learnPages = [
|
||||
|
||||
@@ -11,12 +11,15 @@ export function middleware(req: NextRequest) {
|
||||
if (path === '/guide/bulk-qr-code-generation') {
|
||||
return NextResponse.redirect(new URL('/learn/developer', req.url), 301);
|
||||
}
|
||||
if (path === '/guide/qr-code-best-practices') {
|
||||
return NextResponse.redirect(new URL('/learn/basics', req.url), 301);
|
||||
}
|
||||
|
||||
// Public routes that don't require authentication
|
||||
const publicPaths = [
|
||||
if (path === '/guide/qr-code-best-practices') {
|
||||
return NextResponse.redirect(new URL('/learn/basics', req.url), 301);
|
||||
}
|
||||
if (path === '/create-qr') {
|
||||
return NextResponse.redirect(new URL('/dynamic-qr-code-generator', req.url), 301);
|
||||
}
|
||||
|
||||
// Public routes that don't require authentication
|
||||
const publicPaths = [
|
||||
'/',
|
||||
'/pricing',
|
||||
'/faq',
|
||||
@@ -42,11 +45,13 @@ export function middleware(req: NextRequest) {
|
||||
'/display',
|
||||
'/contact',
|
||||
'/about',
|
||||
'/learn',
|
||||
'/authors',
|
||||
'/press',
|
||||
'/testimonials',
|
||||
];
|
||||
'/learn',
|
||||
'/use-cases',
|
||||
'/authors',
|
||||
'/press',
|
||||
'/testimonials',
|
||||
'/qr-code-for-marketing-campaigns',
|
||||
];
|
||||
|
||||
// Check if path is public
|
||||
const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/'));
|
||||
@@ -95,4 +100,4 @@ export const config = {
|
||||
*/
|
||||
'/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user