From 671c1a1559685ae3cdba1c73159ee3345db8073f Mon Sep 17 00:00:00 2001 From: knuthtimo-lab Date: Fri, 10 Jul 2026 21:52:36 +0200 Subject: [PATCH] SEO V2 --- next.config.mjs | 118 +- .../(main)/(marketing)/MarketingLayout.tsx | 169 +- src/app/(main)/(marketing)/pricing/page.tsx | 61 +- .../(marketing)/use-cases/[slug]/page.tsx | 2 + src/app/(main)/(marketing)/use-cases/page.tsx | 124 +- .../(marketing-de)/barcode-generator/page.tsx | 452 - src/app/(marketing-de)/layout.tsx | 66 +- .../(marketing-de)/qr-code-erstellen/page.tsx | 29 +- src/app/de/[slug]/page.tsx | 110 + src/app/de/layout.tsx | 79 + src/app/de/preise/page.tsx | 85 + src/app/sitemap.ts | 67 +- src/components/marketing/FreeToolsGrid.tsx | 5 +- src/components/marketing/HomePageClient.tsx | 120 +- .../marketing}/MarketingDeLayout.tsx | 49 +- .../marketing/UseCasePageTemplate.tsx | 66 +- src/components/ui/Footer.tsx | 4 +- src/lib/blog-data.ts | 8144 ++++++++--------- src/lib/growth-pages-de.ts | 1308 +++ src/lib/growth-pages.ts | 265 +- src/lib/i18n/index.ts | 36 + src/lib/indexnow.ts | 56 +- src/middleware.ts | 125 +- tmp-add-all-usecases.js | 49 - tmp-cleanup.js | 20 - tmp-homepage.js | 50 - tmp-nav.js | 101 - tmp-rewrite.js | 197 - 28 files changed, 6396 insertions(+), 5561 deletions(-) delete mode 100644 src/app/(marketing-de)/barcode-generator/page.tsx create mode 100644 src/app/de/[slug]/page.tsx create mode 100644 src/app/de/layout.tsx create mode 100644 src/app/de/preise/page.tsx rename src/{app/(marketing-de) => components/marketing}/MarketingDeLayout.tsx (92%) create mode 100644 src/lib/growth-pages-de.ts create mode 100644 src/lib/i18n/index.ts delete mode 100644 tmp-add-all-usecases.js delete mode 100644 tmp-cleanup.js delete mode 100644 tmp-homepage.js delete mode 100644 tmp-nav.js delete mode 100644 tmp-rewrite.js diff --git a/next.config.mjs b/next.config.mjs index 02bf2c7..4eff15a 100644 --- a/next.config.mjs +++ b/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', diff --git a/src/app/(main)/(marketing)/MarketingLayout.tsx b/src/app/(main)/(marketing)/MarketingLayout.tsx index 33ae362..8d2ed34 100644 --- a/src/app/(main)/(marketing)/MarketingLayout.tsx +++ b/src/app/(main)/(marketing)/MarketingLayout.tsx @@ -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({ - {/* Use Cases Dropdown */} -
setUseCasesOpen(true)} - onMouseLeave={() => setUseCasesOpen(false)} - > - - - - {useCasesOpen && ( - -
- {allUseCases.map((uc) => ( - -
- -
-
-
{uc.title}
-
- - ))} -
-
- View All Use Cases → -
-
- )} -
-
- + {/* Use Cases Dropdown */} +
setUseCasesOpen(true)} + onMouseLeave={() => setUseCasesOpen(false)} + > + + + + {useCasesOpen && ( + +
+ {publishedUseCases.map((uc) => ( + +
+ +
+
+
{uc.title}
+
+ + ))} +
+
+ View All Use Cases → +
+
+ )} +
+
+ {/* Tools Dropdown */}
- {/* Use Cases Accordion */} - - - - {mobileUseCasesOpen && ( - -
- {allUseCases.map((uc) => ( - { setMobileMenuOpen(false); setMobileUseCasesOpen(false); }} - > - {uc.title} - - ))} -
-
- )} -
- + {/* Use Cases Accordion */} + + + + {mobileUseCasesOpen && ( + +
+ {publishedUseCases.map((uc) => ( + { setMobileMenuOpen(false); setMobileUseCasesOpen(false); }} + > + {uc.title} + + ))} +
+
+ )} +
+ {/* Free Tools Accordion */}
-
- -
- -
-
-

- Not sure whether free is enough? -

-

- 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. -

- - Read the free vs paid QR generator comparison - -
-
- -
+
+ +
+ +
+
+

+ Not sure whether free is enough? +

+

+ 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. +

+ + Read the free vs paid QR generator comparison + +
+
+ +

Compare our plans

Free includes 3 active dynamic QR codes and unlimited static QR codes. Pro diff --git a/src/app/(main)/(marketing)/use-cases/[slug]/page.tsx b/src/app/(main)/(marketing)/use-cases/[slug]/page.tsx index c79e73f..c6b8cb2 100644 --- a/src/app/(main)/(marketing)/use-cases/[slug]/page.tsx +++ b/src/app/(main)/(marketing)/use-cases/[slug]/page.tsx @@ -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, }); } diff --git a/src/app/(main)/(marketing)/use-cases/page.tsx b/src/app/(main)/(marketing)/use-cases/page.tsx index b0a84e7..52125ee 100644 --- a/src/app/(main)/(marketing)/use-cases/page.tsx +++ b/src/app/(main)/(marketing)/use-cases/page.tsx @@ -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() {

- {allUseCases.map((page) => ( + {publishedUseCases.map((page) => (
@@ -205,66 +205,66 @@ export default function UseCasesHubPage() { ))}
- - -
-
-
-
- Industry pages with early traction -
-

- Service business QR workflows -

-

- These industry routes connect practical use cases with the QR - tools local businesses usually need first. -

-
- -
- {[ - { - 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) => ( - -

- {item.title} -

-

- {item.description} -

-
- Open page -
- - ))} -
-
-
- -
-
+
+ +
+
+
+
+ Industry pages with early traction +
+

+ Service business QR workflows +

+

+ These industry routes connect practical use cases with the QR + tools local businesses usually need first. +

+
+ +
+ {[ + { + 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) => ( + +

+ {item.title} +

+

+ {item.description} +

+
+ Open page +
+ + ))} +
+
+
+ +
+
diff --git a/src/app/(marketing-de)/barcode-generator/page.tsx b/src/app/(marketing-de)/barcode-generator/page.tsx deleted file mode 100644 index 39ab0d2..0000000 --- a/src/app/(marketing-de)/barcode-generator/page.tsx +++ /dev/null @@ -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 ( - <> -