SEO V2
This commit is contained in:
118
next.config.mjs
118
next.config.mjs
@@ -34,66 +34,68 @@ const nextConfig = {
|
||||
pagesBufferLength: 2,
|
||||
},
|
||||
poweredByHeader: false,
|
||||
webpack: (config, { dev }) => {
|
||||
if (!dev && isWslOnWindowsMount()) {
|
||||
config.cache = {
|
||||
type: 'filesystem',
|
||||
cacheDirectory: path.join(os.tmpdir(), 'qrmaster-next-webpack-cache'),
|
||||
webpack: (config, { dev }) => {
|
||||
if (!dev && isWslOnWindowsMount()) {
|
||||
config.cache = {
|
||||
type: 'filesystem',
|
||||
cacheDirectory: path.join(os.tmpdir(), 'qrmaster-next-webpack-cache'),
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
async headers() {
|
||||
const longCacheHeaders = [
|
||||
{
|
||||
key: 'Cache-Control',
|
||||
value: 'public, max-age=31536000, immutable',
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
{ source: '/favicon1.png', headers: longCacheHeaders },
|
||||
{ source: '/favicon.svg', headers: longCacheHeaders },
|
||||
{ source: '/favicon.ico', headers: longCacheHeaders },
|
||||
{ source: '/logo.svg', headers: longCacheHeaders },
|
||||
{ source: '/producthunt-featured.svg', headers: longCacheHeaders },
|
||||
];
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
// SEO Cannibalization Redirects
|
||||
{
|
||||
source: '/tools/teams-qr-code',
|
||||
destination: '/blog/microsoft-teams-qr-code',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/dynamic-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: '/use-cases/restaurant-menu-qr-codes',
|
||||
destination: '/restaurants',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/blog/qr-code-restaurant-menu',
|
||||
destination: '/restaurants',
|
||||
permanent: true,
|
||||
},
|
||||
|
||||
|
||||
return config;
|
||||
},
|
||||
async headers() {
|
||||
const longCacheHeaders = [
|
||||
{
|
||||
key: 'Cache-Control',
|
||||
value: 'public, max-age=31536000, immutable',
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
{ source: '/favicon1.png', headers: longCacheHeaders },
|
||||
{ source: '/favicon.svg', headers: longCacheHeaders },
|
||||
{ source: '/favicon.ico', headers: longCacheHeaders },
|
||||
{ source: '/logo.svg', headers: longCacheHeaders },
|
||||
{ source: '/producthunt-featured.svg', headers: longCacheHeaders },
|
||||
];
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
// German home lives at /qr-code-erstellen (established ranking page);
|
||||
// /de only hosts the German subpages.
|
||||
{
|
||||
source: '/de',
|
||||
destination: '/qr-code-erstellen',
|
||||
permanent: true,
|
||||
},
|
||||
// SEO Cannibalization Redirects
|
||||
{
|
||||
source: '/dynamic-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: '/use-cases/restaurant-menu-qr-codes',
|
||||
destination: '/restaurants',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/blog/qr-code-restaurant-menu',
|
||||
destination: '/restaurants',
|
||||
permanent: true,
|
||||
},
|
||||
|
||||
{
|
||||
source: '/create-qr',
|
||||
destination: '/dynamic-qr-code-generator',
|
||||
|
||||
@@ -5,10 +5,9 @@ import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Footer } from '@/components/ui/Footer';
|
||||
import en from '@/i18n/en.json';
|
||||
import { allUseCases } from '@/lib/growth-pages';
|
||||
import { LayoutGrid } 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 en from '@/i18n/en.json';
|
||||
import { publishedUseCases } from '@/lib/growth-pages';
|
||||
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 { cn } from '@/lib/utils';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
@@ -20,10 +19,10 @@ export default function MarketingLayout({
|
||||
}) {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [toolsOpen, setToolsOpen] = useState(false);
|
||||
const [toolsOpen, setToolsOpen] = useState(false);
|
||||
const [useCasesOpen, setUseCasesOpen] = useState(false);
|
||||
const [resourcesOpen, setResourcesOpen] = useState(false);
|
||||
const [mobileToolsOpen, setMobileToolsOpen] = useState(false);
|
||||
const [mobileToolsOpen, setMobileToolsOpen] = useState(false);
|
||||
const [mobileUseCasesOpen, setMobileUseCasesOpen] = useState(false);
|
||||
const [mobileResourcesOpen, setMobileResourcesOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
@@ -40,10 +39,10 @@ export default function MarketingLayout({
|
||||
// Close simple menus when path changes
|
||||
useEffect(() => {
|
||||
setMobileMenuOpen(false);
|
||||
setToolsOpen(false);
|
||||
setToolsOpen(false);
|
||||
setUseCasesOpen(false);
|
||||
setResourcesOpen(false);
|
||||
setMobileToolsOpen(false);
|
||||
setMobileToolsOpen(false);
|
||||
setMobileUseCasesOpen(false);
|
||||
setMobileResourcesOpen(false);
|
||||
}, [pathname]);
|
||||
@@ -151,50 +150,50 @@ export default function MarketingLayout({
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* 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 →</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
{publishedUseCases.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 →</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* Tools Dropdown */}
|
||||
<div
|
||||
className="relative group px-3 py-2"
|
||||
@@ -329,39 +328,39 @@ export default function MarketingLayout({
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* 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>
|
||||
|
||||
{/* 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">
|
||||
{publishedUseCases.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>
|
||||
|
||||
{/* Free Tools Accordion */}
|
||||
<button
|
||||
onClick={() => setMobileToolsOpen(!mobileToolsOpen)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import PricingClient from './PricingClient';
|
||||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import PricingClient from './PricingClient';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
|
||||
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.',
|
||||
alternates: {
|
||||
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: {
|
||||
index: true,
|
||||
@@ -98,30 +103,30 @@ export default function PricingPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto max-w-5xl px-4 pb-4 sm:px-6 lg:px-8">
|
||||
<FAQSection items={faqItems} title="Pricing questions" />
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<h2 className="text-xl font-semibold text-slate-900">
|
||||
Not sure whether free is enough?
|
||||
</h2>
|
||||
<p className="mt-3 max-w-3xl leading-7 text-slate-600">
|
||||
Compare free QR generators with paid dynamic QR workflows before
|
||||
choosing a plan. The difference usually comes down to editing
|
||||
after print, scan tracking, branding, and reprint risk.
|
||||
</p>
|
||||
<Link
|
||||
href="/compare/free-vs-paid-qr-code-generator"
|
||||
className="mt-4 inline-flex text-sm font-semibold text-blue-700 hover:text-blue-800"
|
||||
>
|
||||
Read the free vs paid QR generator comparison
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="sr-only">
|
||||
<div className="container mx-auto max-w-5xl px-4 pb-4 sm:px-6 lg:px-8">
|
||||
<FAQSection items={faqItems} title="Pricing questions" />
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<h2 className="text-xl font-semibold text-slate-900">
|
||||
Not sure whether free is enough?
|
||||
</h2>
|
||||
<p className="mt-3 max-w-3xl leading-7 text-slate-600">
|
||||
Compare free QR generators with paid dynamic QR workflows before
|
||||
choosing a plan. The difference usually comes down to editing
|
||||
after print, scan tracking, branding, and reprint risk.
|
||||
</p>
|
||||
<Link
|
||||
href="/compare/free-vs-paid-qr-code-generator"
|
||||
className="mt-4 inline-flex text-sm font-semibold text-blue-700 hover:text-blue-800"
|
||||
>
|
||||
Read the free vs paid QR generator comparison
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="sr-only">
|
||||
<h2>Compare our plans</h2>
|
||||
<p>
|
||||
Free includes 3 active dynamic QR codes and unlimited static QR codes. Pro
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
UseCasePageTemplate,
|
||||
} from '@/components/marketing/UseCasePageTemplate';
|
||||
import { allUseCases, getUseCasePage } from '@/lib/growth-pages';
|
||||
import { getGermanTwinByEnSlug } from '@/lib/growth-pages-de';
|
||||
|
||||
export function generateStaticParams() {
|
||||
return allUseCases.map((item) => ({
|
||||
@@ -23,6 +24,7 @@ export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||
title: page.title,
|
||||
description: page.metaDescription,
|
||||
canonicalPath: page.href,
|
||||
dePath: getGermanTwinByEnSlug(page.slug)?.href,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ import {
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Card } from "@/components/ui/Card";
|
||||
import {
|
||||
allUseCases,
|
||||
commercialPages,
|
||||
featuredUseCases,
|
||||
publishedUseCases,
|
||||
supportResources,
|
||||
upcomingUseCaseIdeas,
|
||||
} from "@/lib/growth-pages";
|
||||
@@ -184,7 +184,7 @@ export default function UseCasesHubPage() {
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<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">
|
||||
@@ -205,66 +205,66 @@ export default function UseCasesHubPage() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-white py-16">
|
||||
<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="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
|
||||
Industry pages with early traction
|
||||
</div>
|
||||
<h2 className="mt-3 text-3xl font-bold text-slate-900">
|
||||
Service business QR workflows
|
||||
</h2>
|
||||
<p className="mt-4 text-lg leading-8 text-slate-600">
|
||||
These industry routes connect practical use cases with the QR
|
||||
tools local businesses usually need first.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
href: "/qr-code-for/barbershops",
|
||||
title: "QR Codes for Barbershops",
|
||||
description:
|
||||
"Booking links, review prompts, WiFi access, and social profiles for barbershop visits.",
|
||||
},
|
||||
{
|
||||
href: "/tools/wifi-qr-code",
|
||||
title: "WiFi QR Code Generator",
|
||||
description:
|
||||
"A practical guest-facing QR tool for service businesses and venues.",
|
||||
},
|
||||
{
|
||||
href: "/qr-code-erstellen",
|
||||
title: "QR Code Erstellen",
|
||||
description:
|
||||
"German QR creation page for local business owners who search in German.",
|
||||
},
|
||||
].map((item) => (
|
||||
<Link
|
||||
key={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"
|
||||
>
|
||||
<h3 className="text-xl font-bold text-slate-900">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-3 text-sm leading-6 text-slate-600">
|
||||
{item.description}
|
||||
</p>
|
||||
<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" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-slate-50 py-16">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
</section>
|
||||
|
||||
<section className="bg-white py-16">
|
||||
<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="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
|
||||
Industry pages with early traction
|
||||
</div>
|
||||
<h2 className="mt-3 text-3xl font-bold text-slate-900">
|
||||
Service business QR workflows
|
||||
</h2>
|
||||
<p className="mt-4 text-lg leading-8 text-slate-600">
|
||||
These industry routes connect practical use cases with the QR
|
||||
tools local businesses usually need first.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
href: "/qr-code-for/barbershops",
|
||||
title: "QR Codes for Barbershops",
|
||||
description:
|
||||
"Booking links, review prompts, WiFi access, and social profiles for barbershop visits.",
|
||||
},
|
||||
{
|
||||
href: "/tools/wifi-qr-code",
|
||||
title: "WiFi QR Code Generator",
|
||||
description:
|
||||
"A practical guest-facing QR tool for service businesses and venues.",
|
||||
},
|
||||
{
|
||||
href: "/qr-code-erstellen",
|
||||
title: "QR Code Erstellen",
|
||||
description:
|
||||
"German QR creation page for local business owners who search in German.",
|
||||
},
|
||||
].map((item) => (
|
||||
<Link
|
||||
key={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"
|
||||
>
|
||||
<h3 className="text-xl font-bold text-slate-900">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-3 text-sm leading-6 text-slate-600">
|
||||
{item.description}
|
||||
</p>
|
||||
<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" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-slate-50 py-16">
|
||||
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,0.95fr)]">
|
||||
<Card className="rounded-3xl border-slate-200 bg-white p-8 shadow-sm">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Suspense } from 'react';
|
||||
import '@/styles/globals.css';
|
||||
import { Providers } from '@/components/Providers';
|
||||
import MarketingDeLayout from './MarketingDeLayout';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||
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: {
|
||||
@@ -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.',
|
||||
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',
|
||||
},
|
||||
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',
|
||||
@@ -55,33 +55,33 @@ export const metadata: Metadata = {
|
||||
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function MarketingDeGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
};
|
||||
|
||||
export default function MarketingDeGroupLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="de">
|
||||
<body className="font-sans">
|
||||
<Suspense fallback={null}>
|
||||
<Providers>
|
||||
<FacebookPixel />
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
<body className="font-sans">
|
||||
<Suspense fallback={null}>
|
||||
<Providers>
|
||||
<FacebookPixel />
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
||||
/>
|
||||
<MarketingDeLayout>
|
||||
{children}
|
||||
</MarketingDeLayout>
|
||||
</Providers>
|
||||
</Suspense>
|
||||
</body>
|
||||
/>
|
||||
<MarketingDeLayout>
|
||||
{children}
|
||||
</MarketingDeLayout>
|
||||
</Providers>
|
||||
</Suspense>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import type { Metadata } from 'next';
|
||||
import { useCasePagesDe } from '@/lib/growth-pages-de';
|
||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||
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>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
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>
|
||||
</div>
|
||||
<div className="p-6 border rounded-lg">
|
||||
@@ -266,6 +268,31 @@ export default function QRCodeErstellenPage() {
|
||||
</p>
|
||||
</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 */}
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||
<AnswerFirstBlock
|
||||
|
||||
110
src/app/de/[slug]/page.tsx
Normal file
110
src/app/de/[slug]/page.tsx
Normal 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
79
src/app/de/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
85
src/app/de/preise/page.tsx
Normal file
85
src/app/de/preise/page.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
publishedComparisonPages,
|
||||
publishedGuidePages,
|
||||
} from '../lib/pseo-published-pages';
|
||||
import { publishedUseCases } from '../lib/growth-pages';
|
||||
import { useCasePagesDe } from '../lib/growth-pages-de';
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const baseUrl = 'https://www.qrmaster.net';
|
||||
@@ -32,6 +34,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
'paypal-qr-code',
|
||||
'zoom-qr-code',
|
||||
'teams-qr-code',
|
||||
'google-review-qr-code',
|
||||
'barcode-generator',
|
||||
];
|
||||
|
||||
@@ -58,6 +61,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
|
||||
const highPriorityTools: Record<string, number> = {
|
||||
'barcode-generator': 0.95,
|
||||
'google-review-qr-code': 0.9,
|
||||
};
|
||||
|
||||
const toolLastModified: Record<string, Date> = {
|
||||
@@ -111,66 +115,25 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
changeFrequency: 'weekly' as const,
|
||||
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(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/use-cases/business-card-qr-codes`,
|
||||
...Object.values(useCasePagesDe).map((useCase) => ({
|
||||
url: `${baseUrl}${useCase.href}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/use-cases/event-qr-codes`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/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`,
|
||||
lastModified: new Date(),
|
||||
|
||||
@@ -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: 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: MapPin, name: 'Location', description: 'Share GPS coordinates', href: '/tools/location-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: 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/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' },
|
||||
];
|
||||
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
'use client';
|
||||
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Hero } from '@/components/marketing/Hero';
|
||||
import AIComingSoonBanner from '@/components/marketing/AIComingSoonBanner';
|
||||
import { StatsStrip } from '@/components/marketing/StatsStrip';
|
||||
import { TemplateCards } from '@/components/marketing/TemplateCards';
|
||||
import { InstantGenerator } from '@/components/marketing/InstantGenerator';
|
||||
import { StaticVsDynamic } from '@/components/marketing/StaticVsDynamic';
|
||||
import { Features } from '@/components/marketing/Features';
|
||||
import { Pricing } from '@/components/marketing/Pricing';
|
||||
import { FAQ } from '@/components/marketing/FAQ';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { ReprintCalculatorTeaser } from '@/components/marketing/ReprintCalculatorTeaser';
|
||||
import { ScrollToTop } from '@/components/ui/ScrollToTop';
|
||||
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||
import { TestimonialsCarousel } from '@/components/marketing/TestimonialsCarousel';
|
||||
import { testimonials } from '@/lib/testimonial-data';
|
||||
import AIComingSoonBanner from '@/components/marketing/AIComingSoonBanner';
|
||||
import { StatsStrip } from '@/components/marketing/StatsStrip';
|
||||
import { TemplateCards } from '@/components/marketing/TemplateCards';
|
||||
import { InstantGenerator } from '@/components/marketing/InstantGenerator';
|
||||
import { StaticVsDynamic } from '@/components/marketing/StaticVsDynamic';
|
||||
import { Features } from '@/components/marketing/Features';
|
||||
import { Pricing } from '@/components/marketing/Pricing';
|
||||
import { FAQ } from '@/components/marketing/FAQ';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { ReprintCalculatorTeaser } from '@/components/marketing/ReprintCalculatorTeaser';
|
||||
import { ScrollToTop } from '@/components/ui/ScrollToTop';
|
||||
import { FreeToolsGrid } from '@/components/marketing/FreeToolsGrid';
|
||||
import { TestimonialsCarousel } from '@/components/marketing/TestimonialsCarousel';
|
||||
import { testimonials } from '@/lib/testimonial-data';
|
||||
import en from '@/i18n/en.json';
|
||||
import { allUseCases } from '@/lib/growth-pages';
|
||||
|
||||
export default function HomePageClient() {
|
||||
// Always use English for marketing pages
|
||||
const t = en;
|
||||
|
||||
const industries = [
|
||||
'Restaurant Chain',
|
||||
'Tech Startup',
|
||||
'Real Estate',
|
||||
'Event Agency',
|
||||
'Retail Store',
|
||||
'Healthcare',
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Hero t={t} />
|
||||
|
||||
{/* Main Interaction: Generator */}
|
||||
<InstantGenerator t={t} />
|
||||
|
||||
<AIComingSoonBanner />
|
||||
|
||||
import { publishedUseCases } from '@/lib/growth-pages';
|
||||
|
||||
export default function HomePageClient() {
|
||||
// Always use English for marketing pages
|
||||
const t = en;
|
||||
|
||||
const industries = [
|
||||
'Restaurant Chain',
|
||||
'Tech Startup',
|
||||
'Real Estate',
|
||||
'Event Agency',
|
||||
'Retail Store',
|
||||
'Healthcare',
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Hero t={t} />
|
||||
|
||||
{/* Main Interaction: Generator */}
|
||||
<InstantGenerator t={t} />
|
||||
|
||||
<AIComingSoonBanner />
|
||||
|
||||
{/* Free Tools Grid */}
|
||||
<FreeToolsGrid />
|
||||
|
||||
@@ -133,7 +133,7 @@ export default function HomePageClient() {
|
||||
</p>
|
||||
</div>
|
||||
<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
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
@@ -157,22 +157,22 @@ export default function HomePageClient() {
|
||||
</section>
|
||||
|
||||
{/* Testimonials Carousel */}
|
||||
<TestimonialsCarousel testimonials={testimonials} />
|
||||
|
||||
<React.Fragment>
|
||||
<StaticVsDynamic t={t} />
|
||||
<ReprintCalculatorTeaser />
|
||||
<Features t={t} />
|
||||
</React.Fragment>
|
||||
|
||||
{/* Pricing Section */}
|
||||
<Pricing t={t} />
|
||||
|
||||
{/* FAQ Section */}
|
||||
<FAQ t={t} />
|
||||
|
||||
{/* Scroll to Top Button */}
|
||||
<ScrollToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
<TestimonialsCarousel testimonials={testimonials} />
|
||||
|
||||
<React.Fragment>
|
||||
<StaticVsDynamic t={t} />
|
||||
<ReprintCalculatorTeaser />
|
||||
<Features t={t} />
|
||||
</React.Fragment>
|
||||
|
||||
{/* Pricing Section */}
|
||||
<Pricing t={t} />
|
||||
|
||||
{/* FAQ Section */}
|
||||
<FAQ t={t} />
|
||||
|
||||
{/* Scroll to Top Button */}
|
||||
<ScrollToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import { usePathname } from 'next/navigation';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Footer } from '@/components/ui/Footer';
|
||||
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 { cn } from '@/lib/utils';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
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 { AnimatePresence, motion } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function MarketingLayout({
|
||||
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: '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: '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' },
|
||||
];
|
||||
|
||||
@@ -72,8 +73,11 @@ export default function MarketingLayout({
|
||||
<div className="sr-only" aria-hidden="false">
|
||||
<nav aria-label="Seitenübersicht">
|
||||
<ul>
|
||||
<li><a href="/">Startseite</a></li>
|
||||
<li><a href="/pricing">Preise</a></li>
|
||||
<li><a href="/qr-code-erstellen">Startseite</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="/faq">FAQ</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/zoom-qr-code">Zoom 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>
|
||||
</ul>
|
||||
</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">
|
||||
{/* Logo */}
|
||||
<Link href="/" 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">
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="QR Master"
|
||||
fill
|
||||
sizes="64px"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xl font-bold text-slate-900 tracking-tight group-hover:text-indigo-600 transition-colors">QR Master</span>
|
||||
</Link>
|
||||
{/* Logo */}
|
||||
<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">
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
alt="QR Master"
|
||||
fill
|
||||
sizes="64px"
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xl font-bold text-slate-900 tracking-tight group-hover:text-indigo-600 transition-colors">QR Master</span>
|
||||
</Link>
|
||||
|
||||
|
||||
{/* 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">
|
||||
{t.nav.features}
|
||||
</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}
|
||||
</Link>
|
||||
<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>
|
||||
|
||||
<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="/#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>
|
||||
|
||||
@@ -38,6 +38,40 @@ type LinkCard = {
|
||||
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 = {
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -78,16 +112,19 @@ type UseCasePageTemplateProps = {
|
||||
authoritySignals?: string[];
|
||||
directAnswer?: string;
|
||||
schemaData?: Record<string, unknown>[];
|
||||
labels?: UseCaseTemplateLabels;
|
||||
};
|
||||
|
||||
export function buildUseCaseMetadata({
|
||||
title,
|
||||
description,
|
||||
canonicalPath,
|
||||
dePath,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
canonicalPath: string;
|
||||
dePath?: string;
|
||||
}): Metadata {
|
||||
const canonical = `https://www.qrmaster.net${canonicalPath}`;
|
||||
|
||||
@@ -101,6 +138,7 @@ export function buildUseCaseMetadata({
|
||||
languages: {
|
||||
"x-default": canonical,
|
||||
en: canonical,
|
||||
...(dePath ? { de: `https://www.qrmaster.net${dePath}` } : {}),
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
@@ -395,6 +433,7 @@ export function UseCasePageTemplate({
|
||||
authoritySignals = [],
|
||||
directAnswer,
|
||||
schemaData = [],
|
||||
labels = defaultLabels,
|
||||
}: UseCasePageTemplateProps) {
|
||||
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="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">
|
||||
<span>Use Case</span>
|
||||
<span>{labels.badge}</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
@@ -433,12 +472,7 @@ export function UseCasePageTemplate({
|
||||
|
||||
{/* Sub-benefits (Styled as authentic, clean Framer-style Apple Notification banners) */}
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{[
|
||||
{ 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) => (
|
||||
{labels.subBenefits.map((benefit, idx) => ({ ...benefit, icon: subBenefitIcons[idx % subBenefitIcons.length] })).map((item) => (
|
||||
<div
|
||||
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]"
|
||||
@@ -449,7 +483,7 @@ export function UseCasePageTemplate({
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-baseline justify-between">
|
||||
<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>
|
||||
<p className="text-[12px] text-slate-500 leading-snug mt-0.5">{item.body}</p>
|
||||
</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-amber-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>
|
||||
{/* Visual Container */}
|
||||
<div className="relative aspect-[4/3] overflow-hidden rounded-xl bg-slate-50/50">
|
||||
@@ -536,8 +570,8 @@ export function UseCasePageTemplate({
|
||||
whatIsIt={answer}
|
||||
whenToUse={whenToUse}
|
||||
comparison={{
|
||||
leftTitle: "Static QR",
|
||||
rightTitle: "Dynamic QR",
|
||||
leftTitle: labels.comparisonLeftTitle,
|
||||
rightTitle: labels.comparisonRightTitle,
|
||||
items: comparisonItems,
|
||||
}}
|
||||
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>
|
||||
<div className="text-xs font-semibold uppercase tracking-wider text-slate-450">
|
||||
Standard Guidelines
|
||||
{labels.guidelinesEyebrow}
|
||||
</div>
|
||||
<h2 className="mt-2 text-2xl font-bold text-slate-900 sm:text-3xl -tracking-[0.02em]">
|
||||
{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">
|
||||
<Link2 className="h-5 w-5" />
|
||||
</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 className="mt-6 space-y-4">
|
||||
@@ -676,7 +710,7 @@ export function UseCasePageTemplate({
|
||||
|
||||
{/* FAQs */}
|
||||
<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>
|
||||
|
||||
{/* 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="max-w-2xl space-y-3">
|
||||
<div className="text-xs font-semibold uppercase tracking-wider text-slate-450">
|
||||
Next step
|
||||
{labels.nextStepEyebrow}
|
||||
</div>
|
||||
<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>
|
||||
<p className="text-base leading-relaxed text-slate-500">
|
||||
{description}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Link from 'next/link';
|
||||
import en from '@/i18n/en.json';
|
||||
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';
|
||||
|
||||
interface FooterProps {
|
||||
@@ -93,7 +93,7 @@ export function Footer({ variant = 'marketing', t }: FooterProps) {
|
||||
<div>
|
||||
<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'}`}>
|
||||
{allUseCases.map((uc) => (
|
||||
{publishedUseCases.map((uc) => (
|
||||
<li key={uc.slug}>
|
||||
<Link href={uc.href} className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>
|
||||
{uc.title}
|
||||
|
||||
8144
src/lib/blog-data.ts
8144
src/lib/blog-data.ts
File diff suppressed because it is too large
Load Diff
1308
src/lib/growth-pages-de.ts
Normal file
1308
src/lib/growth-pages-de.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -220,16 +220,16 @@ export const allUseCases: UseCaseLink[] = [
|
||||
parentTitle: 'Dynamic QR Code Generator',
|
||||
ctaLabel: 'Create your payment QR code',
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
{
|
||||
slug: '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',
|
||||
'flyer-qr-codes',
|
||||
'packaging-qr-codes',
|
||||
@@ -307,101 +314,101 @@ function bySlug(slug: string): UseCaseLink {
|
||||
return match;
|
||||
}
|
||||
|
||||
export const useCasePageContent: Record<string, UseCasePageContent> = {
|
||||
'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.',
|
||||
},
|
||||
],
|
||||
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.'
|
||||
}
|
||||
],
|
||||
heroImage: '/marketing/use-cases/qr-codes-for-gyms.png',
|
||||
heroImageAlt: 'QR code sticker on a gym weight machine being scanned for a video tutorial',
|
||||
export const useCasePageContent: Record<string, UseCasePageContent> = {
|
||||
'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.',
|
||||
},
|
||||
],
|
||||
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.'
|
||||
}
|
||||
],
|
||||
heroImage: '/marketing/use-cases/qr-codes-for-gyms.png',
|
||||
heroImageAlt: 'QR code sticker on a gym weight machine being scanned for a video tutorial',
|
||||
},
|
||||
'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',
|
||||
title: 'Sibling page: Flyer QR Codes',
|
||||
description:
|
||||
'Useful when event promotion depends on measurable printed distribution before event day.',
|
||||
},
|
||||
],
|
||||
faq: [
|
||||
{
|
||||
description:
|
||||
'Useful when event promotion depends on measurable printed distribution before event day.',
|
||||
},
|
||||
{
|
||||
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?',
|
||||
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:
|
||||
'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: [
|
||||
{
|
||||
@@ -1063,6 +1082,12 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
|
||||
description:
|
||||
'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: [
|
||||
{
|
||||
@@ -1260,8 +1285,8 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
|
||||
'Useful for direct payment-oriented workflows when PayPal is the intended post-scan action.',
|
||||
},
|
||||
{
|
||||
href: '/use-cases/restaurant-menu-qr-codes',
|
||||
title: 'Sibling page: Restaurant Menu QR Codes',
|
||||
href: '/restaurants',
|
||||
title: 'Restaurant QR Codes',
|
||||
description:
|
||||
'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',
|
||||
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: [
|
||||
{
|
||||
@@ -1526,6 +1556,11 @@ export const useCasePageContent: Record<string, UseCasePageContent> = {
|
||||
title: 'Dynamic vs Static QR Codes',
|
||||
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: [
|
||||
{
|
||||
|
||||
36
src/lib/i18n/index.ts
Normal file
36
src/lib/i18n/index.ts
Normal 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}`;
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import dotenv from 'dotenv';
|
||||
import { blogPosts } from '../lib/blog-data';
|
||||
import { pillarMeta } from '../lib/pillar-data';
|
||||
import { authors } from '../lib/author-data';
|
||||
import { publishedUseCases } from '../lib/growth-pages';
|
||||
import { useCasePagesDe } from '../lib/growth-pages-de';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -55,34 +57,44 @@ export function getAllIndexableUrls(): string[] {
|
||||
'wifi-qr-code', 'crypto-qr-code', 'event-qr-code', 'facebook-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',
|
||||
'zoom-qr-code', 'teams-qr-code',
|
||||
'zoom-qr-code', 'teams-qr-code', 'google-review-qr-code',
|
||||
].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
|
||||
const blogPages = blogPosts.map(post => `${baseUrl}/blog/${post.slug}`);
|
||||
|
||||
// Main pages (synced with sitemap.ts)
|
||||
const mainPages = [
|
||||
baseUrl,
|
||||
`${baseUrl}/about`,
|
||||
`${baseUrl}/contact`,
|
||||
`${baseUrl}/press`,
|
||||
`${baseUrl}/testimonials`,
|
||||
`${baseUrl}/qr-code-erstellen`,
|
||||
`${baseUrl}/qr-code-tracking`,
|
||||
`${baseUrl}/reprint-calculator`,
|
||||
`${baseUrl}/dynamic-qr-code-generator`,
|
||||
`${baseUrl}/bulk-qr-code-generator`,
|
||||
const mainPages = [
|
||||
baseUrl,
|
||||
`${baseUrl}/about`,
|
||||
`${baseUrl}/contact`,
|
||||
`${baseUrl}/press`,
|
||||
`${baseUrl}/testimonials`,
|
||||
`${baseUrl}/qr-code-erstellen`,
|
||||
`${baseUrl}/qr-code-tracking`,
|
||||
`${baseUrl}/reprint-calculator`,
|
||||
`${baseUrl}/dynamic-qr-code-generator`,
|
||||
`${baseUrl}/bulk-qr-code-generator`,
|
||||
`${baseUrl}/custom-qr-code-generator`,
|
||||
`${baseUrl}/manage-qr-codes`,
|
||||
`${baseUrl}/pricing`,
|
||||
`${baseUrl}/tools`,
|
||||
`${baseUrl}/features`,
|
||||
`${baseUrl}/faq`,
|
||||
`${baseUrl}/blog`,
|
||||
`${baseUrl}/privacy`,
|
||||
`${baseUrl}/newsletter`,
|
||||
];
|
||||
`${baseUrl}/manage-qr-codes`,
|
||||
`${baseUrl}/pricing`,
|
||||
`${baseUrl}/tools`,
|
||||
`${baseUrl}/features`,
|
||||
`${baseUrl}/faq`,
|
||||
`${baseUrl}/blog`,
|
||||
`${baseUrl}/privacy`,
|
||||
`${baseUrl}/newsletter`,
|
||||
];
|
||||
|
||||
// Learn hub and pillar pages
|
||||
const learnPages = [
|
||||
@@ -93,7 +105,7 @@ export function getAllIndexableUrls(): string[] {
|
||||
// Author pages
|
||||
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
|
||||
|
||||
@@ -40,7 +40,7 @@ function attachAttributionCookie(req: NextRequest, response: NextResponse) {
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const path = req.nextUrl.pathname;
|
||||
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
|
||||
const publicPaths = [
|
||||
'/',
|
||||
'/pricing',
|
||||
'/faq',
|
||||
'/blog',
|
||||
'/login',
|
||||
'/signup',
|
||||
'/privacy',
|
||||
'/',
|
||||
'/pricing',
|
||||
'/faq',
|
||||
'/blog',
|
||||
'/login',
|
||||
'/signup',
|
||||
'/privacy',
|
||||
'/newsletter',
|
||||
'/restaurants',
|
||||
'/tools',
|
||||
'/features',
|
||||
// '/guide', // Redirected to /learn/*
|
||||
'/qr-code-erstellen',
|
||||
'/dynamic-qr-code-generator',
|
||||
'/dynamic-barcode-generator',
|
||||
'/bulk-qr-code-generator',
|
||||
'/qr-code-tracking',
|
||||
'/qr-code-analytics',
|
||||
'/features',
|
||||
// '/guide', // Redirected to /learn/*
|
||||
'/de',
|
||||
'/qr-code-erstellen',
|
||||
'/dynamic-qr-code-generator',
|
||||
'/dynamic-barcode-generator',
|
||||
'/bulk-qr-code-generator',
|
||||
'/qr-code-tracking',
|
||||
'/qr-code-analytics',
|
||||
'/reprint-calculator',
|
||||
'/custom-qr-code-generator',
|
||||
'/manage-qr-codes',
|
||||
'/coupon',
|
||||
'/feedback',
|
||||
'/vcard',
|
||||
'/display',
|
||||
'/contact',
|
||||
'/about',
|
||||
'/learn',
|
||||
'/use-cases',
|
||||
'/authors',
|
||||
'/press',
|
||||
'/testimonials',
|
||||
'/qr-code-for-marketing-campaigns',
|
||||
'/qr-code-for',
|
||||
'/qr-code-print-size-guide',
|
||||
'/use-cases/flyer-qr-codes',
|
||||
'/use-cases/packaging-qr-codes',
|
||||
'/use-cases/real-estate-sign-qr-codes',
|
||||
'/use-cases/feedback-qr-codes',
|
||||
'/use-cases/payment-qr-codes',
|
||||
'/use-cases/coupon-qr-codes',
|
||||
'/alternatives',
|
||||
'/vs',
|
||||
];
|
||||
|
||||
// Check if path is public
|
||||
const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/'));
|
||||
|
||||
'/coupon',
|
||||
'/feedback',
|
||||
'/vcard',
|
||||
'/display',
|
||||
'/contact',
|
||||
'/about',
|
||||
'/learn',
|
||||
'/use-cases',
|
||||
'/authors',
|
||||
'/press',
|
||||
'/testimonials',
|
||||
'/qr-code-for-marketing-campaigns',
|
||||
'/qr-code-for',
|
||||
'/qr-code-print-size-guide',
|
||||
'/use-cases/flyer-qr-codes',
|
||||
'/use-cases/packaging-qr-codes',
|
||||
'/use-cases/real-estate-sign-qr-codes',
|
||||
'/use-cases/feedback-qr-codes',
|
||||
'/use-cases/payment-qr-codes',
|
||||
'/use-cases/coupon-qr-codes',
|
||||
'/alternatives',
|
||||
'/vs',
|
||||
];
|
||||
|
||||
// Check if path is public
|
||||
const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/'));
|
||||
|
||||
// Allow API routes
|
||||
if (path.startsWith('/api/')) {
|
||||
return attachAttributionCookie(req, NextResponse.next());
|
||||
}
|
||||
|
||||
// Allow redirect routes (QR code redirects)
|
||||
|
||||
// Allow redirect routes (QR code redirects)
|
||||
if (path.startsWith('/r/')) {
|
||||
return attachAttributionCookie(req, NextResponse.next());
|
||||
}
|
||||
|
||||
// Allow static files
|
||||
|
||||
// Allow static files
|
||||
if (path.includes('.') || path.startsWith('/_next')) {
|
||||
return attachAttributionCookie(req, NextResponse.next());
|
||||
}
|
||||
|
||||
|
||||
// Allow public paths
|
||||
if (isPublicPath) {
|
||||
return attachAttributionCookie(req, NextResponse.next());
|
||||
@@ -158,7 +159,7 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
// For protected routes, check for userId cookie
|
||||
const userId = req.cookies.get('userId')?.value;
|
||||
|
||||
|
||||
if (!userId) {
|
||||
// Not authenticated - redirect to signup
|
||||
const signupUrl = new URL('/signup', req.url);
|
||||
@@ -170,16 +171,16 @@ export function middleware(req: NextRequest) {
|
||||
// Authenticated - allow access
|
||||
return attachAttributionCookie(req, NextResponse.next());
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
* - public folder
|
||||
*/
|
||||
'/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)',
|
||||
],
|
||||
};
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
* - public folder
|
||||
*/
|
||||
'/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
101
tmp-nav.js
101
tmp-nav.js
@@ -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 →</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");
|
||||
197
tmp-rewrite.js
197
tmp-rewrite.js
@@ -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");
|
||||
Reference in New Issue
Block a user