This commit is contained in:
2026-07-10 21:52:36 +02:00
parent bb528d6ef9
commit 671c1a1559
28 changed files with 6396 additions and 5561 deletions

View File

@@ -34,66 +34,68 @@ const nextConfig = {
pagesBufferLength: 2, pagesBufferLength: 2,
}, },
poweredByHeader: false, poweredByHeader: false,
webpack: (config, { dev }) => { webpack: (config, { dev }) => {
if (!dev && isWslOnWindowsMount()) { if (!dev && isWslOnWindowsMount()) {
config.cache = { config.cache = {
type: 'filesystem', type: 'filesystem',
cacheDirectory: path.join(os.tmpdir(), 'qrmaster-next-webpack-cache'), cacheDirectory: path.join(os.tmpdir(), 'qrmaster-next-webpack-cache'),
}; };
} }
return config; return config;
}, },
async headers() { async headers() {
const longCacheHeaders = [ const longCacheHeaders = [
{ {
key: 'Cache-Control', key: 'Cache-Control',
value: 'public, max-age=31536000, immutable', value: 'public, max-age=31536000, immutable',
}, },
]; ];
return [ return [
{ source: '/favicon1.png', headers: longCacheHeaders }, { source: '/favicon1.png', headers: longCacheHeaders },
{ source: '/favicon.svg', headers: longCacheHeaders }, { source: '/favicon.svg', headers: longCacheHeaders },
{ source: '/favicon.ico', headers: longCacheHeaders }, { source: '/favicon.ico', headers: longCacheHeaders },
{ source: '/logo.svg', headers: longCacheHeaders }, { source: '/logo.svg', headers: longCacheHeaders },
{ source: '/producthunt-featured.svg', headers: longCacheHeaders }, { source: '/producthunt-featured.svg', headers: longCacheHeaders },
]; ];
}, },
async redirects() { async redirects() {
return [ return [
// SEO Cannibalization Redirects // German home lives at /qr-code-erstellen (established ranking page);
{ // /de only hosts the German subpages.
source: '/tools/teams-qr-code', {
destination: '/blog/microsoft-teams-qr-code', source: '/de',
permanent: true, destination: '/qr-code-erstellen',
}, permanent: true,
{ },
source: '/dynamic-barcode-generator', // SEO Cannibalization Redirects
destination: '/tools/barcode-generator', {
permanent: true, source: '/dynamic-barcode-generator',
}, destination: '/tools/barcode-generator',
{ permanent: true,
source: '/barcode-generator', },
destination: '/tools/barcode-generator', {
permanent: true, source: '/barcode-generator',
}, destination: '/tools/barcode-generator',
{ permanent: true,
source: '/blog/qr-code-analytics', },
destination: '/qr-code-analytics', {
permanent: true, source: '/blog/qr-code-analytics',
}, destination: '/qr-code-analytics',
{ permanent: true,
source: '/use-cases/restaurant-menu-qr-codes', },
destination: '/restaurants', {
permanent: true, source: '/use-cases/restaurant-menu-qr-codes',
}, destination: '/restaurants',
{ permanent: true,
source: '/blog/qr-code-restaurant-menu', },
destination: '/restaurants', {
permanent: true, source: '/blog/qr-code-restaurant-menu',
}, destination: '/restaurants',
permanent: true,
},
{ {
source: '/create-qr', source: '/create-qr',
destination: '/dynamic-qr-code-generator', destination: '/dynamic-qr-code-generator',

View File

@@ -5,10 +5,9 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Footer } from '@/components/ui/Footer'; import { Footer } from '@/components/ui/Footer';
import en from '@/i18n/en.json'; import en from '@/i18n/en.json';
import { allUseCases } from '@/lib/growth-pages'; import { publishedUseCases } from '@/lib/growth-pages';
import { LayoutGrid } from 'lucide-react'; import { ChevronDown, LayoutGrid, BookOpen, Building2, Wifi, Contact, MessageCircle, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users, Barcode as BarcodeIcon, Star } from 'lucide-react';
import { ChevronDown, BookOpen, Building2, Wifi, Contact, MessageCircle, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users, Barcode as BarcodeIcon, Star } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
@@ -20,10 +19,10 @@ export default function MarketingLayout({
}) { }) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
const [toolsOpen, setToolsOpen] = useState(false); const [toolsOpen, setToolsOpen] = useState(false);
const [useCasesOpen, setUseCasesOpen] = useState(false); const [useCasesOpen, setUseCasesOpen] = useState(false);
const [resourcesOpen, setResourcesOpen] = useState(false); const [resourcesOpen, setResourcesOpen] = useState(false);
const [mobileToolsOpen, setMobileToolsOpen] = useState(false); const [mobileToolsOpen, setMobileToolsOpen] = useState(false);
const [mobileUseCasesOpen, setMobileUseCasesOpen] = useState(false); const [mobileUseCasesOpen, setMobileUseCasesOpen] = useState(false);
const [mobileResourcesOpen, setMobileResourcesOpen] = useState(false); const [mobileResourcesOpen, setMobileResourcesOpen] = useState(false);
const pathname = usePathname(); const pathname = usePathname();
@@ -40,10 +39,10 @@ export default function MarketingLayout({
// Close simple menus when path changes // Close simple menus when path changes
useEffect(() => { useEffect(() => {
setMobileMenuOpen(false); setMobileMenuOpen(false);
setToolsOpen(false); setToolsOpen(false);
setUseCasesOpen(false); setUseCasesOpen(false);
setResourcesOpen(false); setResourcesOpen(false);
setMobileToolsOpen(false); setMobileToolsOpen(false);
setMobileUseCasesOpen(false); setMobileUseCasesOpen(false);
setMobileResourcesOpen(false); setMobileResourcesOpen(false);
}, [pathname]); }, [pathname]);
@@ -151,50 +150,50 @@ export default function MarketingLayout({
</AnimatePresence> </AnimatePresence>
</div> </div>
{/* Use Cases Dropdown */} {/* Use Cases Dropdown */}
<div <div
className="relative group px-3 py-2" className="relative group px-3 py-2"
onMouseEnter={() => setUseCasesOpen(true)} onMouseEnter={() => setUseCasesOpen(true)}
onMouseLeave={() => setUseCasesOpen(false)} onMouseLeave={() => setUseCasesOpen(false)}
> >
<button className="flex items-center space-x-1 text-sm font-medium text-slate-600 group-hover:text-slate-900 transition-colors"> <button className="flex items-center space-x-1 text-sm font-medium text-slate-600 group-hover:text-slate-900 transition-colors">
<span>Use Cases</span> <span>Use Cases</span>
<ChevronDown className={cn("w-4 h-4 transition-transform duration-200", useCasesOpen && "rotate-180")} /> <ChevronDown className={cn("w-4 h-4 transition-transform duration-200", useCasesOpen && "rotate-180")} />
</button> </button>
<AnimatePresence> <AnimatePresence>
{useCasesOpen && ( {useCasesOpen && (
<motion.div <motion.div
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }} exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.15 }} transition={{ duration: 0.15 }}
className="absolute left-1/2 -translate-x-1/2 top-full mt-2 w-[700px] bg-white rounded-2xl shadow-lg border border-slate-100 p-4 overflow-hidden" className="absolute left-1/2 -translate-x-1/2 top-full mt-2 w-[700px] bg-white rounded-2xl shadow-lg border border-slate-100 p-4 overflow-hidden"
> >
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
{allUseCases.map((uc) => ( {publishedUseCases.map((uc) => (
<Link <Link
key={uc.slug} key={uc.slug}
href={uc.href} href={uc.href}
className="flex items-center space-x-3 p-2.5 rounded-xl transition-colors hover:bg-slate-50" className="flex items-center space-x-3 p-2.5 rounded-xl transition-colors hover:bg-slate-50"
> >
<div className="p-2 rounded-lg shrink-0 bg-indigo-50 text-indigo-500"> <div className="p-2 rounded-lg shrink-0 bg-indigo-50 text-indigo-500">
<LayoutGrid className="w-4 h-4" /> <LayoutGrid className="w-4 h-4" />
</div> </div>
<div> <div>
<div className="text-sm font-semibold text-slate-900">{uc.title}</div> <div className="text-sm font-semibold text-slate-900">{uc.title}</div>
</div> </div>
</Link> </Link>
))} ))}
</div> </div>
<div className="mt-3 pt-3 border-t border-slate-100 -mx-4 -mb-4 px-4 py-3 text-center bg-slate-50/50"> <div className="mt-3 pt-3 border-t border-slate-100 -mx-4 -mb-4 px-4 py-3 text-center bg-slate-50/50">
<Link href="/use-cases" className="text-xs text-indigo-600 font-medium hover:underline">View All Use Cases &rarr;</Link> <Link href="/use-cases" className="text-xs text-indigo-600 font-medium hover:underline">View All Use Cases &rarr;</Link>
</div> </div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
</div> </div>
{/* Tools Dropdown */} {/* Tools Dropdown */}
<div <div
className="relative group px-3 py-2" className="relative group px-3 py-2"
@@ -329,39 +328,39 @@ export default function MarketingLayout({
)} )}
</AnimatePresence> </AnimatePresence>
{/* Use Cases Accordion */} {/* Use Cases Accordion */}
<button <button
onClick={() => setMobileUseCasesOpen(!mobileUseCasesOpen)} onClick={() => setMobileUseCasesOpen(!mobileUseCasesOpen)}
className="flex items-center justify-between w-full px-4 py-3 rounded-xl hover:bg-slate-50 text-slate-700 font-semibold" className="flex items-center justify-between w-full px-4 py-3 rounded-xl hover:bg-slate-50 text-slate-700 font-semibold"
> >
<span>Use Cases</span> <span>Use Cases</span>
<ChevronDown className={cn("w-5 h-5 transition-transform", mobileUseCasesOpen && "rotate-180")} /> <ChevronDown className={cn("w-5 h-5 transition-transform", mobileUseCasesOpen && "rotate-180")} />
</button> </button>
<AnimatePresence> <AnimatePresence>
{mobileUseCasesOpen && ( {mobileUseCasesOpen && (
<motion.div <motion.div
initial={{ opacity: 0, height: 0 }} initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }} animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }} exit={{ opacity: 0, height: 0 }}
className="overflow-hidden" className="overflow-hidden"
> >
<div className="max-h-[50vh] overflow-y-auto pl-4 space-y-1 border-l-2 border-slate-100 ml-4"> <div className="max-h-[50vh] overflow-y-auto pl-4 space-y-1 border-l-2 border-slate-100 ml-4">
{allUseCases.map((uc) => ( {publishedUseCases.map((uc) => (
<Link <Link
key={uc.slug} key={uc.slug}
href={uc.href} href={uc.href}
className="block px-4 py-2.5 rounded-lg hover:bg-slate-50 text-slate-600 text-sm" className="block px-4 py-2.5 rounded-lg hover:bg-slate-50 text-slate-600 text-sm"
onClick={() => { setMobileMenuOpen(false); setMobileUseCasesOpen(false); }} onClick={() => { setMobileMenuOpen(false); setMobileUseCasesOpen(false); }}
> >
{uc.title} {uc.title}
</Link> </Link>
))} ))}
</div> </div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
{/* Free Tools Accordion */} {/* Free Tools Accordion */}
<button <button
onClick={() => setMobileToolsOpen(!mobileToolsOpen)} onClick={() => setMobileToolsOpen(!mobileToolsOpen)}

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import Link from 'next/link'; import Link from 'next/link';
import PricingClient from './PricingClient'; import PricingClient from './PricingClient';
import SeoJsonLd from '@/components/SeoJsonLd'; import SeoJsonLd from '@/components/SeoJsonLd';
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock'; import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
import { FAQSection } from '@/components/aeo/FAQSection'; import { FAQSection } from '@/components/aeo/FAQSection';
@@ -15,6 +15,11 @@ export const metadata: Metadata = {
'Compare QR Master pricing plans. Free includes 3 active dynamic QR codes, Pro includes 50, and Business includes 500 plus bulk QR creation.', 'Compare QR Master pricing plans. Free includes 3 active dynamic QR codes, Pro includes 50, and Business includes 500 plus bulk QR creation.',
alternates: { alternates: {
canonical: 'https://www.qrmaster.net/pricing', canonical: 'https://www.qrmaster.net/pricing',
languages: {
'x-default': 'https://www.qrmaster.net/pricing',
en: 'https://www.qrmaster.net/pricing',
de: 'https://www.qrmaster.net/de/preise',
},
}, },
robots: { robots: {
index: true, index: true,
@@ -98,30 +103,30 @@ export default function PricingPage() {
/> />
</div> </div>
<div className="container mx-auto max-w-5xl px-4 pb-4 sm:px-6 lg:px-8"> <div className="container mx-auto max-w-5xl px-4 pb-4 sm:px-6 lg:px-8">
<FAQSection items={faqItems} title="Pricing questions" /> <FAQSection items={faqItems} title="Pricing questions" />
</div> </div>
<section className="container mx-auto max-w-5xl px-4 pb-10 sm:px-6 lg:px-8"> <section className="container mx-auto max-w-5xl px-4 pb-10 sm:px-6 lg:px-8">
<div className="rounded-lg border border-slate-200 bg-slate-50 p-5"> <div className="rounded-lg border border-slate-200 bg-slate-50 p-5">
<h2 className="text-xl font-semibold text-slate-900"> <h2 className="text-xl font-semibold text-slate-900">
Not sure whether free is enough? Not sure whether free is enough?
</h2> </h2>
<p className="mt-3 max-w-3xl leading-7 text-slate-600"> <p className="mt-3 max-w-3xl leading-7 text-slate-600">
Compare free QR generators with paid dynamic QR workflows before Compare free QR generators with paid dynamic QR workflows before
choosing a plan. The difference usually comes down to editing choosing a plan. The difference usually comes down to editing
after print, scan tracking, branding, and reprint risk. after print, scan tracking, branding, and reprint risk.
</p> </p>
<Link <Link
href="/compare/free-vs-paid-qr-code-generator" href="/compare/free-vs-paid-qr-code-generator"
className="mt-4 inline-flex text-sm font-semibold text-blue-700 hover:text-blue-800" className="mt-4 inline-flex text-sm font-semibold text-blue-700 hover:text-blue-800"
> >
Read the free vs paid QR generator comparison Read the free vs paid QR generator comparison
</Link> </Link>
</div> </div>
</section> </section>
<div className="sr-only"> <div className="sr-only">
<h2>Compare our plans</h2> <h2>Compare our plans</h2>
<p> <p>
Free includes 3 active dynamic QR codes and unlimited static QR codes. Pro Free includes 3 active dynamic QR codes and unlimited static QR codes. Pro

View File

@@ -5,6 +5,7 @@ import {
UseCasePageTemplate, UseCasePageTemplate,
} from '@/components/marketing/UseCasePageTemplate'; } from '@/components/marketing/UseCasePageTemplate';
import { allUseCases, getUseCasePage } from '@/lib/growth-pages'; import { allUseCases, getUseCasePage } from '@/lib/growth-pages';
import { getGermanTwinByEnSlug } from '@/lib/growth-pages-de';
export function generateStaticParams() { export function generateStaticParams() {
return allUseCases.map((item) => ({ return allUseCases.map((item) => ({
@@ -23,6 +24,7 @@ export function generateMetadata({ params }: { params: { slug: string } }) {
title: page.title, title: page.title,
description: page.metaDescription, description: page.metaDescription,
canonicalPath: page.href, canonicalPath: page.href,
dePath: getGermanTwinByEnSlug(page.slug)?.href,
}); });
} }

View File

@@ -19,9 +19,9 @@ import {
import { Button } from "@/components/ui/Button"; import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card"; import { Card } from "@/components/ui/Card";
import { import {
allUseCases,
commercialPages, commercialPages,
featuredUseCases, featuredUseCases,
publishedUseCases,
supportResources, supportResources,
upcomingUseCaseIdeas, upcomingUseCaseIdeas,
} from "@/lib/growth-pages"; } from "@/lib/growth-pages";
@@ -184,7 +184,7 @@ export default function UseCasesHubPage() {
</div> </div>
<div className="grid gap-6 md:grid-cols-2 xl:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 xl:grid-cols-3">
{allUseCases.map((page) => ( {publishedUseCases.map((page) => (
<Link key={page.slug} href={page.href} className="group block"> <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"> <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"> <div className="text-sm font-semibold uppercase tracking-[0.18em] text-blue-700">
@@ -205,66 +205,66 @@ export default function UseCasesHubPage() {
))} ))}
</div> </div>
</div> </div>
</section> </section>
<section className="bg-white py-16"> <section className="bg-white py-16">
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-8 max-w-3xl"> <div className="mb-8 max-w-3xl">
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700"> <div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
Industry pages with early traction Industry pages with early traction
</div> </div>
<h2 className="mt-3 text-3xl font-bold text-slate-900"> <h2 className="mt-3 text-3xl font-bold text-slate-900">
Service business QR workflows Service business QR workflows
</h2> </h2>
<p className="mt-4 text-lg leading-8 text-slate-600"> <p className="mt-4 text-lg leading-8 text-slate-600">
These industry routes connect practical use cases with the QR These industry routes connect practical use cases with the QR
tools local businesses usually need first. tools local businesses usually need first.
</p> </p>
</div> </div>
<div className="grid gap-4 md:grid-cols-3"> <div className="grid gap-4 md:grid-cols-3">
{[ {[
{ {
href: "/qr-code-for/barbershops", href: "/qr-code-for/barbershops",
title: "QR Codes for Barbershops", title: "QR Codes for Barbershops",
description: description:
"Booking links, review prompts, WiFi access, and social profiles for barbershop visits.", "Booking links, review prompts, WiFi access, and social profiles for barbershop visits.",
}, },
{ {
href: "/tools/wifi-qr-code", href: "/tools/wifi-qr-code",
title: "WiFi QR Code Generator", title: "WiFi QR Code Generator",
description: description:
"A practical guest-facing QR tool for service businesses and venues.", "A practical guest-facing QR tool for service businesses and venues.",
}, },
{ {
href: "/qr-code-erstellen", href: "/qr-code-erstellen",
title: "QR Code Erstellen", title: "QR Code Erstellen",
description: description:
"German QR creation page for local business owners who search in German.", "German QR creation page for local business owners who search in German.",
}, },
].map((item) => ( ].map((item) => (
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
className="rounded-2xl border border-slate-200 bg-slate-50 p-6 transition-colors hover:border-blue-200 hover:bg-blue-50/70" className="rounded-2xl border border-slate-200 bg-slate-50 p-6 transition-colors hover:border-blue-200 hover:bg-blue-50/70"
> >
<h3 className="text-xl font-bold text-slate-900"> <h3 className="text-xl font-bold text-slate-900">
{item.title} {item.title}
</h3> </h3>
<p className="mt-3 text-sm leading-6 text-slate-600"> <p className="mt-3 text-sm leading-6 text-slate-600">
{item.description} {item.description}
</p> </p>
<div className="mt-5 inline-flex items-center text-sm font-semibold text-blue-700"> <div className="mt-5 inline-flex items-center text-sm font-semibold text-blue-700">
Open page <ArrowRight className="ml-2 h-4 w-4" /> Open page <ArrowRight className="ml-2 h-4 w-4" />
</div> </div>
</Link> </Link>
))} ))}
</div> </div>
</div> </div>
</section> </section>
<section className="bg-slate-50 py-16"> <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="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)]"> <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"> <Card className="rounded-3xl border-slate-200 bg-white p-8 shadow-sm">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">

View File

@@ -1,452 +0,0 @@
import React from 'react';
import type { Metadata } from 'next';
import SeoJsonLd from '@/components/SeoJsonLd';
import { organizationSchema, websiteSchema } from '@/lib/schema';
import {
generateFaqSchema,
generateSoftwareAppSchema,
} from '@/lib/schema-utils';
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
import de from '@/i18n/de.json';
import { FAQ } from '@/components/marketing/FAQ';
import { ScrollToTop } from '@/components/ui/ScrollToTop';
import {
Barcode as BarcodeIcon,
Check,
Shield,
Zap,
Printer,
Download,
} from 'lucide-react';
export const metadata: Metadata = {
title: {
absolute: 'Barcode Generator Kostenlos EAN, UPC, Code 128 Online',
},
description:
'Erstellen Sie Barcodes kostenlos online: EAN-13, UPC-A und Code 128. Sofort druckfertig. Für Einzelhandel, Lager und Logistik. Keine Anmeldung erforderlich.',
keywords: [
'barcode generator',
'barcode generator kostenlos',
'ean-13 generator',
'upc-a generator',
'code 128 generator',
'barcode erstellen',
'barcode online erstellen',
'strichcode generator',
'barcode für amazon erstellen',
'ean barcode generator',
'produktbarcode erstellen',
'lagerverwaltung barcode',
'barcode drucken',
],
alternates: {
canonical: 'https://www.qrmaster.net/tools/barcode-generator',
languages: {
'x-default': 'https://www.qrmaster.net/tools/barcode-generator',
en: 'https://www.qrmaster.net/tools/barcode-generator',
de: 'https://www.qrmaster.net/tools/barcode-generator',
},
},
openGraph: {
title: 'Barcode Generator Kostenlos EAN, UPC & Code 128 erstellen',
description:
'Barcode Generator: Erstellen Sie professionelle Barcodes für Einzelhandel, Lager und Logistik. Kostenlos, ohne Anmeldung. Sofort druckfertig.',
url: 'https://www.qrmaster.net/tools/barcode-generator',
locale: 'de_DE',
type: 'website',
images: [
{
url: 'https://www.qrmaster.net/barcode-generator-preview.png',
width: 1200,
height: 630,
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Barcode Generator Kostenlos Online erstellen',
description:
'EAN-13, UPC-A und Code 128 Barcodes kostenlos erstellen. Sofort druckfertig.',
},
robots: {
index: true,
follow: true,
},
};
const faqQuestions = {
'Was ist ein Barcode Generator?': {
question: 'Was ist ein Barcode Generator?',
answer:
'Ein Barcode Generator ist ein Online-Tool, das Zahlen oder Texte in scannbare Barcode-Bilder umwandelt. Diese können für Produkte, Etiketten und Inventarsysteme verwendet werden.',
},
'Ist der Barcode Generator kostenlos?': {
question: 'Ist der Barcode Generator kostenlos?',
answer:
'Ja, unser Barcode Generator ist vollständig kostenlos nutzbar. Sie können Barcodes ohne Anmeldung generieren, herunterladen und drucken.',
},
'Welches Barcode-Format brauche ich?': {
question: 'Welches Barcode-Format brauche ich?',
answer:
'EAN-13 ist der Standard für Einzelhandel in Europa und weltweit. UPC-A wird hauptsächlich in den USA und Kanada verwendet. Code 128 eignet sich am besten für Logistik und interne Verfolgung, da er Buchstaben und Zahlen unterstützt.',
},
'Kann ich Barcodes als Vektorgrafik (SVG) herunterladen?': {
question: 'Kann ich Barcodes als Vektorgrafik (SVG) herunterladen?',
answer:
'Ja! SVG-Dateien sind vektorbasiert und können ohne Qualitätsverlust auf jede Größe skaliert werden. Perfekt für professionelle Produktverpackungen und Etiketten.',
},
'Wie erstelle ich einen Barcode online?': {
question: 'Wie erstelle ich einen Barcode online?',
answer:
'Geben Sie Ihre Produktnummer oder Ihren Text ein, wählen Sie das gewünschte Barcode-Format (z.B. EAN-13 oder Code 128) und der Barcode wird sofort erstellt. Dann können Sie ihn als PNG oder SVG herunterladen.',
},
'Sind die erstellten Barcodes scannbar?': {
question: 'Sind die erstellten Barcodes scannbar?',
answer:
'Ja, wir generieren standardkonforme Barcodes, die von jedem handelsüblichen Barcode-Scanner gelesen werden können, einschließlich Smartphone-Kamera-Apps.',
},
'Kann ich diese Barcodes für Amazon verwenden?': {
question: 'Kann ich diese Barcodes für Amazon verwenden?',
answer:
'Sie können hier das Barcode-Bild erstellen, wenn Sie bereits eine gültige EAN/UPC-Nummer haben. Offizielle GS1-Registrierungsnummern erhalten Sie jedoch nur direkt bei GS1, um Produkte auf Amazon oder in großen Einzelhandelssystemen zu listen.',
},
'Was ist der Unterschied zwischen Barcode und QR-Code?': {
question: 'Was ist der Unterschied zwischen Barcode und QR-Code?',
answer:
'Ein Barcode speichert Daten horizontal (1D) und wird hauptsächlich für Produktkennungen verwendet. Ein QR-Code speichert Daten in 2D (Matrix) und kann viel mehr Informationen enthalten, wie URLs, Kontaktdaten oder WLAN-Zugangsdaten.',
},
'Kann ich mehrere Barcodes gleichzeitig erstellen?': {
question: 'Kann ich mehrere Barcodes gleichzeitig erstellen?',
answer:
'Ja, mit einem PRO-Account können Sie mehrere Barcodes gleichzeitig generieren. Dies ist ideal für große Produktmengen oder Lagerverwaltung.',
},
'Was ist eine Prüfziffer bei Barcodes?': {
question: 'Was ist eine Prüfziffer bei Barcodes?',
answer:
'Die Prüfziffer ist die letzte Ziffer in einem Barcode, die mathematisch aus den anderen Ziffern berechnet wird. Sie stellt sicher, dass der Barcode korrekt gescannt wird, selbst wenn er leicht beschädigt oder verkratzt ist.',
},
};
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
generateSoftwareAppSchema(
'Barcode Generator',
'Erstellen Sie druckfertige Barcodes für EAN, UPC, Code 128 und weitere Formate. Kostenlos und ohne Anmeldung.',
'/barcode-generator-preview.png',
'UtilitiesApplication'
),
generateFaqSchema(faqQuestions),
],
};
export default function BarcodeGeneratorDEPage() {
const t = de;
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* SEO Content für Suchmaschinen */}
<div className="sr-only" aria-hidden="false">
<p>
QR Masters kostenloser Barcode Generator ermöglicht es Ihnen,
professionelle Barcodes für Produkte, Etiketten und Inventarsysteme zu
erstellen. Unterstützt EAN-13 für europäischen Einzelhandel, UPC-A für
US-Markt und Code 128 für Logistik und interne Verwendung. Alle
Barcodes sind standardkonform und sofort druckfertig.
</p>
<p>
Unser Barcode Generator funktioniert vollständig im Browser. Keine
Software-Installation erforderlich. Exportieren Sie Ihre Barcodes als
PNG für den sofortigen Druck oder als SVG für professionelle
Verpackungsdesigns. Perfekt für kleine Unternehmen, Online-Händler und
Lagerverwaltung.
</p>
</div>
{/* Hero Section */}
<section className="relative bg-gradient-to-b from-slate-900 to-slate-800 text-white py-16 md:py-24">
<div className="absolute inset-0 opacity-10">
<svg className="w-full h-full" width="100%" height="100%">
<defs>
<pattern
id="barcode_pattern_de"
width="60"
height="60"
patternUnits="userSpaceOnUse"
>
<path
d="M5 0 V 60 M15 0 V 60 M20 0 V 60 M35 0 V 60 M40 0 V 60 M55 0 V 60"
stroke="white"
strokeWidth="2"
strokeOpacity="0.5"
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#barcode_pattern_de)" />
</svg>
</div>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl relative z-10">
<div className="max-w-3xl mx-auto text-center">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/10 text-white/90 text-sm font-medium mb-6 backdrop-blur-sm border border-white/10">
<span className="flex h-2 w-2 relative">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-blue-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-blue-400"></span>
</span>
Kostenloses Tool Professionell & Schnell
</div>
<h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold tracking-tight mb-6">
Kostenloser{' '}
<span className="text-blue-400">Barcode Generator</span>
</h1>
<p className="text-lg md:text-xl text-slate-300 mb-8 leading-relaxed">
Erstellen Sie professionelle Barcodes in Sekunden. Für{' '}
<strong className="text-white">
Einzelhandel, Lager und Logistik
</strong>
. Unterstützt EAN-13, UPC-A und Code 128. Keine Anmeldung
erforderlich.
</p>
<div className="flex flex-wrap justify-center gap-4 text-sm font-medium">
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
<Check className="w-4 h-4 text-blue-400" />
Einzelhandel-fähig
</div>
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
<Check className="w-4 h-4 text-blue-400" />
SVG-Export
</div>
<div className="flex items-center gap-2 bg-white/5 px-4 py-2.5 rounded-xl border border-white/10">
<Check className="w-4 h-4 text-blue-400" />
Ohne Anmeldung
</div>
</div>
</div>
</div>
</section>
{/* Generator Section */}
<section className="py-12 px-4 bg-slate-100">
<div className="container mx-auto max-w-7xl">
<div className="text-center mb-6">
<h2 className="text-2xl font-bold mb-2">Barcode jetzt erstellen</h2>
<p className="text-muted-foreground">
Geben Sie Ihre Daten ein und laden Sie den Barcode herunter
</p>
</div>
<div className="bg-white rounded-3xl shadow-xl overflow-hidden border border-slate-200">
<div className="bg-slate-900 px-6 py-3 flex items-center gap-2">
<div className="flex gap-1.5">
<div className="w-3 h-3 rounded-full bg-red-500"></div>
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
<div className="w-3 h-3 rounded-full bg-green-500"></div>
</div>
<span className="text-white/60 text-sm ml-2">
qrmaster.net/tools/barcode-generator
</span>
</div>
<iframe
src="https://www.qrmaster.net/tools/barcode-generator"
className="w-full h-[650px] border-0"
title="Barcode Generator"
/>
</div>
<div className="text-center mt-6">
<a
href="https://www.qrmaster.net/tools/barcode-generator"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-slate-900 transition-colors"
>
<span>Vollbild öffnen</span>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
</div>
</div>
</section>
{/* Barcode Types Info */}
<section className="py-16 px-4 bg-white">
<div className="container mx-auto max-w-7xl">
<h2 className="text-3xl font-bold text-center mb-12">
Barcode-Formate im Überblick
</h2>
<div className="grid md:grid-cols-3 gap-8">
<div className="p-6 border rounded-xl">
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center mb-4">
<BarcodeIcon className="w-6 h-6 text-blue-600" />
</div>
<h3 className="font-bold text-lg mb-2">EAN-13</h3>
<p className="text-sm text-muted-foreground mb-3">
Internationaler Standard für Einzelhandel. 13 Ziffern, weltweit
anerkannt.
</p>
<div className="text-xs text-muted-foreground">
<strong>Einsatz:</strong> Produkte in Europa & global
</div>
</div>
<div className="p-6 border rounded-xl">
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center mb-4">
<BarcodeIcon className="w-6 h-6 text-green-600" />
</div>
<h3 className="font-bold text-lg mb-2">UPC-A</h3>
<p className="text-sm text-muted-foreground mb-3">
Amerikanischer Standard. 12 Ziffern, hauptsächlich in
Nordamerika.
</p>
<div className="text-xs text-muted-foreground">
<strong>Einsatz:</strong> USA & Kanada Einzelhandel
</div>
</div>
<div className="p-6 border rounded-xl">
<div className="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center mb-4">
<BarcodeIcon className="w-6 h-6 text-purple-600" />
</div>
<h3 className="font-bold text-lg mb-2">Code 128</h3>
<p className="text-sm text-muted-foreground mb-3">
Flexible Lösung für Logistik. Alphanumerisch, hohe Datendichte.
</p>
<div className="text-xs text-muted-foreground">
<strong>Einsatz:</strong> Versand, Lager, Inventar
</div>
</div>
</div>
</div>
</section>
{/* How It Works */}
<section className="py-16 px-4 bg-slate-50">
<div className="container mx-auto max-w-7xl">
<h2 className="text-3xl font-bold text-center mb-12">
So funktioniert unser Barcode Generator
</h2>
<div className="grid md:grid-cols-5 gap-8">
<div className="text-center">
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
1
</div>
<h3 className="font-bold mb-2">Daten eingeben</h3>
<p className="text-sm text-muted-foreground">
Geben Sie Ihre Produktnummer oder Text ein.
</p>
</div>
<div className="text-center">
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
2
</div>
<h3 className="font-bold mb-2">Format wählen</h3>
<p className="text-sm text-muted-foreground">
EAN-13, UPC-A oder Code 128 auswählen.
</p>
</div>
<div className="text-center">
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
3
</div>
<h3 className="font-bold mb-2">Vorschau</h3>
<p className="text-sm text-muted-foreground">
Barcode wird in Echtzeit aktualisiert.
</p>
</div>
<div className="text-center">
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
4
</div>
<h3 className="font-bold mb-2">Herunterladen</h3>
<p className="text-sm text-muted-foreground">
Als PNG oder SVG speichern.
</p>
</div>
<div className="text-center">
<div className="w-12 h-12 bg-slate-900 text-white rounded-full flex items-center justify-center mx-auto mb-4 font-bold">
5
</div>
<h3 className="font-bold mb-2">Drucken</h3>
<p className="text-sm text-muted-foreground">
Sofort auf Etiketten drucken.
</p>
</div>
</div>
</div>
</section>
{/* Use Cases */}
<section className="py-16 px-4 bg-white">
<div className="container mx-auto max-w-7xl">
<h2 className="text-3xl font-bold text-center mb-4">
Für wen ist unser Barcode Generator geeignet?
</h2>
<p className="text-center text-muted-foreground mb-12 max-w-2xl mx-auto">
Ob Kleinunternehmer, Online-Händler oder Lagerverwaltung unser
Tool unterstützt Sie bei allen Barcode-Bedarfen.
</p>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="p-6 bg-slate-50 rounded-xl">
<Printer className="w-8 h-8 text-blue-600 mb-3" />
<h3 className="font-bold mb-2">Einzelhandel</h3>
<p className="text-sm text-muted-foreground">
EAN-13 Barcodes für Produktetiketten im stationären Handel.
</p>
</div>
<div className="p-6 bg-slate-50 rounded-xl">
<Shield className="w-8 h-8 text-green-600 mb-3" />
<h3 className="font-bold mb-2">E-Commerce</h3>
<p className="text-sm text-muted-foreground">
UPC/EAN für Amazon, eBay und andere Marktplätze.
</p>
</div>
<div className="p-6 bg-slate-50 rounded-xl">
<Zap className="w-8 h-8 text-orange-600 mb-3" />
<h3 className="font-bold mb-2">Lager & Logistik</h3>
<p className="text-sm text-muted-foreground">
Code 128 für interne Bestandsverfolgung und Versand.
</p>
</div>
<div className="p-6 bg-slate-50 rounded-xl">
<Download className="w-8 h-8 text-purple-600 mb-3" />
<h3 className="font-bold mb-2">Druckerei</h3>
<p className="text-sm text-muted-foreground">
SVG-Export für professionelle Verpackungsdesigns.
</p>
</div>
</div>
</div>
</section>
{/* FAQ Section */}
<FAQ
t={{
faq: { title: 'Häufig gestellte Fragen', questions: faqQuestions },
}}
/>
{/* Free Tools Grid */}
<FreeToolsGrid />
{/* Scroll to Top */}
<ScrollToTop />
</>
);
}

View File

@@ -1,10 +1,10 @@
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { Suspense } from 'react'; import { Suspense } from 'react';
import '@/styles/globals.css'; import '@/styles/globals.css';
import { Providers } from '@/components/Providers'; import { Providers } from '@/components/Providers';
import MarketingDeLayout from './MarketingDeLayout'; import MarketingDeLayout from '@/components/marketing/MarketingDeLayout';
import { organizationSchema, websiteSchema } from '@/lib/schema'; import { organizationSchema, websiteSchema } from '@/lib/schema';
import FacebookPixel from '@/components/analytics/FacebookPixel'; import FacebookPixel from '@/components/analytics/FacebookPixel';
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {
@@ -13,14 +13,14 @@ export const metadata: Metadata = {
}, },
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.', description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
metadataBase: new URL('https://www.qrmaster.net'), metadataBase: new URL('https://www.qrmaster.net'),
icons: { icons: {
icon: [ icon: [
{ url: '/favicon.svg', type: 'image/svg+xml' }, { url: '/favicon.svg', type: 'image/svg+xml' },
{ url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' }, { url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' },
], ],
shortcut: '/favicon.ico', shortcut: '/favicon.ico',
apple: '/logo.svg', apple: '/logo.svg',
}, },
openGraph: { openGraph: {
type: 'website', type: 'website',
siteName: 'QR Master', siteName: 'QR Master',
@@ -55,33 +55,33 @@ export const metadata: Metadata = {
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '', 'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
}, },
}, },
}; };
export default function MarketingDeGroupLayout({ export default function MarketingDeGroupLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return ( return (
<html lang="de"> <html lang="de">
<body className="font-sans"> <body className="font-sans">
<Suspense fallback={null}> <Suspense fallback={null}>
<Providers> <Providers>
<FacebookPixel /> <FacebookPixel />
<script <script
type="application/ld+json" type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }} dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
/> />
<script <script
type="application/ld+json" type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }} dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
/> />
<MarketingDeLayout> <MarketingDeLayout>
{children} {children}
</MarketingDeLayout> </MarketingDeLayout>
</Providers> </Providers>
</Suspense> </Suspense>
</body> </body>
</html> </html>
); );
} }

View File

@@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import Link from 'next/link';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { useCasePagesDe } from '@/lib/growth-pages-de';
import SeoJsonLd from '@/components/SeoJsonLd'; import SeoJsonLd from '@/components/SeoJsonLd';
import { organizationSchema, websiteSchema } from '@/lib/schema'; import { organizationSchema, websiteSchema } from '@/lib/schema';
import { generateFaqSchema } from '@/lib/schema-utils'; import { generateFaqSchema } from '@/lib/schema-utils';
@@ -136,7 +138,7 @@ export default function QRCodeErstellenPage() {
<h3 className="font-semibold text-lg mb-2">Speisekarten QR-Code</h3> <h3 className="font-semibold text-lg mb-2">Speisekarten QR-Code</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Ideal für Restaurants, Cafés und Bars. Gäste scannen den Code und Ideal für Restaurants, Cafés und Bars. Gäste scannen den Code und
sehen Ihre Speisekarte digital auf dem Handy sehen Ihre Speisekarte digital auf dem Handy.
</p> </p>
</div> </div>
<div className="p-6 border rounded-lg"> <div className="p-6 border rounded-lg">
@@ -266,6 +268,31 @@ export default function QRCodeErstellenPage() {
</p> </p>
</div> </div>
{/* Beliebte Anwendungsfälle - interne Verlinkung auf deutsche Use-Case-Seiten */}
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl py-12">
<h2 className="text-3xl font-bold text-center mb-3">
Beliebte Anwendungsfälle
</h2>
<p className="text-center text-muted-foreground mb-8">
Ausführliche Anleitungen für Ihren Einsatzzweck mit Checklisten und
praxisnahen FAQs.
</p>
<div className="grid md:grid-cols-3 gap-6">
{Object.values(useCasePagesDe).map((useCase) => (
<Link
key={useCase.slug}
href={useCase.href}
className="group p-6 border rounded-lg transition-colors hover:border-slate-300 hover:bg-slate-50"
>
<h3 className="font-semibold text-lg mb-2 group-hover:text-blue-600 transition-colors">
{useCase.title}
</h3>
<p className="text-sm text-muted-foreground">{useCase.summary}</p>
</Link>
))}
</div>
</div>
{/* Answer First Block (SEO/AEO) - German */} {/* Answer First Block (SEO/AEO) - German */}
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl"> <div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
<AnswerFirstBlock <AnswerFirstBlock

110
src/app/de/[slug]/page.tsx Normal file
View File

@@ -0,0 +1,110 @@
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { UseCasePageTemplate } from '@/components/marketing/UseCasePageTemplate';
import {
getUseCasePageDe,
useCasePagesDe,
useCaseTemplateLabelsDe,
} from '@/lib/growth-pages-de';
import { buildLanguageAlternates, absoluteUrl } from '@/lib/i18n';
export const dynamicParams = false;
export function generateStaticParams() {
return Object.keys(useCasePagesDe).map((slug) => ({ slug }));
}
export function generateMetadata({ params }: { params: { slug: string } }): Metadata {
const page = getUseCasePageDe(params.slug);
if (!page) {
return {};
}
const alternates = buildLanguageAlternates({
en: `/use-cases/${page.enSlug}`,
de: page.href,
});
return {
title: {
absolute: `${page.title} | QR Master`,
},
description: page.metaDescription,
alternates: {
// The German page canonicalizes to itself; hreflang links the pair.
canonical: absoluteUrl(page.href),
languages: alternates.languages,
},
openGraph: {
title: `${page.title} | QR Master`,
description: page.metaDescription,
url: absoluteUrl(page.href),
type: 'website',
locale: 'de_DE',
images: ['/og-image.png'],
},
twitter: {
title: `${page.title} | QR Master`,
description: page.metaDescription,
},
};
}
export default function UseCaseDetailPageDe({
params,
}: {
params: { slug: string };
}) {
const page = getUseCasePageDe(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: 'Startseite', url: '/qr-code-erstellen' },
{ name: page.title, url: page.href },
]}
answer={page.answer}
whenToUse={page.whenToUse}
comparisonItems={page.comparisonItems}
howToSteps={page.howToSteps}
primaryCta={{
// German visitors should convert on the German generator page, not
// the English commercial parent.
href: '/qr-code-erstellen',
label: page.ctaLabel,
}}
secondaryCta={{
href: page.parentHref,
label: `Mehr erfahren: ${page.parentTitle}`,
}}
workflowTitle={page.workflowTitle}
workflowIntro={page.workflowIntro}
workflowCards={page.workflowCards}
checklistTitle={page.checklistTitle}
checklist={page.checklist}
supportLinks={page.supportLinks}
faq={page.faq}
heroImage={page.heroImage}
heroImageAlt={page.heroImageAlt}
authoritySignals={page.authoritySignals}
directAnswer={page.directAnswer}
labels={{
...useCaseTemplateLabelsDe,
faqTitle: `FAQ: ${page.title}`,
}}
/>
);
}

79
src/app/de/layout.tsx Normal file
View File

@@ -0,0 +1,79 @@
import type { Metadata } from 'next';
import { Suspense } from 'react';
import '@/styles/globals.css';
import { Providers } from '@/components/Providers';
import MarketingDeLayout from '@/components/marketing/MarketingDeLayout';
import { organizationSchema, websiteSchema } from '@/lib/schema';
import FacebookPixel from '@/components/analytics/FacebookPixel';
export const metadata: Metadata = {
title: {
default: 'QR Master QR Code Generator & Analytics',
template: '%s | QR Master',
},
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
metadataBase: new URL('https://www.qrmaster.net'),
icons: {
icon: [
{ url: '/favicon.svg', type: 'image/svg+xml' },
{ url: '/favicon.ico', sizes: '16x16 32x32', type: 'image/x-icon' },
],
shortcut: '/favicon.ico',
apple: '/logo.svg',
},
openGraph: {
type: 'website',
siteName: 'QR Master',
title: 'QR Master QR Code Generator & Analytics',
description: 'Erstellen Sie dynamische QR Codes für Feedback, PDF, Coupons und App Stores. Verfolgen Sie Scans und skalieren Sie Kampagnen mit sicheren Analysen.',
locale: 'de_DE',
images: [
{
url: 'https://www.qrmaster.net/og-image.png',
width: 1200,
height: 630,
alt: 'QR Master - Dynamischer QR Code Generator',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@qrmaster',
images: ['https://www.qrmaster.net/og-image.png'],
},
robots: { index: true, follow: true },
verification: {
other: {
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
},
},
};
export default function GermanRootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="de">
<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}
</MarketingDeLayout>
</Providers>
</Suspense>
</body>
</html>
);
}

View File

@@ -0,0 +1,85 @@
import React from 'react';
import type { Metadata } from 'next';
import SeoJsonLd from '@/components/SeoJsonLd';
import { Pricing } from '@/components/marketing/Pricing';
import { FAQSection } from '@/components/aeo/FAQSection';
import { breadcrumbSchema, faqPageSchema } from '@/lib/schema';
import { getDictionary, buildLanguageAlternates, absoluteUrl } from '@/lib/i18n';
const alternates = buildLanguageAlternates({ en: '/pricing', de: '/de/preise' });
export const metadata: Metadata = {
title: {
absolute: 'Preise & Pläne | QR Master',
},
description:
'QR Master Preise im Vergleich: Free mit 3 aktiven dynamischen QR-Codes, Pro mit 50 und Business mit 500 inklusive Massen-Erstellung.',
alternates: {
canonical: absoluteUrl('/de/preise'),
languages: alternates.languages,
},
robots: {
index: true,
follow: true,
},
openGraph: {
title: 'Preise & Pläne | QR Master',
description:
'QR Master Preise im Vergleich: Free mit 3 aktiven dynamischen QR-Codes, Pro mit 50 und Business mit 500 inklusive Massen-Erstellung.',
url: absoluteUrl('/de/preise'),
type: 'website',
locale: 'de_DE',
images: [
{
url: 'https://www.qrmaster.net/og-image.png',
width: 1200,
height: 630,
alt: 'QR Master Preise & Pläne',
},
],
},
};
const faqItems = [
{
question: 'Wie viele dynamische QR-Codes sind in den einzelnen Plänen enthalten?',
answer:
'Free enthält 3 aktive dynamische QR-Codes. Pro enthält 50 dynamische QR-Codes. Business enthält 500 dynamische QR-Codes.',
},
{
question: 'Enthalten alle Pläne statische QR-Codes?',
answer: 'Ja. Alle Pläne enthalten unbegrenzt viele statische QR-Codes.',
},
{
question: 'Welcher Plan enthält die Massen-Erstellung (Bulk)?',
answer: 'Die Massen-Erstellung von QR-Codes ist im Business-Plan enthalten.',
},
{
question: 'Welche Pläne enthalten Statistiken und Branding?',
answer:
'Der Free-Plan enthält grundlegendes Scan-Tracking. Pro ergänzt erweiterte Statistiken und individuelles Branding. Business enthält alles aus Pro plus Massen-Erstellung und Prioritäts-Support per E-Mail.',
},
];
const breadcrumbItems = [
{ name: 'Startseite', url: '/qr-code-erstellen' },
{ name: 'Preise', url: '/de/preise' },
];
export default function PreisePage() {
const t = getDictionary('de');
return (
<>
<SeoJsonLd data={[breadcrumbSchema(breadcrumbItems), faqPageSchema(faqItems)]} />
<div className="bg-white">
<Pricing t={t} />
<div className="container mx-auto max-w-5xl px-4 py-10 sm:px-6 lg:px-8">
<FAQSection items={faqItems} title="Fragen zu den Preisen" />
</div>
</div>
</>
);
}

View File

@@ -7,6 +7,8 @@ import {
publishedComparisonPages, publishedComparisonPages,
publishedGuidePages, publishedGuidePages,
} from '../lib/pseo-published-pages'; } from '../lib/pseo-published-pages';
import { publishedUseCases } from '../lib/growth-pages';
import { useCasePagesDe } from '../lib/growth-pages-de';
export default function sitemap(): MetadataRoute.Sitemap { export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://www.qrmaster.net'; const baseUrl = 'https://www.qrmaster.net';
@@ -32,6 +34,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
'paypal-qr-code', 'paypal-qr-code',
'zoom-qr-code', 'zoom-qr-code',
'teams-qr-code', 'teams-qr-code',
'google-review-qr-code',
'barcode-generator', 'barcode-generator',
]; ];
@@ -58,6 +61,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
const highPriorityTools: Record<string, number> = { const highPriorityTools: Record<string, number> = {
'barcode-generator': 0.95, 'barcode-generator': 0.95,
'google-review-qr-code': 0.9,
}; };
const toolLastModified: Record<string, Date> = { const toolLastModified: Record<string, Date> = {
@@ -111,66 +115,25 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: 'weekly' as const, changeFrequency: 'weekly' as const,
priority: 0.9, priority: 0.9,
}, },
...publishedUseCases.map((useCase) => ({
url: `${baseUrl}${useCase.href}`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
})),
// German pages under /de
{ {
url: `${baseUrl}/use-cases/restaurant-menu-qr-codes`, url: `${baseUrl}/de/preise`,
lastModified: new Date(), lastModified: new Date(),
changeFrequency: 'monthly' as const, changeFrequency: 'monthly' as const,
priority: 0.8, priority: 0.8,
}, },
{ ...Object.values(useCasePagesDe).map((useCase) => ({
url: `${baseUrl}/use-cases/business-card-qr-codes`, url: `${baseUrl}${useCase.href}`,
lastModified: new Date(), lastModified: new Date(),
changeFrequency: 'monthly' as const, changeFrequency: 'monthly' as const,
priority: 0.8, priority: 0.8,
}, })),
{
url: `${baseUrl}/use-cases/event-qr-codes`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
{
url: `${baseUrl}/use-cases/flyer-qr-codes`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
{
url: `${baseUrl}/use-cases/packaging-qr-codes`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
{
url: `${baseUrl}/use-cases/real-estate-sign-qr-codes`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
{
url: `${baseUrl}/use-cases/feedback-qr-codes`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
{
url: `${baseUrl}/use-cases/qr-codes-for-review-collection`,
lastModified: new Date('2026-05-11'),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
{
url: `${baseUrl}/use-cases/payment-qr-codes`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
{
url: `${baseUrl}/use-cases/coupon-qr-codes`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
{ {
url: `${baseUrl}/qr-code-for-marketing-campaigns`, url: `${baseUrl}/qr-code-for-marketing-campaigns`,
lastModified: new Date(), lastModified: new Date(),

View File

@@ -42,8 +42,9 @@ const TOOLS = [
{ icon: Youtube, name: 'YouTube', description: 'Open YouTube video', href: '/tools/youtube-qr-code', color: 'text-red-600', bg: 'bg-red-50' }, { icon: Youtube, name: 'YouTube', description: 'Open YouTube video', href: '/tools/youtube-qr-code', color: 'text-red-600', bg: 'bg-red-50' },
{ icon: MessageCircle, name: 'WhatsApp', description: 'Send WhatsApp message', href: '/tools/whatsapp-qr-code', color: 'text-green-600', bg: 'bg-green-50' }, { icon: MessageCircle, name: 'WhatsApp', description: 'Send WhatsApp message', href: '/tools/whatsapp-qr-code', color: 'text-green-600', bg: 'bg-green-50' },
{ icon: Music, name: 'TikTok', description: 'Open TikTok profile', href: '/tools/tiktok-qr-code', color: 'text-slate-900', bg: 'bg-slate-100' }, { icon: Music, name: 'TikTok', description: 'Open TikTok profile', href: '/tools/tiktok-qr-code', color: 'text-slate-900', bg: 'bg-slate-100' },
{ icon: MapPin, name: 'Location', description: 'Share GPS coordinates', href: '/tools/location-qr-code', color: 'text-emerald-500', bg: 'bg-emerald-50' }, { icon: MapPin, name: 'Location', description: 'Share GPS coordinates', href: '/tools/geolocation-qr-code', color: 'text-emerald-500', bg: 'bg-emerald-50' },
{ icon: Phone, name: 'Call', description: 'Start a phone call', href: '/tools/phone-qr-code', color: 'text-teal-500', bg: 'bg-teal-50' }, { icon: Phone, name: 'Call', description: 'Start a phone call', href: '/tools/call-qr-code-generator', color: 'text-teal-500', bg: 'bg-teal-50' },
{ icon: Star, name: 'Google Review', description: 'Collect more reviews', href: '/tools/google-review-qr-code', color: 'text-yellow-500', bg: 'bg-yellow-50' },
{ icon: Barcode, name: 'Barcode', description: 'Free online barcode generator', href: '/tools/barcode-generator', color: 'text-amber-600', bg: 'bg-amber-50' }, { icon: Barcode, name: 'Barcode', description: 'Free online barcode generator', href: '/tools/barcode-generator', color: 'text-amber-600', bg: 'bg-amber-50' },
]; ];

View File

@@ -1,47 +1,47 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { Hero } from '@/components/marketing/Hero'; import { Hero } from '@/components/marketing/Hero';
import AIComingSoonBanner from '@/components/marketing/AIComingSoonBanner'; import AIComingSoonBanner from '@/components/marketing/AIComingSoonBanner';
import { StatsStrip } from '@/components/marketing/StatsStrip'; import { StatsStrip } from '@/components/marketing/StatsStrip';
import { TemplateCards } from '@/components/marketing/TemplateCards'; import { TemplateCards } from '@/components/marketing/TemplateCards';
import { InstantGenerator } from '@/components/marketing/InstantGenerator'; import { InstantGenerator } from '@/components/marketing/InstantGenerator';
import { StaticVsDynamic } from '@/components/marketing/StaticVsDynamic'; import { StaticVsDynamic } from '@/components/marketing/StaticVsDynamic';
import { Features } from '@/components/marketing/Features'; import { Features } from '@/components/marketing/Features';
import { Pricing } from '@/components/marketing/Pricing'; import { Pricing } from '@/components/marketing/Pricing';
import { FAQ } from '@/components/marketing/FAQ'; import { FAQ } from '@/components/marketing/FAQ';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { ReprintCalculatorTeaser } from '@/components/marketing/ReprintCalculatorTeaser'; import { ReprintCalculatorTeaser } from '@/components/marketing/ReprintCalculatorTeaser';
import { ScrollToTop } from '@/components/ui/ScrollToTop'; import { ScrollToTop } from '@/components/ui/ScrollToTop';
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid'; import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
import { TestimonialsCarousel } from '@/components/marketing/TestimonialsCarousel'; import { TestimonialsCarousel } from '@/components/marketing/TestimonialsCarousel';
import { testimonials } from '@/lib/testimonial-data'; import { testimonials } from '@/lib/testimonial-data';
import en from '@/i18n/en.json'; import en from '@/i18n/en.json';
import { allUseCases } from '@/lib/growth-pages'; import { publishedUseCases } from '@/lib/growth-pages';
export default function HomePageClient() { export default function HomePageClient() {
// Always use English for marketing pages // Always use English for marketing pages
const t = en; const t = en;
const industries = [ const industries = [
'Restaurant Chain', 'Restaurant Chain',
'Tech Startup', 'Tech Startup',
'Real Estate', 'Real Estate',
'Event Agency', 'Event Agency',
'Retail Store', 'Retail Store',
'Healthcare', 'Healthcare',
]; ];
return ( return (
<> <>
<Hero t={t} /> <Hero t={t} />
{/* Main Interaction: Generator */} {/* Main Interaction: Generator */}
<InstantGenerator t={t} /> <InstantGenerator t={t} />
<AIComingSoonBanner /> <AIComingSoonBanner />
{/* Free Tools Grid */} {/* Free Tools Grid */}
<FreeToolsGrid /> <FreeToolsGrid />
@@ -133,7 +133,7 @@ export default function HomePageClient() {
</p> </p>
</div> </div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{allUseCases.slice(0, 8).map((item) => ( {publishedUseCases.slice(0, 8).map((item) => (
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
@@ -157,22 +157,22 @@ export default function HomePageClient() {
</section> </section>
{/* Testimonials Carousel */} {/* Testimonials Carousel */}
<TestimonialsCarousel testimonials={testimonials} /> <TestimonialsCarousel testimonials={testimonials} />
<React.Fragment> <React.Fragment>
<StaticVsDynamic t={t} /> <StaticVsDynamic t={t} />
<ReprintCalculatorTeaser /> <ReprintCalculatorTeaser />
<Features t={t} /> <Features t={t} />
</React.Fragment> </React.Fragment>
{/* Pricing Section */} {/* Pricing Section */}
<Pricing t={t} /> <Pricing t={t} />
{/* FAQ Section */} {/* FAQ Section */}
<FAQ t={t} /> <FAQ t={t} />
{/* Scroll to Top Button */} {/* Scroll to Top Button */}
<ScrollToTop /> <ScrollToTop />
</> </>
); );
} }

View File

@@ -6,10 +6,10 @@ import { usePathname } from 'next/navigation';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Footer } from '@/components/ui/Footer'; import { Footer } from '@/components/ui/Footer';
import de from '@/i18n/de.json'; import de from '@/i18n/de.json';
import { ChevronDown, Wifi, Contact, MessageCircle, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users, Barcode as BarcodeIcon } from 'lucide-react'; import { ChevronDown, Wifi, Contact, MessageCircle, Link2, Type, Mail, MessageSquare, Phone, Calendar, MapPin, Facebook, Instagram, Twitter, Youtube, Music, Bitcoin, CreditCard, Video, Users, Star, Barcode as BarcodeIcon } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import Image from 'next/image'; import Image from 'next/image';
export default function MarketingLayout({ export default function MarketingLayout({
children, children,
@@ -63,6 +63,7 @@ export default function MarketingLayout({
{ name: 'PayPal', description: 'Receive payments', href: '/tools/paypal-qr-code', icon: CreditCard, color: 'text-blue-700', bgColor: 'bg-blue-50' }, { name: 'PayPal', description: 'Receive payments', href: '/tools/paypal-qr-code', icon: CreditCard, color: 'text-blue-700', bgColor: 'bg-blue-50' },
{ name: 'Zoom', description: 'Join Zoom meeting', href: '/tools/zoom-qr-code', icon: Video, color: 'text-sky-500', bgColor: 'bg-sky-50' }, { name: 'Zoom', description: 'Join Zoom meeting', href: '/tools/zoom-qr-code', icon: Video, color: 'text-sky-500', bgColor: 'bg-sky-50' },
{ name: 'Teams', description: 'Join Teams meeting', href: '/tools/teams-qr-code', icon: Users, color: 'text-violet-500', bgColor: 'bg-violet-50' }, { name: 'Teams', description: 'Join Teams meeting', href: '/tools/teams-qr-code', icon: Users, color: 'text-violet-500', bgColor: 'bg-violet-50' },
{ name: 'Google Bewertungen', description: 'Mehr Bewertungen sammeln', href: '/tools/google-review-qr-code', icon: Star, color: 'text-yellow-500', bgColor: 'bg-yellow-50' },
{ name: 'Barcode', description: 'Barcode erstellen', href: '/tools/barcode-generator', icon: BarcodeIcon, color: 'text-slate-800', bgColor: 'bg-slate-100' }, { name: 'Barcode', description: 'Barcode erstellen', href: '/tools/barcode-generator', icon: BarcodeIcon, color: 'text-slate-800', bgColor: 'bg-slate-100' },
]; ];
@@ -72,8 +73,11 @@ export default function MarketingLayout({
<div className="sr-only" aria-hidden="false"> <div className="sr-only" aria-hidden="false">
<nav aria-label="Seitenübersicht"> <nav aria-label="Seitenübersicht">
<ul> <ul>
<li><a href="/">Startseite</a></li> <li><a href="/qr-code-erstellen">Startseite</a></li>
<li><a href="/pricing">Preise</a></li> <li><a href="/de/preise">Preise</a></li>
<li><a href="/de/qr-codes-fuer-fitnessstudios">QR-Codes für Fitnessstudios</a></li>
<li><a href="/de/qr-codes-fuer-events">QR-Codes für Events</a></li>
<li><a href="/de/qr-codes-fuer-immobilienschilder">QR-Codes für Immobilienschilder</a></li>
<li><a href="/blog">Blog</a></li> <li><a href="/blog">Blog</a></li>
<li><a href="/faq">FAQ</a></li> <li><a href="/faq">FAQ</a></li>
<li><a href="/login">Anmelden</a></li> <li><a href="/login">Anmelden</a></li>
@@ -98,6 +102,7 @@ export default function MarketingLayout({
<li><a href="/tools/paypal-qr-code">PayPal QR Code</a></li> <li><a href="/tools/paypal-qr-code">PayPal QR Code</a></li>
<li><a href="/tools/zoom-qr-code">Zoom QR Code</a></li> <li><a href="/tools/zoom-qr-code">Zoom QR Code</a></li>
<li><a href="/tools/teams-qr-code">Teams QR Code</a></li> <li><a href="/tools/teams-qr-code">Teams QR Code</a></li>
<li><a href="/tools/google-review-qr-code">Google Bewertungen QR Code</a></li>
<li><a href="/tools/barcode-generator">Barcode Generator</a></li> <li><a href="/tools/barcode-generator">Barcode Generator</a></li>
</ul> </ul>
</nav> </nav>
@@ -109,20 +114,20 @@ export default function MarketingLayout({
> >
<nav className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl h-20 flex items-center justify-between"> <nav className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl h-20 flex items-center justify-between">
{/* Logo */} {/* Logo */}
<Link href="/" className="flex items-center space-x-3 group"> <Link href="/qr-code-erstellen" className="flex items-center space-x-3 group">
<div className="relative w-16 h-16 overflow-hidden rounded-full shadow-indigo-200 shadow-lg group-hover:scale-105 transition-transform duration-200"> <div className="relative w-16 h-16 overflow-hidden rounded-full shadow-indigo-200 shadow-lg group-hover:scale-105 transition-transform duration-200">
<Image <Image
src="/logo.svg" src="/logo.svg"
alt="QR Master" alt="QR Master"
fill fill
sizes="64px" sizes="64px"
className="object-cover" className="object-cover"
priority priority
/> />
</div> </div>
<span className="text-xl font-bold text-slate-900 tracking-tight group-hover:text-indigo-600 transition-colors">QR Master</span> <span className="text-xl font-bold text-slate-900 tracking-tight group-hover:text-indigo-600 transition-colors">QR Master</span>
</Link> </Link>
{/* Desktop Navigation */} {/* Desktop Navigation */}
@@ -176,7 +181,7 @@ export default function MarketingLayout({
<Link href="/#features" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors"> <Link href="/#features" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
{t.nav.features} {t.nav.features}
</Link> </Link>
<Link href="/#pricing" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors"> <Link href="/de/preise" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
{t.nav.pricing} {t.nav.pricing}
</Link> </Link>
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors"> <Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
@@ -265,7 +270,7 @@ export default function MarketingLayout({
<div className="h-px bg-slate-100 my-2"></div> <div className="h-px bg-slate-100 my-2"></div>
<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="/#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="/de/preise" 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="/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="/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="/#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> <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>

View File

@@ -38,6 +38,40 @@ type LinkCard = {
description: string; description: string;
}; };
export type UseCaseTemplateLabels = {
badge: string;
subBenefits: { title: string; body: string }[];
nowLabel: string;
setupPreviewLabel: string;
comparisonLeftTitle: string;
comparisonRightTitle: string;
guidelinesEyebrow: string;
relatedResourcesTitle: string;
faqTitle?: string;
nextStepEyebrow: string;
ctaBannerTitle: string;
};
const defaultLabels: UseCaseTemplateLabels = {
badge: "Use Case",
subBenefits: [
{ title: "Print Stability", body: "Keep printed tables & flyers clean and permanent" },
{ title: "Instant Redirection", body: "Update links instantly without reprinting codes" },
{ title: "Attribution Flows", body: "Built-in campaign tracking and flow analysis" },
{ title: "Mobile Optimized", body: "Designed to load fast on mobile data connections" },
],
nowLabel: "now",
setupPreviewLabel: "Setup Preview",
comparisonLeftTitle: "Static QR",
comparisonRightTitle: "Dynamic QR",
guidelinesEyebrow: "Standard Guidelines",
relatedResourcesTitle: "Related resources",
nextStepEyebrow: "Next step",
ctaBannerTitle: "Start with a QR setup that keeps your print permanent.",
};
const subBenefitIcons = [ShieldCheck, RefreshCw, BarChart3, Zap];
type UseCasePageTemplateProps = { type UseCasePageTemplateProps = {
title: string; title: string;
description: string; description: string;
@@ -78,16 +112,19 @@ type UseCasePageTemplateProps = {
authoritySignals?: string[]; authoritySignals?: string[];
directAnswer?: string; directAnswer?: string;
schemaData?: Record<string, unknown>[]; schemaData?: Record<string, unknown>[];
labels?: UseCaseTemplateLabels;
}; };
export function buildUseCaseMetadata({ export function buildUseCaseMetadata({
title, title,
description, description,
canonicalPath, canonicalPath,
dePath,
}: { }: {
title: string; title: string;
description: string; description: string;
canonicalPath: string; canonicalPath: string;
dePath?: string;
}): Metadata { }): Metadata {
const canonical = `https://www.qrmaster.net${canonicalPath}`; const canonical = `https://www.qrmaster.net${canonicalPath}`;
@@ -101,6 +138,7 @@ export function buildUseCaseMetadata({
languages: { languages: {
"x-default": canonical, "x-default": canonical,
en: canonical, en: canonical,
...(dePath ? { de: `https://www.qrmaster.net${dePath}` } : {}),
}, },
}, },
openGraph: { openGraph: {
@@ -395,6 +433,7 @@ export function UseCasePageTemplate({
authoritySignals = [], authoritySignals = [],
directAnswer, directAnswer,
schemaData = [], schemaData = [],
labels = defaultLabels,
}: UseCasePageTemplateProps) { }: UseCasePageTemplateProps) {
return ( return (
<> <>
@@ -419,7 +458,7 @@ export function UseCasePageTemplate({
<div className="grid gap-16 lg:grid-cols-[1.15fr_0.85fr] lg:items-center"> <div className="grid gap-16 lg:grid-cols-[1.15fr_0.85fr] lg:items-center">
<div className="space-y-8"> <div className="space-y-8">
<div className="inline-flex items-center rounded-md border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold tracking-wider text-slate-500 uppercase shadow-sm"> <div className="inline-flex items-center rounded-md border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-semibold tracking-wider text-slate-500 uppercase shadow-sm">
<span>Use Case</span> <span>{labels.badge}</span>
</div> </div>
<div className="space-y-5"> <div className="space-y-5">
@@ -433,12 +472,7 @@ export function UseCasePageTemplate({
{/* Sub-benefits (Styled as authentic, clean Framer-style Apple Notification banners) */} {/* Sub-benefits (Styled as authentic, clean Framer-style Apple Notification banners) */}
<div className="grid gap-4 sm:grid-cols-2"> <div className="grid gap-4 sm:grid-cols-2">
{[ {labels.subBenefits.map((benefit, idx) => ({ ...benefit, icon: subBenefitIcons[idx % subBenefitIcons.length] })).map((item) => (
{ title: "Print Stability", body: "Keep printed tables & flyers clean and permanent", icon: ShieldCheck },
{ title: "Instant Redirection", body: "Update links instantly without reprinting codes", icon: RefreshCw },
{ title: "Attribution Flows", body: "Built-in campaign tracking and flow analysis", icon: BarChart3 },
{ title: "Mobile Optimized", body: "Designed to load fast on mobile data connections", icon: Zap },
].map((item) => (
<div <div
key={item.title} key={item.title}
className="flex items-center gap-3.5 rounded-[1.25rem] bg-[#f4f4f5]/90 border border-slate-200/30 p-3.5 shadow-[0_8px_24px_rgba(0,0,0,0.02)] backdrop-blur-md transition-all duration-200 hover:scale-[1.01] hover:bg-[#f4f4f5]" className="flex items-center gap-3.5 rounded-[1.25rem] bg-[#f4f4f5]/90 border border-slate-200/30 p-3.5 shadow-[0_8px_24px_rgba(0,0,0,0.02)] backdrop-blur-md transition-all duration-200 hover:scale-[1.01] hover:bg-[#f4f4f5]"
@@ -449,7 +483,7 @@ export function UseCasePageTemplate({
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-baseline justify-between"> <div className="flex items-baseline justify-between">
<span className="text-[13px] font-semibold text-slate-900 truncate">{item.title}</span> <span className="text-[13px] font-semibold text-slate-900 truncate">{item.title}</span>
<span className="text-[10px] text-slate-400 font-medium shrink-0 ml-2">now</span> <span className="text-[10px] text-slate-400 font-medium shrink-0 ml-2">{labels.nowLabel}</span>
</div> </div>
<p className="text-[12px] text-slate-500 leading-snug mt-0.5">{item.body}</p> <p className="text-[12px] text-slate-500 leading-snug mt-0.5">{item.body}</p>
</div> </div>
@@ -502,7 +536,7 @@ export function UseCasePageTemplate({
<span className="w-2 h-2 rounded-full bg-rose-400/80" /> <span className="w-2 h-2 rounded-full bg-rose-400/80" />
<span className="w-2 h-2 rounded-full bg-amber-400/80" /> <span className="w-2 h-2 rounded-full bg-amber-400/80" />
<span className="w-2 h-2 rounded-full bg-emerald-400/80" /> <span className="w-2 h-2 rounded-full bg-emerald-400/80" />
<span className="text-[9px] text-slate-400 font-semibold ml-4 tracking-wide uppercase">Setup Preview</span> <span className="text-[9px] text-slate-400 font-semibold ml-4 tracking-wide uppercase">{labels.setupPreviewLabel}</span>
</div> </div>
{/* Visual Container */} {/* Visual Container */}
<div className="relative aspect-[4/3] overflow-hidden rounded-xl bg-slate-50/50"> <div className="relative aspect-[4/3] overflow-hidden rounded-xl bg-slate-50/50">
@@ -536,8 +570,8 @@ export function UseCasePageTemplate({
whatIsIt={answer} whatIsIt={answer}
whenToUse={whenToUse} whenToUse={whenToUse}
comparison={{ comparison={{
leftTitle: "Static QR", leftTitle: labels.comparisonLeftTitle,
rightTitle: "Dynamic QR", rightTitle: labels.comparisonRightTitle,
items: comparisonItems, items: comparisonItems,
}} }}
howTo={{ howTo={{
@@ -613,7 +647,7 @@ export function UseCasePageTemplate({
<div className="flex items-center justify-between gap-4 border-b border-slate-100 pb-5"> <div className="flex items-center justify-between gap-4 border-b border-slate-100 pb-5">
<div> <div>
<div className="text-xs font-semibold uppercase tracking-wider text-slate-450"> <div className="text-xs font-semibold uppercase tracking-wider text-slate-450">
Standard Guidelines {labels.guidelinesEyebrow}
</div> </div>
<h2 className="mt-2 text-2xl font-bold text-slate-900 sm:text-3xl -tracking-[0.02em]"> <h2 className="mt-2 text-2xl font-bold text-slate-900 sm:text-3xl -tracking-[0.02em]">
{checklistTitle} {checklistTitle}
@@ -645,7 +679,7 @@ export function UseCasePageTemplate({
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-slate-50 border border-slate-100 text-slate-550"> <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-slate-50 border border-slate-100 text-slate-550">
<Link2 className="h-5 w-5" /> <Link2 className="h-5 w-5" />
</div> </div>
<h2 className="text-2xl font-bold text-slate-900 sm:text-3xl -tracking-[0.02em]">Related resources</h2> <h2 className="text-2xl font-bold text-slate-900 sm:text-3xl -tracking-[0.02em]">{labels.relatedResourcesTitle}</h2>
</div> </div>
<div className="mt-6 space-y-4"> <div className="mt-6 space-y-4">
@@ -676,7 +710,7 @@ export function UseCasePageTemplate({
{/* FAQs */} {/* FAQs */}
<div className="container mx-auto max-w-5xl px-4 pb-12 sm:px-6 lg:px-8"> <div className="container mx-auto max-w-5xl px-4 pb-12 sm:px-6 lg:px-8">
<FAQSection items={faq} title={`${title} FAQ`} /> <FAQSection items={faq} title={labels.faqTitle ?? `${title} FAQ`} />
</div> </div>
{/* Bottom CTA Banner (Flat light-gray card, no gradients) */} {/* Bottom CTA Banner (Flat light-gray card, no gradients) */}
@@ -686,10 +720,10 @@ export function UseCasePageTemplate({
<div className="relative z-10 flex flex-col gap-8 lg:flex-row lg:items-center lg:justify-between"> <div className="relative z-10 flex flex-col gap-8 lg:flex-row lg:items-center lg:justify-between">
<div className="max-w-2xl space-y-3"> <div className="max-w-2xl space-y-3">
<div className="text-xs font-semibold uppercase tracking-wider text-slate-450"> <div className="text-xs font-semibold uppercase tracking-wider text-slate-450">
Next step {labels.nextStepEyebrow}
</div> </div>
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl -tracking-[0.02em] leading-tight text-slate-950"> <h2 className="text-3xl font-bold tracking-tight sm:text-4xl -tracking-[0.02em] leading-tight text-slate-950">
Start with a QR setup that keeps your print permanent. {labels.ctaBannerTitle}
</h2> </h2>
<p className="text-base leading-relaxed text-slate-500"> <p className="text-base leading-relaxed text-slate-500">
{description} {description}

View File

@@ -1,7 +1,7 @@
import Link from 'next/link'; import Link from 'next/link';
import en from '@/i18n/en.json'; import en from '@/i18n/en.json';
import { Instagram, Twitter, Linkedin, Facebook } from 'lucide-react'; import { Instagram, Twitter, Linkedin, Facebook } from 'lucide-react';
import { allUseCases } from '@/lib/growth-pages'; import { publishedUseCases } from '@/lib/growth-pages';
import { industryPages } from '@/lib/industry-pages'; import { industryPages } from '@/lib/industry-pages';
interface FooterProps { interface FooterProps {
@@ -93,7 +93,7 @@ export function Footer({ variant = 'marketing', t }: FooterProps) {
<div> <div>
<h3 className={`font-semibold mb-4 ${isDashboard ? 'text-gray-900' : ''}`}>Use Cases</h3> <h3 className={`font-semibold mb-4 ${isDashboard ? 'text-gray-900' : ''}`}>Use Cases</h3>
<ul className={`space-y-2 ${isDashboard ? 'text-gray-500' : 'text-gray-400'}`}> <ul className={`space-y-2 ${isDashboard ? 'text-gray-500' : 'text-gray-400'}`}>
{allUseCases.map((uc) => ( {publishedUseCases.map((uc) => (
<li key={uc.slug}> <li key={uc.slug}>
<Link href={uc.href} className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}> <Link href={uc.href} className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>
{uc.title} {uc.title}

File diff suppressed because it is too large Load Diff

1308
src/lib/growth-pages-de.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -220,16 +220,16 @@ export const allUseCases: UseCaseLink[] = [
parentTitle: 'Dynamic QR Code Generator', parentTitle: 'Dynamic QR Code Generator',
ctaLabel: 'Create your payment QR code', ctaLabel: 'Create your payment QR code',
}, },
{ {
slug: 'qr-codes-for-gyms', slug: 'qr-codes-for-gyms',
href: '/use-cases/qr-codes-for-gyms', href: '/use-cases/qr-codes-for-gyms',
title: 'QR Codes for Gyms', title: 'QR Codes for Gyms',
cluster: 'gyms', cluster: 'gyms',
summary: 'Link workout equipment to tutorial videos, schedules, or trials without reprinting stickers on every machine.', summary: 'Link workout equipment to tutorial videos, schedules, or trials without reprinting stickers on every machine.',
parentHref: '/dynamic-qr-code-generator', parentHref: '/dynamic-qr-code-generator',
parentTitle: 'Dynamic QR Code Generator', parentTitle: 'Dynamic QR Code Generator',
ctaLabel: 'Create your gym QR code', ctaLabel: 'Create your gym QR code',
}, },
{ {
slug: 'coupon-qr-codes', slug: 'coupon-qr-codes',
href: '/use-cases/coupon-qr-codes', href: '/use-cases/coupon-qr-codes',
@@ -243,9 +243,16 @@ export const allUseCases: UseCaseLink[] = [
}, },
]; ];
export const featuredUseCases: UseCaseLink[] = allUseCases.filter((item) => // Slugs that 301-redirect elsewhere (see next.config.mjs) — must not be
// linked from nav, homepage, or sitemap.
const redirectedUseCaseSlugs = ['restaurant-menu-qr-codes'];
export const publishedUseCases: UseCaseLink[] = allUseCases.filter(
(item) => !redirectedUseCaseSlugs.includes(item.slug)
);
export const featuredUseCases: UseCaseLink[] = publishedUseCases.filter((item) =>
[ [
'restaurant-menu-qr-codes',
'business-card-qr-codes', 'business-card-qr-codes',
'flyer-qr-codes', 'flyer-qr-codes',
'packaging-qr-codes', 'packaging-qr-codes',
@@ -307,101 +314,101 @@ function bySlug(slug: string): UseCaseLink {
return match; return match;
} }
export const useCasePageContent: Record<string, UseCasePageContent> = { export const useCasePageContent: Record<string, UseCasePageContent> = {
'qr-codes-for-gyms': { 'qr-codes-for-gyms': {
...bySlug('qr-codes-for-gyms'), ...bySlug('qr-codes-for-gyms'),
eyebrow: 'Gyms & Fitness', eyebrow: 'Gyms & Fitness',
titleSuffix: 'for Equipment Tutorials, Schedules, and Trials', titleSuffix: 'for Equipment Tutorials, Schedules, and Trials',
metaDescription: metaDescription:
'Use gym QR codes to route members to equipment video tutorials, class schedules, and free trial signups without replacing stickers.', 'Use gym QR codes to route members to equipment video tutorials, class schedules, and free trial signups without replacing stickers.',
intro: intro:
'Gym QR codes work best when heavy-duty stickers on equipment stay permanent, while the tutorial videos or schedules they link to can be updated.', 'Gym QR codes work best when heavy-duty stickers on equipment stay permanent, while the tutorial videos or schedules they link to can be updated.',
answer: answer:
'A gym QR code should point members to an instructional video for the machine they are using, or direct prospects to a mobile signup flow.', 'A gym QR code should point members to an instructional video for the machine they are using, or direct prospects to a mobile signup flow.',
whenToUse: [ whenToUse: [
'You want to provide video instructions on how to use complex machines.', 'You want to provide video instructions on how to use complex machines.',
'You have class schedules that change weekly or seasonally.', 'You have class schedules that change weekly or seasonally.',
'You need a contactless way for walk-ins to sign up for a free trial.', 'You need a contactless way for walk-ins to sign up for a free trial.',
], ],
comparisonItems: [ comparisonItems: [
{ label: 'Sticker durability', text: 'Permanent application', value: true }, { label: 'Sticker durability', text: 'Permanent application', value: true },
{ label: 'Video updates', text: 'Change links anytime', value: true }, { label: 'Video updates', text: 'Change links anytime', value: true },
{ label: 'Member engagement', text: 'Track usage stats', value: true }, { label: 'Member engagement', text: 'Track usage stats', value: true },
], ],
howToSteps: [ howToSteps: [
'Create one dynamic QR code for each piece of equipment or studio door.', 'Create one dynamic QR code for each piece of equipment or studio door.',
'Print on durable, sweat-resistant vinyl stickers and apply to machines.', 'Print on durable, sweat-resistant vinyl stickers and apply to machines.',
'Update the linked videos or schedules in your dashboard as needed.', 'Update the linked videos or schedules in your dashboard as needed.',
], ],
workflowTitle: 'What a good gym QR setup should handle', workflowTitle: 'What a good gym QR setup should handle',
workflowIntro: workflowIntro:
'Stickers on gym equipment are hard to remove. The QR workflow should make your physical labels permanent while the digital content evolves.', 'Stickers on gym equipment are hard to remove. The QR workflow should make your physical labels permanent while the digital content evolves.',
workflowCards: [ workflowCards: [
{ {
title: 'Equipment tutorials', title: 'Equipment tutorials',
description: 'Link QR codes on weight machines to short YouTube or TikTok videos demonstrating proper form.', description: 'Link QR codes on weight machines to short YouTube or TikTok videos demonstrating proper form.',
}, },
{ {
title: 'Class schedules', title: 'Class schedules',
description: 'Place a QR code on the studio door that links to the live, up-to-date weekly class schedule.', description: 'Place a QR code on the studio door that links to the live, up-to-date weekly class schedule.',
}, },
{ {
title: 'Contactless signup', title: 'Contactless signup',
description: 'Put a QR code on the front door or window for after-hours visitors to claim a guest pass digitally.', description: 'Put a QR code on the front door or window for after-hours visitors to claim a guest pass digitally.',
}, },
], ],
checklistTitle: 'Gym QR checklist', checklistTitle: 'Gym QR checklist',
checklist: [ checklist: [
'Print QR codes on high-quality, sweat-resistant vinyl so they survive cleaning sprays.', 'Print QR codes on high-quality, sweat-resistant vinyl so they survive cleaning sprays.',
'Keep the URL destination dynamic so you never have to scrape off old stickers.', 'Keep the URL destination dynamic so you never have to scrape off old stickers.',
"Add small text next to the code, e.g., 'Scan to see how to use'.", "Add small text next to the code, e.g., 'Scan to see how to use'.",
'Ensure your gym has decent WiFi coverage for members to stream the videos.', 'Ensure your gym has decent WiFi coverage for members to stream the videos.',
], ],
supportLinks: [ supportLinks: [
{ {
href: '/dynamic-qr-code-generator', href: '/dynamic-qr-code-generator',
title: 'Commercial parent: Dynamic QR Code Generator', title: 'Commercial parent: Dynamic QR Code Generator',
description: 'Best fit for keeping equipment stickers permanent while updating video links.', description: 'Best fit for keeping equipment stickers permanent while updating video links.',
}, },
{ {
href: '/qr-code-tracking', href: '/qr-code-tracking',
title: 'Sibling page: QR Code Tracking', title: 'Sibling page: QR Code Tracking',
description: 'Useful to see which machines get the most scans and instructional video views.', description: 'Useful to see which machines get the most scans and instructional video views.',
}, },
{ {
href: '/use-cases/feedback-qr-codes', href: '/use-cases/feedback-qr-codes',
title: 'Feedback QR Codes', title: 'Feedback QR Codes',
description: 'Learn how to place QR codes in locker rooms for facility feedback.', description: 'Learn how to place QR codes in locker rooms for facility feedback.',
}, },
], ],
faq: [ faq: [
{ {
question: 'How do I prevent members from peeling off QR codes on gym equipment?', question: 'How do I prevent members from peeling off QR codes on gym equipment?',
answer: 'Use heavy-duty, commercial-grade vinyl stickers with strong adhesive. Place them on flat, metal surfaces away from where members typically grip the machines. Polycarbonate overlays can also prevent peeling and scratching.' answer: 'Use heavy-duty, commercial-grade vinyl stickers with strong adhesive. Place them on flat, metal surfaces away from where members typically grip the machines. Polycarbonate overlays can also prevent peeling and scratching.'
}, },
{ {
question: 'Should gym equipment QR codes link to YouTube or a custom video player?', question: 'Should gym equipment QR codes link to YouTube or a custom video player?',
answer: 'While YouTube is easy, an unlisted Vimeo video or a custom video player on your own domain is better. This prevents competitors\' ads from playing before the tutorial and keeps members focused on their workout.' answer: 'While YouTube is easy, an unlisted Vimeo video or a custom video player on your own domain is better. This prevents competitors\' ads from playing before the tutorial and keeps members focused on their workout.'
}, },
{ {
question: 'Can I track which machines are most popular using QR codes?', question: 'Can I track which machines are most popular using QR codes?',
answer: 'Yes! By using a unique dynamic QR code for each machine, you can track scan frequency in your dashboard. High scan rates usually indicate high usage, helping you decide which equipment to buy more of in the future.' answer: 'Yes! By using a unique dynamic QR code for each machine, you can track scan frequency in your dashboard. High scan rates usually indicate high usage, helping you decide which equipment to buy more of in the future.'
}, },
{ {
question: 'Do I need separate QR codes for iOS and Android users to download my gym app?', question: 'Do I need separate QR codes for iOS and Android users to download my gym app?',
answer: 'No. You can create a single dynamic App Store QR code that detects the user\'s device and automatically routes iPhones to the Apple App Store and Androids to the Google Play Store.' answer: 'No. You can create a single dynamic App Store QR code that detects the user\'s device and automatically routes iPhones to the Apple App Store and Androids to the Google Play Store.'
}, },
{ {
question: 'How do QR codes help with 24/7 contactless gym signups?', question: 'How do QR codes help with 24/7 contactless gym signups?',
answer: 'You can place a large QR code on your front window. When scanned, it routes prospects to a mobile signup form and waiver. Once completed, your system can automatically issue a temporary digital access pass to their phone.' answer: 'You can place a large QR code on your front window. When scanned, it routes prospects to a mobile signup form and waiver. Once completed, your system can automatically issue a temporary digital access pass to their phone.'
}, },
{ {
question: 'What happens if we replace a machine with a newer model?', question: 'What happens if we replace a machine with a newer model?',
answer: 'If you use dynamic QR codes, you don\'t need to change the physical sticker if you transfer it. Alternatively, if you apply a new sticker, you can just redirect the old QR code URL to the new machine\'s tutorial video.' answer: 'If you use dynamic QR codes, you don\'t need to change the physical sticker if you transfer it. Alternatively, if you apply a new sticker, you can just redirect the old QR code URL to the new machine\'s tutorial video.'
} }
], ],
heroImage: '/marketing/use-cases/qr-codes-for-gyms.png', heroImage: '/marketing/use-cases/qr-codes-for-gyms.png',
heroImageAlt: 'QR code sticker on a gym weight machine being scanned for a video tutorial', heroImageAlt: 'QR code sticker on a gym weight machine being scanned for a video tutorial',
}, },
'restaurant-menu-qr-codes': { 'restaurant-menu-qr-codes': {
...bySlug('restaurant-menu-qr-codes'), ...bySlug('restaurant-menu-qr-codes'),
@@ -668,12 +675,18 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
{ {
href: '/use-cases/flyer-qr-codes', href: '/use-cases/flyer-qr-codes',
title: 'Sibling page: Flyer QR Codes', title: 'Sibling page: Flyer QR Codes',
description: description:
'Useful when event promotion depends on measurable printed distribution before event day.', 'Useful when event promotion depends on measurable printed distribution before event day.',
}, },
], {
faq: [ href: '/tools/teams-qr-code',
{ title: 'Teams QR Code Generator',
description:
'Free tool so attendees join Teams meetings and webinars with one scan.',
},
],
faq: [
{
question: 'How do dynamic QR codes prevent disaster if our event schedule changes?', question: 'How do dynamic QR codes prevent disaster if our event schedule changes?',
answer: 'If a speaker cancels or a room changes after you\'ve printed 5,000 attendee badges, a dynamic QR code saves the day. You simply update the digital schedule URL in your dashboard, and everyone\'s printed badge instantly points to the new information.', answer: 'If a speaker cancels or a room changes after you\'ve printed 5,000 attendee badges, a dynamic QR code saves the day. You simply update the digital schedule URL in your dashboard, and everyone\'s printed badge instantly points to the new information.',
}, },
@@ -864,6 +877,12 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
description: description:
'Useful when packaging scans need to trigger a measurable offer or retention prompt.', 'Useful when packaging scans need to trigger a measurable offer or retention prompt.',
}, },
{
href: '/tools/barcode-generator',
title: 'Barcode Generator',
description:
'Free tool when packaging needs retail barcodes alongside QR codes.',
},
], ],
faq: [ faq: [
{ {
@@ -1063,6 +1082,12 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
description: description:
'Useful when the same offline surfaces support both feedback collection and post-visit offers.', 'Useful when the same offline surfaces support both feedback collection and post-visit offers.',
}, },
{
href: '/tools/google-review-qr-code',
title: 'Google Review QR Code Generator',
description:
'Free tool when the feedback ask should become a public Google review.',
},
], ],
faq: [ faq: [
{ {
@@ -1260,8 +1285,8 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
'Useful for direct payment-oriented workflows when PayPal is the intended post-scan action.', 'Useful for direct payment-oriented workflows when PayPal is the intended post-scan action.',
}, },
{ {
href: '/use-cases/restaurant-menu-qr-codes', href: '/restaurants',
title: 'Sibling page: Restaurant Menu QR Codes', title: 'Restaurant QR Codes',
description: description:
'Useful when table cards combine service information and a later payment or ordering action.', 'Useful when table cards combine service information and a later payment or ordering action.',
}, },
@@ -1443,6 +1468,11 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
title: 'Dynamic vs Static QR Codes', title: 'Dynamic vs Static QR Codes',
description: 'Existing editorial asset for choosing editable QR destinations.', description: 'Existing editorial asset for choosing editable QR destinations.',
}, },
{
href: '/tools/wifi-qr-code',
title: 'WiFi QR Code Generator',
description: 'Free tool so guests join the hotel WiFi without typing a password.',
},
], ],
faq: [ faq: [
{ {
@@ -1526,6 +1556,11 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
title: 'Dynamic vs Static QR Codes', title: 'Dynamic vs Static QR Codes',
description: 'Useful background for choosing dynamic QR destinations.', description: 'Useful background for choosing dynamic QR destinations.',
}, },
{
href: '/tools/google-review-qr-code',
title: 'Google Review QR Code Generator',
description: 'Free tool for turning happy clients at the counter into public Google reviews.',
},
], ],
faq: [ faq: [
{ {

36
src/lib/i18n/index.ts Normal file
View File

@@ -0,0 +1,36 @@
import en from '@/i18n/en.json';
import de from '@/i18n/de.json';
export type Locale = 'en' | 'de';
export const locales: Locale[] = ['en', 'de'];
export const defaultLocale: Locale = 'en';
const dictionaries = { en, de } as const;
export type Dictionary = (typeof dictionaries)[Locale];
// Server-side dictionary access. The JSON files are bundled statically, so this
// works in Server Components without async loading.
export function getDictionary(locale: Locale): Dictionary {
return dictionaries[locale] ?? dictionaries[defaultLocale];
}
const BASE_URL = 'https://www.qrmaster.net';
// Builds alternates metadata for a localized page pair. English lives at the
// root, German under /de with its own (possibly localized) path.
export function buildLanguageAlternates(paths: { en: string; de: string }) {
return {
canonical: `${BASE_URL}${paths.en}`,
languages: {
'x-default': `${BASE_URL}${paths.en}`,
en: `${BASE_URL}${paths.en}`,
de: `${BASE_URL}${paths.de}`,
},
};
}
export function absoluteUrl(path: string) {
return `${BASE_URL}${path}`;
}

View File

@@ -3,6 +3,8 @@ import dotenv from 'dotenv';
import { blogPosts } from '../lib/blog-data'; import { blogPosts } from '../lib/blog-data';
import { pillarMeta } from '../lib/pillar-data'; import { pillarMeta } from '../lib/pillar-data';
import { authors } from '../lib/author-data'; import { authors } from '../lib/author-data';
import { publishedUseCases } from '../lib/growth-pages';
import { useCasePagesDe } from '../lib/growth-pages-de';
dotenv.config(); dotenv.config();
@@ -55,34 +57,44 @@ export function getAllIndexableUrls(): string[] {
'wifi-qr-code', 'crypto-qr-code', 'event-qr-code', 'facebook-qr-code', 'wifi-qr-code', 'crypto-qr-code', 'event-qr-code', 'facebook-qr-code',
'instagram-qr-code', 'twitter-qr-code', 'youtube-qr-code', 'whatsapp-qr-code', 'instagram-qr-code', 'twitter-qr-code', 'youtube-qr-code', 'whatsapp-qr-code',
'tiktok-qr-code', 'geolocation-qr-code', 'call-qr-code-generator', 'paypal-qr-code', 'tiktok-qr-code', 'geolocation-qr-code', 'call-qr-code-generator', 'paypal-qr-code',
'zoom-qr-code', 'teams-qr-code', 'zoom-qr-code', 'teams-qr-code', 'google-review-qr-code',
].map(slug => `${baseUrl}/tools/${slug}`); ].map(slug => `${baseUrl}/tools/${slug}`);
// Use-case pages (English) and their German twins under /de
const useCasePages = [
`${baseUrl}/use-cases`,
...publishedUseCases.map(uc => `${baseUrl}${uc.href}`),
];
const germanPages = [
`${baseUrl}/de/preise`,
...Object.values(useCasePagesDe).map(uc => `${baseUrl}${uc.href}`),
];
// Blog posts // Blog posts
const blogPages = blogPosts.map(post => `${baseUrl}/blog/${post.slug}`); const blogPages = blogPosts.map(post => `${baseUrl}/blog/${post.slug}`);
// Main pages (synced with sitemap.ts) // Main pages (synced with sitemap.ts)
const mainPages = [ const mainPages = [
baseUrl, baseUrl,
`${baseUrl}/about`, `${baseUrl}/about`,
`${baseUrl}/contact`, `${baseUrl}/contact`,
`${baseUrl}/press`, `${baseUrl}/press`,
`${baseUrl}/testimonials`, `${baseUrl}/testimonials`,
`${baseUrl}/qr-code-erstellen`, `${baseUrl}/qr-code-erstellen`,
`${baseUrl}/qr-code-tracking`, `${baseUrl}/qr-code-tracking`,
`${baseUrl}/reprint-calculator`, `${baseUrl}/reprint-calculator`,
`${baseUrl}/dynamic-qr-code-generator`, `${baseUrl}/dynamic-qr-code-generator`,
`${baseUrl}/bulk-qr-code-generator`, `${baseUrl}/bulk-qr-code-generator`,
`${baseUrl}/custom-qr-code-generator`, `${baseUrl}/custom-qr-code-generator`,
`${baseUrl}/manage-qr-codes`, `${baseUrl}/manage-qr-codes`,
`${baseUrl}/pricing`, `${baseUrl}/pricing`,
`${baseUrl}/tools`, `${baseUrl}/tools`,
`${baseUrl}/features`, `${baseUrl}/features`,
`${baseUrl}/faq`, `${baseUrl}/faq`,
`${baseUrl}/blog`, `${baseUrl}/blog`,
`${baseUrl}/privacy`, `${baseUrl}/privacy`,
`${baseUrl}/newsletter`, `${baseUrl}/newsletter`,
]; ];
// Learn hub and pillar pages // Learn hub and pillar pages
const learnPages = [ const learnPages = [
@@ -93,7 +105,7 @@ export function getAllIndexableUrls(): string[] {
// Author pages // Author pages
const authorPages = authors.map(author => `${baseUrl}/authors/${author.slug}`); const authorPages = authors.map(author => `${baseUrl}/authors/${author.slug}`);
return [...mainPages, ...freeTools, ...blogPages, ...learnPages, ...authorPages]; return [...mainPages, ...freeTools, ...useCasePages, ...germanPages, ...blogPages, ...learnPages, ...authorPages];
} }
// If run directly // If run directly

View File

@@ -40,7 +40,7 @@ function attachAttributionCookie(req: NextRequest, response: NextResponse) {
return response; return response;
} }
export function middleware(req: NextRequest) { export function middleware(req: NextRequest) {
const path = req.nextUrl.pathname; const path = req.nextUrl.pathname;
const hostname = req.headers.get('host')?.split(':')[0] || req.nextUrl.hostname; const hostname = req.headers.get('host')?.split(':')[0] || req.nextUrl.hostname;
@@ -72,69 +72,70 @@ export function middleware(req: NextRequest) {
// Public routes that don't require authentication // Public routes that don't require authentication
const publicPaths = [ const publicPaths = [
'/', '/',
'/pricing', '/pricing',
'/faq', '/faq',
'/blog', '/blog',
'/login', '/login',
'/signup', '/signup',
'/privacy', '/privacy',
'/newsletter', '/newsletter',
'/restaurants', '/restaurants',
'/tools', '/tools',
'/features', '/features',
// '/guide', // Redirected to /learn/* // '/guide', // Redirected to /learn/*
'/qr-code-erstellen', '/de',
'/dynamic-qr-code-generator', '/qr-code-erstellen',
'/dynamic-barcode-generator', '/dynamic-qr-code-generator',
'/bulk-qr-code-generator', '/dynamic-barcode-generator',
'/qr-code-tracking', '/bulk-qr-code-generator',
'/qr-code-analytics', '/qr-code-tracking',
'/qr-code-analytics',
'/reprint-calculator', '/reprint-calculator',
'/custom-qr-code-generator', '/custom-qr-code-generator',
'/manage-qr-codes', '/manage-qr-codes',
'/coupon', '/coupon',
'/feedback', '/feedback',
'/vcard', '/vcard',
'/display', '/display',
'/contact', '/contact',
'/about', '/about',
'/learn', '/learn',
'/use-cases', '/use-cases',
'/authors', '/authors',
'/press', '/press',
'/testimonials', '/testimonials',
'/qr-code-for-marketing-campaigns', '/qr-code-for-marketing-campaigns',
'/qr-code-for', '/qr-code-for',
'/qr-code-print-size-guide', '/qr-code-print-size-guide',
'/use-cases/flyer-qr-codes', '/use-cases/flyer-qr-codes',
'/use-cases/packaging-qr-codes', '/use-cases/packaging-qr-codes',
'/use-cases/real-estate-sign-qr-codes', '/use-cases/real-estate-sign-qr-codes',
'/use-cases/feedback-qr-codes', '/use-cases/feedback-qr-codes',
'/use-cases/payment-qr-codes', '/use-cases/payment-qr-codes',
'/use-cases/coupon-qr-codes', '/use-cases/coupon-qr-codes',
'/alternatives', '/alternatives',
'/vs', '/vs',
]; ];
// Check if path is public // Check if path is public
const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/')); const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/'));
// Allow API routes // Allow API routes
if (path.startsWith('/api/')) { if (path.startsWith('/api/')) {
return attachAttributionCookie(req, NextResponse.next()); return attachAttributionCookie(req, NextResponse.next());
} }
// Allow redirect routes (QR code redirects) // Allow redirect routes (QR code redirects)
if (path.startsWith('/r/')) { if (path.startsWith('/r/')) {
return attachAttributionCookie(req, NextResponse.next()); return attachAttributionCookie(req, NextResponse.next());
} }
// Allow static files // Allow static files
if (path.includes('.') || path.startsWith('/_next')) { if (path.includes('.') || path.startsWith('/_next')) {
return attachAttributionCookie(req, NextResponse.next()); return attachAttributionCookie(req, NextResponse.next());
} }
// Allow public paths // Allow public paths
if (isPublicPath) { if (isPublicPath) {
return attachAttributionCookie(req, NextResponse.next()); return attachAttributionCookie(req, NextResponse.next());
@@ -158,7 +159,7 @@ export function middleware(req: NextRequest) {
// For protected routes, check for userId cookie // For protected routes, check for userId cookie
const userId = req.cookies.get('userId')?.value; const userId = req.cookies.get('userId')?.value;
if (!userId) { if (!userId) {
// Not authenticated - redirect to signup // Not authenticated - redirect to signup
const signupUrl = new URL('/signup', req.url); const signupUrl = new URL('/signup', req.url);
@@ -170,16 +171,16 @@ export function middleware(req: NextRequest) {
// Authenticated - allow access // Authenticated - allow access
return attachAttributionCookie(req, NextResponse.next()); return attachAttributionCookie(req, NextResponse.next());
} }
export const config = { export const config = {
matcher: [ matcher: [
/* /*
* Match all request paths except for the ones starting with: * Match all request paths except for the ones starting with:
* - _next/static (static files) * - _next/static (static files)
* - _next/image (image optimization files) * - _next/image (image optimization files)
* - favicon.ico (favicon file) * - favicon.ico (favicon file)
* - public folder * - public folder
*/ */
'/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)', '/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)',
], ],
}; };

View File

@@ -1,49 +0,0 @@
const fs = require('fs');
const file = 'src/lib/growth-pages.ts';
let content = fs.readFileSync(file, 'utf8');
const eventUseCase = ` {
slug: 'event-qr-codes',
href: '/use-cases/event-qr-codes',
title: 'QR Codes for Events & Conferences',
cluster: 'events',
summary: 'Use event QR codes for digital ticketing, session schedules, and attendee networking without worrying about last-minute print changes.',
parentHref: '/dynamic-qr-code-generator',
parentTitle: 'Dynamic QR Code Generator',
ctaLabel: 'Create your event QR code',
},
`;
const realEstateUseCase = ` {
slug: 'real-estate-sign-qr-codes',
href: '/use-cases/real-estate-sign-qr-codes',
title: 'Real Estate QR Codes for Yard Signs',
cluster: 'real-estate',
summary: 'Use real estate QR codes on for-sale signs and brochures to route buyers to current property tours and agent contacts without reprinting yard signs.',
parentHref: '/dynamic-qr-code-generator',
parentTitle: 'Dynamic QR Code Generator',
ctaLabel: 'Create your real estate QR',
},
`;
let changed = false;
if (!content.includes("slug: 'event-qr-codes'")) {
const lastCouponIdx = content.lastIndexOf("slug: 'coupon-qr-codes',");
const insertIdx = content.lastIndexOf(" {", lastCouponIdx);
content = content.substring(0, insertIdx) + eventUseCase + content.substring(insertIdx);
changed = true;
}
if (!content.includes("slug: 'real-estate-sign-qr-codes'")) {
const lastCouponIdx = content.lastIndexOf("slug: 'coupon-qr-codes',");
const insertIdx = content.lastIndexOf(" {", lastCouponIdx);
content = content.substring(0, insertIdx) + realEstateUseCase + content.substring(insertIdx);
changed = true;
}
if (changed) {
fs.writeFileSync(file, content);
console.log("Added remaining missing use cases to allUseCases");
} else {
console.log("Already present");
}

View File

@@ -1,20 +0,0 @@
const fs = require('fs');
const file = 'src/lib/growth-pages.ts';
let content = fs.readFileSync(file, 'utf8');
// The file has two sections: allUseCases and useCasePageContent
// They might both be duplicated.
function removeSecond(text, regexStr) {
const regex = new RegExp(regexStr, 'g');
let match;
let count = 0;
let indices = [];
while ((match = regex.exec(text)) !== null) {
indices.push(match.index);
}
if (indices.length > 1) {
console.log("Found " + indices.length + " instances of " + regexStr + ", removing from second instance");
// We will remove from the second instance to the end of the object
// This is tricky for nested objects.
}
}

View File

@@ -1,50 +0,0 @@
const fs = require('fs');
const file = 'src/components/marketing/HomePageClient.tsx';
let content = fs.readFileSync(file, 'utf8');
if (!content.includes("import { allUseCases }")) {
content = content.replace("import en from '@/i18n/en.json';", "import en from '@/i18n/en.json';\nimport { allUseCases } from '@/lib/growth-pages';");
}
const section = ` {/* Popular Use Cases Section */}
<section className="bg-slate-50 py-16 border-y border-slate-200">
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-8 max-w-3xl">
<h2 className="text-3xl font-bold text-slate-900">
Popular Use Cases
</h2>
<p className="mt-3 text-base leading-7 text-slate-600">
Discover how businesses across different industries use dynamic QR codes to track offline engagement and simplify customer journeys.
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{allUseCases.slice(0, 8).map((item) => (
<Link
key={item.href}
href={item.href}
className="group rounded-lg border border-slate-200 bg-white p-5 shadow-sm transition-all hover:shadow-md hover:border-blue-300"
>
<h3 className="text-lg font-semibold text-slate-900 group-hover:text-blue-700">
{item.title}
</h3>
<p className="mt-2 text-sm leading-6 text-slate-600 line-clamp-2">
{item.summary}
</p>
</Link>
))}
</div>
<div className="mt-8 text-center">
<Link href="/use-cases">
<Button variant="outline" className="font-semibold text-slate-700">View All Use Cases</Button>
</Link>
</div>
</div>
</section>
{/* Testimonials Carousel */}`;
if (!content.includes("Popular Use Cases Section")) {
content = content.replace("{/* Testimonials Carousel */}", section);
}
fs.writeFileSync(file, content);
console.log("Updated HomePageClient.tsx");

View File

@@ -1,101 +0,0 @@
const fs = require('fs');
let code = fs.readFileSync('src/app/(main)/(marketing)/MarketingLayout.tsx', 'utf8');
if (!code.includes("import { allUseCases }")) {
code = code.replace("import en from '@/i18n/en.json';", "import en from '@/i18n/en.json';\nimport { allUseCases } from '@/lib/growth-pages';\nimport { LayoutGrid } from 'lucide-react';");
}
if (!code.includes("const [useCasesOpen")) {
code = code.replace("const [toolsOpen, setToolsOpen] = useState(false);", "const [toolsOpen, setToolsOpen] = useState(false);\n const [useCasesOpen, setUseCasesOpen] = useState(false);");
code = code.replace("const [mobileToolsOpen, setMobileToolsOpen] = useState(false);", "const [mobileToolsOpen, setMobileToolsOpen] = useState(false);\n const [mobileUseCasesOpen, setMobileUseCasesOpen] = useState(false);");
code = code.replace("setToolsOpen(false);", "setToolsOpen(false);\n setUseCasesOpen(false);");
code = code.replace("setMobileToolsOpen(false);", "setMobileToolsOpen(false);\n setMobileUseCasesOpen(false);");
}
const useCasesDropdown = ` {/* Use Cases Dropdown */}
<div
className="relative group px-3 py-2"
onMouseEnter={() => setUseCasesOpen(true)}
onMouseLeave={() => setUseCasesOpen(false)}
>
<button className="flex items-center space-x-1 text-sm font-medium text-slate-600 group-hover:text-slate-900 transition-colors">
<span>Use Cases</span>
<ChevronDown className={cn("w-4 h-4 transition-transform duration-200", useCasesOpen && "rotate-180")} />
</button>
<AnimatePresence>
{useCasesOpen && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.15 }}
className="absolute left-1/2 -translate-x-1/2 top-full mt-2 w-[700px] bg-white rounded-2xl shadow-lg border border-slate-100 p-4 overflow-hidden"
>
<div className="grid grid-cols-2 gap-2">
{allUseCases.map((uc) => (
<Link
key={uc.slug}
href={uc.href}
className="flex items-center space-x-3 p-2.5 rounded-xl transition-colors hover:bg-slate-50"
>
<div className="p-2 rounded-lg shrink-0 bg-indigo-50 text-indigo-500">
<LayoutGrid className="w-4 h-4" />
</div>
<div>
<div className="text-sm font-semibold text-slate-900">{uc.title}</div>
</div>
</Link>
))}
</div>
<div className="mt-3 pt-3 border-t border-slate-100 -mx-4 -mb-4 px-4 py-3 text-center bg-slate-50/50">
<Link href="/use-cases" className="text-xs text-indigo-600 font-medium hover:underline">View All Use Cases &rarr;</Link>
</div>
</motion.div>
)}
</AnimatePresence>
</div>`;
if (!code.includes("<span>Use Cases</span>")) {
code = code.replace("{/* Tools Dropdown */}", useCasesDropdown + "\n\n {/* Tools Dropdown */}");
}
const mobileUseCasesAccordion = ` {/* Use Cases Accordion */}
<button
onClick={() => setMobileUseCasesOpen(!mobileUseCasesOpen)}
className="flex items-center justify-between w-full px-4 py-3 rounded-xl hover:bg-slate-50 text-slate-700 font-semibold"
>
<span>Use Cases</span>
<ChevronDown className={cn("w-5 h-5 transition-transform", mobileUseCasesOpen && "rotate-180")} />
</button>
<AnimatePresence>
{mobileUseCasesOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="overflow-hidden"
>
<div className="max-h-[50vh] overflow-y-auto pl-4 space-y-1 border-l-2 border-slate-100 ml-4">
{allUseCases.map((uc) => (
<Link
key={uc.slug}
href={uc.href}
className="block px-4 py-2.5 rounded-lg hover:bg-slate-50 text-slate-600 text-sm"
onClick={() => { setMobileMenuOpen(false); setMobileUseCasesOpen(false); }}
>
{uc.title}
</Link>
))}
</div>
</motion.div>
)}
</AnimatePresence>`;
if (!code.includes("{/* Use Cases Accordion */}")) {
code = code.replace("{/* Free Tools Accordion */}", mobileUseCasesAccordion + "\n\n {/* Free Tools Accordion */}");
}
fs.writeFileSync('src/app/(main)/(marketing)/MarketingLayout.tsx', code);
console.log("Updated MarketingLayout.tsx");

View File

@@ -1,197 +0,0 @@
const fs = require('fs');
const file = 'src/lib/growth-pages.ts';
let content = fs.readFileSync(file, 'utf8');
const newGymFaq = ` faq: [
{
question: 'How do I prevent members from peeling off QR codes on gym equipment?',
answer: 'Use heavy-duty, commercial-grade vinyl stickers with strong adhesive. Place them on flat, metal surfaces away from where members typically grip the machines. Polycarbonate overlays can also prevent peeling and scratching.'
},
{
question: 'Should gym equipment QR codes link to YouTube or a custom video player?',
answer: 'While YouTube is easy, an unlisted Vimeo video or a custom video player on your own domain is better. This prevents competitors\\' ads from playing before the tutorial and keeps members focused on their workout.'
},
{
question: 'Can I track which machines are most popular using QR codes?',
answer: 'Yes! By using a unique dynamic QR code for each machine, you can track scan frequency in your dashboard. High scan rates usually indicate high usage, helping you decide which equipment to buy more of in the future.'
},
{
question: 'Do I need separate QR codes for iOS and Android users to download my gym app?',
answer: 'No. You can create a single dynamic App Store QR code that detects the user\\'s device and automatically routes iPhones to the Apple App Store and Androids to the Google Play Store.'
},
{
question: 'How do QR codes help with 24/7 contactless gym signups?',
answer: 'You can place a large QR code on your front window. When scanned, it routes prospects to a mobile signup form and waiver. Once completed, your system can automatically issue a temporary digital access pass to their phone.'
},
{
question: 'What happens if we replace a machine with a newer model?',
answer: 'If you use dynamic QR codes, you don\\'t need to change the physical sticker if you transfer it. Alternatively, if you apply a new sticker, you can just redirect the old QR code URL to the new machine\\'s tutorial video.'
}
],`;
const newRealEstateFaq = ` faq: [
{
question: 'How can I reuse expensive real estate yard signs for multiple listings?',
answer: 'By printing a dynamic QR code directly onto your metal or plastic yard sign. When a house sells, you move the sign to a new property and simply log into your dashboard to change the QR code\\'s destination URL to the new listing.'
},
{
question: 'How large should a QR code be on a real estate sign for easy scanning from a car?',
answer: 'To be scannable from a vehicle parked at the curb (about 10-15 feet away), the QR code should be at least 4x4 inches (10x10 cm). Ensure there is high contrast, like a black code on a white background.'
},
{
question: 'What is the best destination for an open house QR code?',
answer: 'Instead of a static PDF flyer, link to a mobile-optimized digital property brochure or a lead-capture form. This allows you to collect the contact information of interested buyers instantly before they leave the property.'
},
{
question: 'Can a QR code automatically save my agent contact details to a buyer\\'s phone?',
answer: 'Yes. You can create a vCard QR code. When scanned, it prompts the buyer\\'s smartphone to instantly add your name, phone number, email, and photo directly to their contacts app.'
},
{
question: 'Will real estate QR codes work if there is no cell service at the property?',
answer: 'A standard URL QR code requires an internet connection to load the property page. If you are selling rural properties with no cell service, consider using an offline PDF QR code or a vCard QR code, which encode the data directly into the matrix.'
},
{
question: 'Can I track which neighborhoods generate the most interest?',
answer: 'Absolutely. If you use different dynamic QR codes for signs in different neighborhoods, your analytics dashboard will show you exactly which signs are getting scanned the most, helping you focus your marketing efforts.'
}
],`;
const newEventsFaq = ` faq: [
{
question: 'How do dynamic QR codes prevent disaster if our event schedule changes?',
answer: 'If a speaker cancels or a room changes after you\\'ve printed 5,000 attendee badges, a dynamic QR code saves the day. You simply update the digital schedule URL in your dashboard, and everyone\\'s printed badge instantly points to the new information.'
},
{
question: 'How do I generate unique QR codes for thousands of attendee badges?',
answer: 'You can use a Bulk QR Code Generator. Simply upload a CSV file with your attendees\\' names and unique URLs (like their individual networking profiles or ticket IDs), and the system will generate a batch of unique QR codes for your badge printer.'
},
{
question: 'Can sponsors use QR codes to capture leads at their booths?',
answer: 'Yes, this is highly effective. Sponsors can display a QR code linking to a digital brochure or a lead capture form. When attendees scan it, their interest is registered, and sponsors can follow up directly after the conference.'
},
{
question: 'What is the most reliable way to display a digital event map?',
answer: 'Instead of forcing users to download a bulky event app, link your QR code to a mobile-friendly, interactive web map. Ensure the map images are optimized for fast loading on crowded conference Wi-Fi networks.'
},
{
question: 'How can we increase session feedback rates using QR codes?',
answer: 'Place a large QR code on the exit doors or on the final presentation slide. Linking directly to a short, 3-question survey while the session is still fresh in their minds significantly boosts response rates compared to post-event emails.'
}
],`;
const gymsPage = `
'qr-codes-for-gyms': {
...bySlug('qr-codes-for-gyms'),
eyebrow: 'Gyms & Fitness',
titleSuffix: 'for Equipment Tutorials, Schedules, and Trials',
metaDescription:
'Use gym QR codes to route members to equipment video tutorials, class schedules, and free trial signups without replacing stickers.',
intro:
'Gym QR codes work best when heavy-duty stickers on equipment stay permanent, while the tutorial videos or schedules they link to can be updated.',
answer:
'A gym QR code should point members to an instructional video for the machine they are using, or direct prospects to a mobile signup flow.',
whenToUse: [
'You want to provide video instructions on how to use complex machines.',
'You have class schedules that change weekly or seasonally.',
'You need a contactless way for walk-ins to sign up for a free trial.',
],
comparisonItems: [
{ label: 'Sticker durability', text: 'Permanent application', value: true },
{ label: 'Video updates', text: 'Change links anytime', value: true },
{ label: 'Member engagement', text: 'Track usage stats', value: true },
],
howToSteps: [
'Create one dynamic QR code for each piece of equipment or studio door.',
'Print on durable, sweat-resistant vinyl stickers and apply to machines.',
'Update the linked videos or schedules in your dashboard as needed.',
],
workflowTitle: 'What a good gym QR setup should handle',
workflowIntro:
'Stickers on gym equipment are hard to remove. The QR workflow should make your physical labels permanent while the digital content evolves.',
workflowCards: [
{
title: 'Equipment tutorials',
description: 'Link QR codes on weight machines to short YouTube or TikTok videos demonstrating proper form.',
},
{
title: 'Class schedules',
description: 'Place a QR code on the studio door that links to the live, up-to-date weekly class schedule.',
},
{
title: 'Contactless signup',
description: 'Put a QR code on the front door or window for after-hours visitors to claim a guest pass digitally.',
},
],
checklistTitle: 'Gym QR checklist',
checklist: [
'Print QR codes on high-quality, sweat-resistant vinyl so they survive cleaning sprays.',
'Keep the URL destination dynamic so you never have to scrape off old stickers.',
"Add small text next to the code, e.g., 'Scan to see how to use'.",
'Ensure your gym has decent WiFi coverage for members to stream the videos.',
],
supportLinks: [
{
href: '/dynamic-qr-code-generator',
title: 'Commercial parent: Dynamic QR Code Generator',
description: 'Best fit for keeping equipment stickers permanent while updating video links.',
},
{
href: '/qr-code-tracking',
title: 'Sibling page: QR Code Tracking',
description: 'Useful to see which machines get the most scans and instructional video views.',
},
{
href: '/use-cases/feedback-qr-codes',
title: 'Feedback QR Codes',
description: 'Learn how to place QR codes in locker rooms for facility feedback.',
},
],
${newGymFaq}
heroImage: '/marketing/use-cases/qr-codes-for-gyms.png',
heroImageAlt: 'QR code sticker on a gym weight machine being scanned for a video tutorial',
},`;
const gymsUseCase = ` {
slug: 'qr-codes-for-gyms',
href: '/use-cases/qr-codes-for-gyms',
title: 'QR Codes for Gyms',
cluster: 'gyms',
summary: 'Link workout equipment to tutorial videos, schedules, or trials without reprinting stickers on every machine.',
parentHref: '/dynamic-qr-code-generator',
parentTitle: 'Dynamic QR Code Generator',
ctaLabel: 'Create your gym QR code',
},
`;
// Helper to replace faq arrays using regex because exact string match is flaky
function replaceFaqBlock(text, keyName, newFaqStr) {
const sectionRegex = new RegExp("'" + keyName + "': \\{[\\\\s\\\\S]*?heroImage:", "g");
return text.replace(sectionRegex, (match) => {
// Find the start of faq: [
const faqRegex = /faq:\s*\[[\s\S]*?\],\s*heroImage:/;
return match.replace(faqRegex, newFaqStr + "\n heroImage:");
});
}
// 1. Add qr-codes-for-gyms if not exists
if (!content.includes("'qr-codes-for-gyms': {")) {
// Inject to useCasePageContent
const useCasePagesStart = "export const useCasePageContent: Record<string, UseCasePageContent> = {";
const startIdx = content.indexOf(useCasePagesStart);
if (startIdx !== -1) {
const insertIdx = startIdx + useCasePagesStart.length;
content = content.substring(0, insertIdx) + gymsPage + content.substring(insertIdx);
}
}
if (!content.includes("slug: 'qr-codes-for-gyms'")) {
const lastCouponIdx = content.lastIndexOf("slug: 'coupon-qr-codes',");
const insertIdx = content.lastIndexOf(" {", lastCouponIdx);
content = content.substring(0, insertIdx) + gymsUseCase + content.substring(insertIdx);
}
// 2. Replace FAQs for real estate and events
content = replaceFaqBlock(content, 'event-qr-codes', newEventsFaq);
content = replaceFaqBlock(content, 'real-estate-sign-qr-codes', newRealEstateFaq);
fs.writeFileSync(file, content);
console.log("Successfully rewrote FAQs and added gyms if needed");