SEO V2
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user