From 254e6490b89c23fdfd95fb8a00ae84bee5f66bf9 Mon Sep 17 00:00:00 2001 From: Timo Knuth Date: Fri, 17 Oct 2025 13:45:33 +0200 Subject: [PATCH] SEO/AEO --- .claude/settings.local.json | 5 +- next-sitemap.config.js | 42 ++ next.config.mjs | 5 +- package-lock.json | 43 ++ package.json | 1 + public/robots.txt | 3 + public/sitemap.xml | 33 ++ public/static/.gitkeep | 2 + src/app/(app)/create/page.tsx | 26 +- src/app/(app)/layout.tsx | 37 +- src/app/(app)/pricing/page.tsx | 24 +- src/app/(app)/settings/page.tsx | 539 ++++-------------- src/app/(marketing)/blog/[slug]/page.tsx | 445 ++++++++++----- src/app/(marketing)/blog/page.tsx | 158 +++-- src/app/(marketing)/faq/page.tsx | 148 ++++- src/app/(marketing)/layout.tsx | 30 +- src/app/(marketing)/page.tsx | 114 ++-- src/app/(marketing)/pricing/page.tsx | 267 +++++++++ src/app/layout.tsx | 31 +- src/app/og/route.tsx | 67 +++ src/components/Breadcrumbs.tsx | 36 ++ src/components/SeoJsonLd.tsx | 23 + src/components/marketing/FAQ.tsx | 12 +- src/components/marketing/Features.tsx | 44 +- src/components/marketing/Hero.tsx | 26 +- src/components/marketing/HomePageClient.tsx | 58 ++ src/components/marketing/InstantGenerator.tsx | 38 +- src/components/marketing/Pricing.tsx | 32 +- src/components/marketing/StaticVsDynamic.tsx | 18 +- src/components/marketing/StatsStrip.tsx | 8 +- src/components/marketing/TemplateCards.tsx | 14 +- src/hooks/useTranslation.ts | 2 + src/i18n/de.json | 28 +- src/i18n/en.json | 30 +- src/lib/schema.ts | 206 +++++++ src/styles/globals.css | 34 +- 36 files changed, 1712 insertions(+), 917 deletions(-) create mode 100644 next-sitemap.config.js create mode 100644 public/robots.txt create mode 100644 public/sitemap.xml create mode 100644 public/static/.gitkeep create mode 100644 src/app/(marketing)/pricing/page.tsx create mode 100644 src/app/og/route.tsx create mode 100644 src/components/Breadcrumbs.tsx create mode 100644 src/components/SeoJsonLd.tsx create mode 100644 src/components/marketing/HomePageClient.tsx create mode 100644 src/lib/schema.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 31407ba..e88e3d8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -15,7 +15,10 @@ "Read(//c/Users/a931627/.ssh/**)", "Bash(ssh-keygen:*)", "Bash(cat:*)", - "Bash(git remote add:*)" + "Bash(git remote add:*)", + "Bash(git push:*)", + "Bash(git remote set-url:*)", + "Bash(npm install:*)" ], "deny": [], "ask": [] diff --git a/next-sitemap.config.js b/next-sitemap.config.js new file mode 100644 index 0000000..fca2437 --- /dev/null +++ b/next-sitemap.config.js @@ -0,0 +1,42 @@ +/** @type {import('next-sitemap').IConfig} */ +module.exports = { + siteUrl: 'https://www.qrmaster.com', + generateRobotsTxt: true, + robotsTxtOptions: { + policies: [ + { + userAgent: '*', + allow: '/', + }, + ], + }, + transform: async (config, path) => { + // Custom priority and changefreq based on path + let priority = 0.7; + let changefreq = 'weekly'; + + if (path === '/') { + priority = 0.9; + changefreq = 'daily'; + } else if (path === '/blog') { + priority = 0.7; + changefreq = 'daily'; + } else if (path === '/pricing') { + priority = 0.8; + changefreq = 'weekly'; + } else if (path === '/faq') { + priority = 0.6; + changefreq = 'weekly'; + } else if (path.startsWith('/blog/')) { + priority = 0.6; + changefreq = 'weekly'; + } + + return { + loc: path, + changefreq, + priority, + lastmod: new Date().toISOString(), + }; + }, +}; diff --git a/next.config.mjs b/next.config.mjs index 4b4fb80..5bd78cc 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,8 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { output: 'standalone', - images: { - unoptimized: true + images: { + unoptimized: true, + domains: ['www.qrmaster.com', 'qrmaster.com', 'images.qrmaster.com'] }, experimental: { serverComponentsExternalPackages: ['@prisma/client', 'bcryptjs'] diff --git a/package-lock.json b/package-lock.json index ab05b8d..bd76b3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "autoprefixer": "^10.4.16", "eslint": "^8.56.0", "eslint-config-next": "14.2.18", + "next-sitemap": "^4.2.3", "postcss": "^8.4.32", "prettier": "^3.1.1", "prisma": "^5.7.0", @@ -120,6 +121,13 @@ "node": ">=6.9.0" } }, + "node_modules/@corex/deepmerge": { + "version": "4.0.43", + "resolved": "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.43.tgz", + "integrity": "sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@emnapi/core": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", @@ -5949,6 +5957,41 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/next-sitemap": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.2.3.tgz", + "integrity": "sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==", + "dev": true, + "funding": [ + { + "url": "https://github.com/iamvishnusankar/next-sitemap.git" + } + ], + "license": "MIT", + "dependencies": { + "@corex/deepmerge": "^4.0.43", + "@next/env": "^13.4.3", + "fast-glob": "^3.2.12", + "minimist": "^1.2.8" + }, + "bin": { + "next-sitemap": "bin/next-sitemap.mjs", + "next-sitemap-cjs": "bin/next-sitemap.cjs" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "next": "*" + } + }, + "node_modules/next-sitemap/node_modules/@next/env": { + "version": "13.5.11", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.11.tgz", + "integrity": "sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==", + "dev": true, + "license": "MIT" + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", diff --git a/package.json b/package.json index a810bcc..f43d7d0 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "autoprefixer": "^10.4.16", "eslint": "^8.56.0", "eslint-config-next": "14.2.18", + "next-sitemap": "^4.2.3", "postcss": "^8.4.32", "prettier": "^3.1.1", "prisma": "^5.7.0", diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..5c5c6a2 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Sitemap: https://www.qrmaster.com/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..0fa6818 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,33 @@ + + + + https://www.qrmaster.com/ + 2025-10-16T00:00:00Z + daily + 0.9 + + + https://www.qrmaster.com/blog + 2025-10-16T00:00:00Z + daily + 0.7 + + + https://www.qrmaster.com/pricing + 2025-10-16T00:00:00Z + weekly + 0.8 + + + https://www.qrmaster.com/faq + 2025-10-16T00:00:00Z + weekly + 0.6 + + + https://www.qrmaster.com/blog/qr-code-analytics + 2025-10-16T00:00:00Z + weekly + 0.6 + + diff --git a/public/static/.gitkeep b/public/static/.gitkeep new file mode 100644 index 0000000..ed02446 --- /dev/null +++ b/public/static/.gitkeep @@ -0,0 +1,2 @@ +# Placeholder for og-image.png +# Replace public/static/og-image.png with actual 1200×630 branded image before production diff --git a/src/app/(app)/create/page.tsx b/src/app/(app)/create/page.tsx index a309066..2a95708 100644 --- a/src/app/(app)/create/page.tsx +++ b/src/app/(app)/create/page.tsx @@ -60,12 +60,8 @@ export default function CreatePage() { const contentTypes = [ { value: 'URL', label: 'URL / Website' }, { value: 'WIFI', label: 'WiFi Network' }, - { value: 'VCARD', label: 'Contact Card' }, - { value: 'PHONE', label: 'Phone Number' }, { value: 'EMAIL', label: 'Email' }, - { value: 'SMS', label: 'SMS' }, - { value: 'TEXT', label: 'Plain Text' }, - { value: 'WHATSAPP', label: 'WhatsApp' }, + { value: 'PHONE', label: 'Phone Number' }, ]; // Get QR content based on content type @@ -159,7 +155,7 @@ export default function CreatePage() { title, contentType, content, - isStatic: !isDynamic, // Add this flag + isStatic: !isDynamic, tags: tags.split(',').map(t => t.trim()).filter(Boolean), style: { // FREE users can only use black/white @@ -291,7 +287,7 @@ export default function CreatePage() { return (
-

{t('create.title')}

+

Create QR Code

@@ -333,7 +329,7 @@ export default function CreatePage() { {/* QR Type Section */} - {t('create.type')} + QR Code Type
@@ -354,22 +350,14 @@ export default function CreatePage() { onChange={() => setIsDynamic(false)} className="mr-2" /> - Static (Direct URL) + Static

- {isDynamic + {isDynamic ? '✅ Dynamic: Track scans, edit URL later, view analytics. QR contains tracking link.' - : '⚡ Static: Direct to URL, no tracking, cannot edit. QR contains actual URL.'} + : '⚡ Static: Direct to content, no tracking, cannot edit. QR contains actual content.'}

- {isDynamic && ( -
-

- Note: Dynamic QR codes route through your server for tracking. - In production, deploy your app to get a public URL instead of localhost. -

-
- )}
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 13c97d3..b211c53 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -2,8 +2,7 @@ import React, { useState } from 'react'; import Link from 'next/link'; -import { usePathname } from 'next/navigation'; -import { signOut } from 'next-auth/react'; +import { usePathname, useRouter } from 'next/navigation'; import { Button } from '@/components/ui/Button'; import { Dropdown, DropdownItem } from '@/components/ui/Dropdown'; import { useTranslation } from '@/hooks/useTranslation'; @@ -14,9 +13,21 @@ export default function AppLayout({ children: React.ReactNode; }) { const pathname = usePathname(); - const { t, locale, setLocale } = useTranslation(); + const router = useRouter(); + const { t } = useTranslation(); const [sidebarOpen, setSidebarOpen] = useState(false); + const handleSignOut = () => { + // Clear all cookies + document.cookie.split(";").forEach(c => { + document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); + }); + // Clear localStorage + localStorage.clear(); + // Redirect to home + router.push('/'); + }; + const navigation = [ { name: 'Dashboard', @@ -54,6 +65,16 @@ export default function AppLayout({ ), }, + { + name: 'Settings', + href: '/settings', + icon: ( + + + + + ), + }, ]; return ( @@ -123,14 +144,6 @@ export default function AppLayout({
- {/* Language Switcher */} - - {/* User Menu */} } > - signOut()}> + Sign Out diff --git a/src/app/(app)/pricing/page.tsx b/src/app/(app)/pricing/page.tsx index b85659d..c5f91ad 100644 --- a/src/app/(app)/pricing/page.tsx +++ b/src/app/(app)/pricing/page.tsx @@ -33,9 +33,9 @@ export default function PricingPage() { description: 'Privatnutzer & Testkunden', features: [ '3 dynamische QR-Codes', - 'Basis-Tracking (Scans + Standort)', - 'Einfache Designs', 'Unbegrenzte statische QR-Codes', + 'Basis-Scan-Tracking', + 'Standard QR-Design-Vorlagen', ], cta: 'Get Started', popular: false, @@ -50,11 +50,11 @@ export default function PricingPage() { priceYearly: 90, description: 'Selbstständige / kleine Firmen', features: [ - '50 QR-Codes', - 'Branding (Logo, Farben)', - 'Detaillierte Analytics', - 'CSV-Export', - 'Passwortschutz', + '50 dynamische QR-Codes', + 'Unbegrenzte statische QR-Codes', + 'Erweiterte Analytik (Scans, Geräte, Standorte)', + 'Individuelles Branding (Farben & Logo)', + 'Download als SVG/PNG', ], cta: 'Upgrade to Pro', popular: true, @@ -69,11 +69,11 @@ export default function PricingPage() { priceYearly: 290, description: 'Agenturen / Startups', features: [ - '500 QR-Codes', - 'Team-Zugänge (bis 3 User)', - 'Benutzerdefinierte Domains', - 'White-Label', - 'Prioritäts-Support', + '500 dynamische QR-Codes', + 'Unbegrenzte statische QR-Codes', + 'Alles aus Pro', + 'Prioritäts-E-Mail-Support', + 'Erweiterte Tracking & Insights', ], cta: 'Upgrade to Business', popular: false, diff --git a/src/app/(app)/settings/page.tsx b/src/app/(app)/settings/page.tsx index 91633d8..af10d04 100644 --- a/src/app/(app)/settings/page.tsx +++ b/src/app/(app)/settings/page.tsx @@ -1,453 +1,146 @@ 'use client'; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; -import { Input } from '@/components/ui/Input'; import { Button } from '@/components/ui/Button'; -import { Badge } from '@/components/ui/Badge'; +import { showToast } from '@/components/ui/Toast'; import { useTranslation } from '@/hooks/useTranslation'; export default function SettingsPage() { - const { t } = useTranslation(); - const [activeTab, setActiveTab] = useState('profile'); + const { t, setLanguage, language } = useTranslation(); const [loading, setLoading] = useState(false); - - // Form states - const [profile, setProfile] = useState({ - name: '', - email: '', - company: '', - phone: '', - }); - // Load user data from localStorage - React.useEffect(() => { + // Form states + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [selectedLanguage, setSelectedLanguage] = useState(language || 'en'); + + // Load user data and language from localStorage + useEffect(() => { const userStr = localStorage.getItem('user'); if (userStr) { - const user = JSON.parse(userStr); - setProfile({ - name: user.name || '', - email: user.email || '', - company: user.company || '', - phone: user.phone || '', - }); + try { + const user = JSON.parse(userStr); + setName(user.name || ''); + setEmail(user.email || ''); + } catch (e) { + console.error('Failed to parse user data:', e); + } + } + + // Load saved language preference + const savedLocale = localStorage.getItem('locale'); + if (savedLocale && (savedLocale === 'en' || savedLocale === 'de')) { + setSelectedLanguage(savedLocale); } }, []); - const [apiKey, setApiKey] = useState(''); - const [showApiKey, setShowApiKey] = useState(false); - - const tabs = [ - { id: 'profile', label: 'Profile', icon: '👤' }, - { id: 'billing', label: 'Billing', icon: '💳' }, - { id: 'team', label: 'Team & Roles', icon: '👥' }, - { id: 'api', label: 'API Keys', icon: '🔑' }, - { id: 'workspace', label: 'Workspace', icon: '🏢' }, - ]; - - const generateApiKey = () => { - const key = 'qrm_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); - setApiKey(key); - setShowApiKey(true); - }; - - const handleSaveProfile = async () => { + const handleSave = async () => { setLoading(true); - // Simulate API call - await new Promise(resolve => setTimeout(resolve, 1000)); - setLoading(false); + + try { + // Update language + setLanguage(selectedLanguage); + + // Update user data in localStorage + const userStr = localStorage.getItem('user'); + if (userStr) { + const user = JSON.parse(userStr); + user.name = name; + localStorage.setItem('user', JSON.stringify(user)); + } + + showToast('Settings saved successfully!', 'success'); + } catch (error) { + console.error('Error saving settings:', error); + showToast('Failed to save settings', 'error'); + } finally { + setLoading(false); + } }; return ( -
+
-

{t('settings.title')}

-

{t('settings.subtitle')}

+

Settings

+

Manage your account settings and preferences

-
- {/* Sidebar */} -
- - - {tabs.map((tab) => ( - - ))} - - -
- - {/* Content */} -
- {/* Profile Tab */} - {activeTab === 'profile' && ( - - - Profile Information - - -
-
- JD -
-
- -

JPG, PNG or GIF. Max 2MB

-
-
- -
- setProfile({ ...profile, name: e.target.value })} - /> - setProfile({ ...profile, email: e.target.value })} - /> - setProfile({ ...profile, company: e.target.value })} - /> - setProfile({ ...profile, phone: e.target.value })} - /> -
- -
- -
-
-
- )} - - {/* Billing Tab */} - {activeTab === 'billing' && ( -
- - - Current Plan - - -
-
-
-

Pro Plan

- Active -
-

€9/month • Renews on Jan 1, 2025

-
- -
- -
-
-

QR Codes

-

234 / 500

-
-
-
-
-
-

Scans

-

45,678 / 100,000

-
-
-
-
-
-

API Calls

-

12,345 / 50,000

-
-
-
-
-
-
-
- - - - Payment Method - - -
-
-
- VISA -
-
-

•••• •••• •••• 4242

-

Expires 12/25

-
-
- -
- -
-
- - - - Billing History - - -
- {[ - { date: 'Dec 1, 2024', amount: '€9.00', status: 'Paid' }, - { date: 'Nov 1, 2024', amount: '€9.00', status: 'Paid' }, - { date: 'Oct 1, 2024', amount: '€9.00', status: 'Paid' }, - ].map((invoice, index) => ( -
-
-

{invoice.date}

-

Pro Plan Monthly

-
-
- {invoice.status} - {invoice.amount} - -
-
- ))} -
-
-
+
+ {/* Profile Settings */} + + + Profile Information + + +
+ + setName(e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500" + placeholder="Enter your name" + />
- )} - {/* Team Tab */} - {activeTab === 'team' && ( - - -
- Team Members - -
-
- -
- {[ - { name: 'John Doe', email: 'john@example.com', role: 'Owner', status: 'Active' }, - { name: 'Jane Smith', email: 'jane@example.com', role: 'Admin', status: 'Active' }, - { name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor', status: 'Active' }, - { name: 'Alice Brown', email: 'alice@example.com', role: 'Viewer', status: 'Pending' }, - ].map((member, index) => ( -
-
-
- - {member.name.split(' ').map(n => n[0]).join('')} - -
-
-

{member.name}

-

{member.email}

-
-
-
- - {member.status} - - - {member.role !== 'Owner' && ( - - )} -
-
- ))} -
- -
-

- Team Seats: 4 of 5 used -

-

- Upgrade to Business plan for unlimited team members -

-
-
-
- )} - - {/* API Keys Tab */} - {activeTab === 'api' && ( -
- - - API Access - - -
-

- Use API keys to integrate QR Master with your applications. Keep your keys secure and never share them publicly. -

-
-

- ⚠️ Warning: API keys provide full access to your account. Treat them like passwords. -

-
-
- - {!apiKey ? ( - - ) : ( -
-
- -
- - - -
-

- This key will only be shown once. Store it securely. -

-
-
- )} -
-
- - - - API Documentation - - -
-
-

Base URL

- - https://api.qrmaster.com/v1 - -
-
-

Authentication

- - Authorization: Bearer YOUR_API_KEY - -
-
-

Example Request

-
-{`curl -X POST https://api.qrmaster.com/v1/qr-codes \\
-  -H "Authorization: Bearer YOUR_API_KEY" \\
-  -H "Content-Type: application/json" \\
-  -d '{"title":"My QR","content":"https://example.com"}'`}
-                      
-
- -
-
-
+
+ + +

+ Email cannot be changed +

- )} + + - {/* Workspace Tab */} - {activeTab === 'workspace' && ( - - - Workspace Settings - - -
- - -
+ {/* Language Settings */} + + + Language Preferences + + +
+ + +

+ Choose your preferred language for the interface +

+
+
+
-
- -
- - qrmaster.com/ - - -
-
- -
- -
- - - -
-
- -
-

Danger Zone

-
-

- Deleting your workspace will permanently remove all QR codes, analytics data, and team members. -

- -
-
- -
- -
-
-
- )} + {/* Save Button */} +
+
); -} \ No newline at end of file +} diff --git a/src/app/(marketing)/blog/[slug]/page.tsx b/src/app/(marketing)/blog/[slug]/page.tsx index ebcfade..1be556e 100644 --- a/src/app/(marketing)/blog/[slug]/page.tsx +++ b/src/app/(marketing)/blog/[slug]/page.tsx @@ -1,157 +1,324 @@ -'use client'; - import React from 'react'; -import { useParams } from 'next/navigation'; +import type { Metadata } from 'next'; import Link from 'next/link'; +import Image from 'next/image'; +import { notFound } from 'next/navigation'; +import SeoJsonLd from '@/components/SeoJsonLd'; +import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs'; +import { blogPostingSchema, breadcrumbSchema, howToSchema } from '@/lib/schema'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; -const blogContent = { - 'qr-codes-im-restaurant': { - title: 'QR-Codes im Restaurant: Die digitale Revolution der Speisekarte', - date: '2024-01-15', - readTime: '5 Min', - category: 'Gastronomie', - content: ` -

Die Gastronomie hat sich in den letzten Jahren stark digitalisiert, und QR-Codes spielen dabei eine zentrale Rolle. Von kontaktlosen Speisekarten bis hin zu digitalen Zahlungssystemen – QR-Codes revolutionieren die Art und Weise, wie Restaurants mit ihren Gästen interagieren.

- -

Vorteile für Restaurants

-
    -
  • Kostenersparnis durch digitale Speisekarten
  • -
  • Einfache Aktualisierung von Preisen und Angeboten
  • -
  • Hygienische, kontaktlose Lösung
  • -
  • Mehrsprachige Menüs ohne zusätzliche Druckkosten
  • -
- -

Vorteile für Gäste

-
    -
  • Schneller Zugriff auf aktuelle Informationen
  • -
  • Detaillierte Produktbeschreibungen und Allergeninformationen
  • -
  • Einfache Bestellung und Bezahlung
  • -
  • Personalisierte Empfehlungen
  • -
- -

Die Implementierung von QR-Codes in Ihrem Restaurant ist einfacher als Sie denken. Mit QR Master können Sie in wenigen Minuten professionelle QR-Codes erstellen, die perfekt zu Ihrem Branding passen.

- `, - }, - 'dynamische-vs-statische-qr-codes': { - title: 'Dynamische vs. Statische QR-Codes: Was ist der Unterschied?', - date: '2024-01-10', - readTime: '3 Min', - category: 'Grundlagen', - content: ` -

Bei der Erstellung von QR-Codes stehen Sie vor der Wahl zwischen statischen und dynamischen Codes. Beide haben ihre Vor- und Nachteile, und die richtige Wahl hängt von Ihrem spezifischen Anwendungsfall ab.

- -

Statische QR-Codes

-

Statische QR-Codes enthalten die Informationen direkt im Code selbst. Einmal erstellt, können sie nicht mehr geändert werden.

-
    -
  • Kostenlos und unbegrenzt nutzbar
  • -
  • Funktionieren für immer ohne Server
  • -
  • Ideal für permanente Informationen
  • -
  • Keine Tracking-Möglichkeiten
  • -
- -

Dynamische QR-Codes

-

Dynamische QR-Codes verweisen auf eine URL, die Sie jederzeit ändern können.

-
    -
  • Inhalt kann nachträglich geändert werden
  • -
  • Detaillierte Scan-Statistiken
  • -
  • Kürzere, sauberere QR-Codes
  • -
  • Perfekt für Marketing-Kampagnen
  • -
- -

Mit QR Master können Sie beide Arten von QR-Codes erstellen und verwalten. Unsere Plattform bietet Ihnen die Flexibilität, die Sie für Ihre Projekte benötigen.

- `, - }, - 'qr-code-marketing-strategien': { - title: 'QR-Code Marketing-Strategien für 2024', - date: '2024-01-05', - readTime: '7 Min', - category: 'Marketing', - content: ` -

QR-Codes sind zu einem unverzichtbaren Werkzeug im modernen Marketing geworden. Hier sind die effektivsten Strategien für 2024.

- -

1. Personalisierte Kundenerlebnisse

-

Nutzen Sie dynamische QR-Codes, um personalisierte Landingpages basierend auf Standort, Zeit oder Kundenverhalten zu erstellen.

- -

2. Social Media Integration

-

Verbinden Sie QR-Codes mit Ihren Social-Media-Kampagnen für nahtlose Cross-Channel-Erlebnisse.

- -

3. Event-Marketing

-

Von Tickets bis zu Networking – QR-Codes machen Events interaktiver und messbar.

- -

4. Loyalty-Programme

-

Digitale Treuekarten und Rabattaktionen lassen sich perfekt mit QR-Codes umsetzen.

- -

5. Analytics und Optimierung

-

Nutzen Sie die Tracking-Funktionen, um Ihre Kampagnen kontinuierlich zu verbessern.

- -

Mit QR Master haben Sie alle Tools, die Sie für erfolgreiches QR-Code-Marketing benötigen. Starten Sie noch heute mit Ihrer ersten Kampagne!

- `, +interface BlogPostData { + slug: string; + title: string; + excerpt: string; + date: string; + datePublished: string; + dateModified: string; + readTime: string; + category: string; + image: string; + imageAlt: string; + author: string; + authorUrl: string; + answer?: string; + howTo?: any; + content: string; +} + +const blogPosts: Record = { + 'qr-code-analytics': { + slug: 'qr-code-analytics', + title: 'QR Code Analytics: Track, Measure & Optimize', + excerpt: 'Master scan analytics, campaign tracking & dashboard insights to maximize QR ROI with dynamic codes.', + date: 'October 16, 2025', + datePublished: '2025-10-16T09:00:00Z', + dateModified: '2025-10-16T09:00:00Z', + readTime: '8 Min', + category: 'Analytics', + image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=1200&q=80', + imageAlt: 'QR code analytics dashboard showing real-time scan metrics and campaign performance data', + author: 'QR Master Team', + authorUrl: 'https://www.qrmaster.com/about', + answer: 'QR code analytics empowers marketers to track scan rates, user behavior, and campaign ROI through real-time dashboards, enabling data-driven optimization of dynamic QR codes and branded marketing campaigns.', + howTo: { + name: 'How to Track QR Code Scans', + description: 'Step-by-step guide to setting up and monitoring QR code analytics', + totalTime: 'PT10M', + steps: [ + { + name: 'Create a Dynamic QR Code', + text: 'Log into your QR Master dashboard and select "Create Dynamic QR Code". Enter your destination URL and customize design options.', + url: 'https://www.qrmaster.com/create', + }, + { + name: 'Enable UTM Tracking', + text: 'Add UTM parameters (source, medium, campaign) to track the QR code in Google Analytics and marketing platforms.', + }, + { + name: 'Access Analytics Dashboard', + text: 'Navigate to Dashboard → Analytics to view real-time scan data, geographic distribution, and device breakdowns.', + url: 'https://www.qrmaster.com/analytics', + }, + ], + }, + content: `
+

What Are Scan Analytics?

+

Scan analytics provide comprehensive insights into how users interact with your QR codes. Our advanced dashboard tracks scan analytics including geographic location, device types, scan timestamps, and user engagement patterns. For marketers running dynamic QR code campaigns, these insights are essential for understanding campaign tracking performance and optimizing conversion rates.

+

With branded QR codes deployed across print materials, event tickets, and business cards, scan analytics reveal which channels drive the highest engagement. Security features ensure all data collection is GDPR-compliant, protecting user privacy while delivering actionable campaign tracking insights.

+ +

How to Set Up QR Code Analytics

+

Step 1: Create a Dynamic QR Code

+

Start by generating a dynamic QR code in your QR Master dashboard. Unlike static codes, dynamic QR codes allow you to update destination URLs and track every scan through our analytics platform.

+ +

Step 2: Enable Campaign Tracking

+

Configure UTM parameters for your QR codes to integrate with Google Analytics and marketing automation platforms. UTM tracking allows you to attribute conversions, measure ROI, and segment campaign performance by source, medium, and campaign name.

+ +

Step 3: Access Your Analytics Dashboard

+

Navigate to the scan analytics dashboard to view real-time reports. Monitor scan rates, geographic distribution, device breakdowns, and time-series data. Set up automated reports to track campaign tracking metrics over time.

+ +

Step 4: Optimize Based on Insights

+

Use scan analytics to identify high-performing campaigns and optimize underperforming ones. A/B test different branded QR designs, placement strategies, and call-to-action messaging to maximize engagement and conversion rates.

+ +

Key Metrics in QR Code Analytics

+

Scan Rates and Volume

+

Track total scans, unique scans, and scan velocity. Scan rates reveal campaign momentum and help identify viral growth patterns. Compare scan volumes across different branded QR variations to determine which designs perform best.

+ +

Geographic Distribution

+

Understand where your audience is scanning from. Geographic analytics support localized marketing strategies and event tracking for conferences, trade shows, and retail activations.

+ +

Device and Browser Analytics

+

Know whether users scan from iOS or Android devices, which browsers they use, and screen resolutions. This data informs mobile optimization strategies and ensures your landing pages deliver seamless experiences across all devices.

+ +

Time-Based Patterns

+

Identify peak scanning hours, days of the week, and seasonal trends. Time-based analytics optimize campaign timing for email blasts, social media posts, and print QR deployments.

+ +

Conversion Tracking

+

Measure downstream actions after the scan—form submissions, purchases, app downloads, or content engagement. Integrate with your CRM and marketing stack to attribute revenue to specific QR campaigns.

+ +

Advanced Campaign Tracking Strategies

+

UTM Tracking Integration

+

Append UTM parameters to your dynamic QR URLs for granular campaign attribution. Use consistent naming conventions across campaigns to compare performance in Google Analytics. UTM tracking bridges offline and online marketing, providing a unified view of customer journeys.

+ +

Multi-Channel Attribution

+

Deploy branded QR codes across print ads, packaging, event tickets, business cards, and signage. Use unique QR codes for each channel to measure which touchpoints drive the highest ROI. Multi-channel attribution reveals the true value of integrated marketing campaigns.

+ +

A/B Testing QR Designs

+

Test different branded QR styles—color schemes, logo placements, and call-to-action text—to optimize scan rates. Our analytics dashboard makes it easy to compare performance and roll out winning variations at scale.

+ +

Retargeting and Remarketing

+

Leverage scan analytics to build retargeting audiences. Users who scan but don't convert can be re-engaged with display ads, email campaigns, and social media retargeting, boosting overall campaign ROI.

+ +

Security and Compliance in QR Analytics

+

All QR Master scan analytics are GDPR-compliant, ensuring user data is collected, stored, and processed securely. We employ enterprise-grade security protocols to protect sensitive campaign data, making our platform ideal for bulk QR generation workflows in regulated industries.

+

Secure QR codes prevent unauthorized access and malicious redirects. Our platform includes link validation, SSL encryption, and fraud detection to maintain trust and protect your brand reputation.

+ +

Use Cases for QR Code Analytics

+

Event Tracking

+

Deploy QR codes on event tickets, badges, and signage to track attendee engagement. Scan analytics reveal which sessions attract the most interest, optimize check-in flows, and measure event ROI.

+ +

Print Marketing Campaigns

+

Use QR codes in magazine ads, direct mail, and packaging to bridge offline and online channels. Campaign tracking quantifies print campaign performance and justifies marketing spend.

+ +

Business Card Analytics

+

Add dynamic QR codes to business cards to track networking effectiveness. Scan analytics show how many contacts engage, when they scan, and which follow-up actions they take.

+ +

Bulk QR Generation for Retail

+

Generate thousands of product QR codes with our bulk QR tool. Track scan analytics at the SKU level to understand customer interest, optimize inventory, and personalize marketing.

+ +

API-Driven Automation

+

Integrate QR code generation and analytics into your marketing automation platform via our API. Automate bulk QR creation, dynamic URL updates, and reporting workflows for enterprise-scale campaigns.

+ +

Maximizing ROI with Scan Analytics

+

To maximize QR code ROI, continuously monitor scan analytics and iterate on campaign strategies. Test different branded QR designs, optimize UTM parameters, and leverage multi-channel attribution to understand the full customer journey.

+

Combine scan analytics with customer data platforms (CDPs) and CRMs to personalize follow-up communications. Segment audiences based on scan behavior and deliver targeted offers that drive conversions.

+

For bulk QR campaigns, use our analytics dashboard to identify trends across thousands of codes. Aggregate data reveals macro patterns while code-level metrics enable micro-optimizations.

+ +

Conclusion

+

QR code analytics transforms QR codes from simple links into powerful marketing instruments. By tracking scan rates, user behavior, and campaign performance through advanced dashboards, marketers gain the insights needed to optimize dynamic QR campaigns, enhance branded experiences, and achieve measurable ROI.

+

Whether you're deploying QR codes for event tracking, print marketing, bulk generation, or API-driven automation, scan analytics provides the data foundation for smarter, more effective campaigns. Start leveraging QR analytics today to unlock the full potential of your QR marketing strategy.

+
`, }, }; -export default function BlogPostPage() { - const params = useParams(); - const slug = params?.slug as string; - const post = blogContent[slug as keyof typeof blogContent]; +function truncateAtWord(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + const truncated = text.slice(0, maxLength); + const lastSpace = truncated.lastIndexOf(' '); + return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated; +} + +export async function generateMetadata({ params }: { params: { slug: string } }): Promise { + const post = blogPosts[params.slug]; if (!post) { - return ( -
-
-

Post not found

-

The blog post you're looking for doesn't exist.

- - - -
-
- ); + return { + title: 'Post Not Found', + }; + } + + const title = truncateAtWord(`${post.title} - QR Analytics Tips`, 60); + const description = truncateAtWord(post.excerpt, 160); + + return { + title, + description, + alternates: { + canonical: `https://www.qrmaster.com/blog/${params.slug}`, + languages: { + 'x-default': `https://www.qrmaster.com/blog/${params.slug}`, + en: `https://www.qrmaster.com/blog/${params.slug}`, + }, + }, + openGraph: { + title, + description, + url: `https://www.qrmaster.com/blog/${params.slug}`, + type: 'article', + publishedTime: post.datePublished, + modifiedTime: post.dateModified, + authors: [post.author], + images: [ + { + url: post.image, + width: 1200, + height: 630, + alt: post.imageAlt, + }, + ], + }, + twitter: { + title, + description, + card: 'summary_large_image', + images: [post.image], + }, + }; +} + +export default function BlogPostPage({ params }: { params: { slug: string } }) { + const post = blogPosts[params.slug]; + + if (!post) { + notFound(); + } + + const breadcrumbItems: BreadcrumbItem[] = [ + { name: 'Home', url: '/' }, + { name: 'Blog', url: '/blog' }, + { name: post.title, url: `/blog/${post.slug}` }, + ]; + + const schemas = [ + blogPostingSchema({ + title: post.title, + description: post.excerpt, + slug: post.slug, + author: post.author, + authorUrl: post.authorUrl, + datePublished: post.datePublished, + dateModified: post.dateModified, + image: post.image, + }), + breadcrumbSchema(breadcrumbItems), + ]; + + if (post.howTo) { + schemas.push(howToSchema(post.howTo)); } return ( -
-
-
- - - - - Back to Blog - + <> + +
+
+
+ -
-
-
- {post.category} - {post.readTime} - {post.date} +
+
+
+ {post.category} + + + + + {post.readTime} read + + By {post.author} + {post.date} +
+ +

+ {post.title} +

+ + {post.answer && ( +
+

Quick Answer

+

{post.answer}

+
+ )} + +
+ {post.imageAlt} +
+
+ +
+ + {post.howTo && ( +
+

{post.howTo.name}

+

{post.howTo.description}

+
    + {post.howTo.steps.map((step: any, index: number) => ( +
  1. + + {index + 1} + +
    +

    {step.name}

    +

    {step.text}

    +
    +
  2. + ))} +
+
+ )} + +
+

+ Ready to Track Your QR Campaigns? +

+

+ Start creating professional dynamic QR codes with advanced scan analytics, campaign tracking, and real-time insights. +

+ + +
-

- {post.title} -

-
- -
- -
-

- Ready to create your QR codes? -

-

- Start creating professional QR codes with advanced tracking and analytics. -

- - - -
-
+ +
-
+ ); -} \ No newline at end of file +} diff --git a/src/app/(marketing)/blog/page.tsx b/src/app/(marketing)/blog/page.tsx index 42675d9..fde65e6 100644 --- a/src/app/(marketing)/blog/page.tsx +++ b/src/app/(marketing)/blog/page.tsx @@ -1,76 +1,128 @@ -'use client'; - import React from 'react'; +import type { Metadata } from 'next'; import Link from 'next/link'; +import Image from 'next/image'; +import SeoJsonLd from '@/components/SeoJsonLd'; +import { websiteSchema } from '@/lib/schema'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; import { Badge } from '@/components/ui/Badge'; +function truncateAtWord(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + const truncated = text.slice(0, maxLength); + const lastSpace = truncated.lastIndexOf(' '); + return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated; +} + +export async function generateMetadata(): Promise { + const title = truncateAtWord('QR Insights: Latest QR Strategies', 60); + const description = truncateAtWord( + 'Expert guides on QR analytics, dynamic codes & smart marketing uses.', + 160 + ); + + return { + title, + description, + alternates: { + canonical: 'https://www.qrmaster.com/blog', + languages: { + 'x-default': 'https://www.qrmaster.com/blog', + en: 'https://www.qrmaster.com/blog', + }, + }, + openGraph: { + title, + description, + url: 'https://www.qrmaster.com/blog', + type: 'website', + }, + twitter: { + title, + description, + }, + }; +} + const blogPosts = [ + { + slug: 'qr-code-analytics', + title: 'QR Code Analytics: Track, Measure & Optimize Campaigns', + excerpt: 'Learn how to leverage scan analytics, campaign tracking, and dashboard insights to maximize QR code ROI.', + date: 'October 16, 2025', + readTime: '8 Min', + category: 'Analytics', + image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&q=80', + }, { slug: 'qr-codes-im-restaurant', - title: 'QR-Codes im Restaurant: Die digitale Revolution der Speisekarte', - excerpt: 'Erfahren Sie, wie QR-Codes die Gastronomie revolutionieren und welche Vorteile sie für Restaurants und Gäste bieten.', - date: '2024-01-15', + title: 'QR Codes in Restaurants: The Digital Menu Revolution', + excerpt: 'Discover how QR codes are revolutionizing the restaurant industry and what benefits they offer for restaurants and guests.', + date: 'January 15, 2024', readTime: '5 Min', - category: 'Gastronomie', - image: '🍽️', + category: 'Restaurant', + image: 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&q=80', }, { slug: 'dynamische-vs-statische-qr-codes', - title: 'Dynamische vs. Statische QR-Codes: Was ist der Unterschied?', - excerpt: 'Ein umfassender Vergleich zwischen dynamischen und statischen QR-Codes und wann Sie welchen Typ verwenden sollten.', - date: '2024-01-10', + title: 'Dynamic vs Static QR Codes: What\'s the Difference?', + excerpt: 'A comprehensive comparison between dynamic and static QR codes and when you should use each type.', + date: 'January 10, 2024', readTime: '3 Min', - category: 'Grundlagen', - image: '📊', - }, - { - slug: 'qr-code-marketing-strategien', - title: 'QR-Code Marketing-Strategien für 2024', - excerpt: 'Die besten Marketing-Strategien mit QR-Codes für Ihr Unternehmen im Jahr 2024.', - date: '2024-01-05', - readTime: '7 Min', - category: 'Marketing', - image: '📈', + category: 'Basics', + image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&q=80', }, ]; export default function BlogPage() { return ( -
-
-
-

- Blog & Resources -

-

- Learn about QR codes, best practices, and industry insights -

-
+ <> + +
+
+
+

+ QR Code Insights +

+

+ Expert guides on dynamic QR codes, campaign tracking, UTM analytics, and smart marketing use cases. + Discover how-to tutorials and best practices for QR code analytics. +

+
-
- {blogPosts.map((post) => ( - - - -
- {post.image} +
+ {blogPosts.map((post) => ( + + +
+ {`${post.title}
-
- {post.category} - {post.readTime} -
- {post.title} - - -

{post.excerpt}

-

{post.date}

-
-
- - ))} + +
+ {post.category} + {post.readTime} read +
+ {post.title} +
+ +

{post.excerpt}

+
+

{post.date}

+ Read more → +
+
+ + + ))} +
-
+ ); -} \ No newline at end of file +} diff --git a/src/app/(marketing)/faq/page.tsx b/src/app/(marketing)/faq/page.tsx index eafa6e6..457efbf 100644 --- a/src/app/(marketing)/faq/page.tsx +++ b/src/app/(marketing)/faq/page.tsx @@ -1,15 +1,143 @@ -'use client'; - import React from 'react'; -import { FAQ } from '@/components/marketing/FAQ'; -import { useTranslation } from '@/hooks/useTranslation'; +import type { Metadata } from 'next'; +import SeoJsonLd from '@/components/SeoJsonLd'; +import { faqPageSchema } from '@/lib/schema'; +import { Card, CardContent } from '@/components/ui/Card'; + +function truncateAtWord(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + const truncated = text.slice(0, maxLength); + const lastSpace = truncated.lastIndexOf(' '); + return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated; +} + +export async function generateMetadata(): Promise { + const title = truncateAtWord('QR Master FAQ: Dynamic & Bulk QR', 60); + const description = truncateAtWord( + 'All answers: dynamic QR, security, analytics, bulk, events & print.', + 160 + ); + + return { + title, + description, + alternates: { + canonical: 'https://www.qrmaster.com/faq', + languages: { + 'x-default': 'https://www.qrmaster.com/faq', + en: 'https://www.qrmaster.com/faq', + }, + }, + openGraph: { + title, + description, + url: 'https://www.qrmaster.com/faq', + type: 'website', + }, + twitter: { + title, + description, + }, + }; +} + +const faqs = [ + { + question: 'What is a dynamic QR code?', + answer: 'A dynamic QR code allows you to change the destination URL after the code has been created and printed. Unlike static QR codes, dynamic codes redirect through a short URL that you control, enabling real-time updates, scan analytics, and campaign tracking without reprinting the code.', + }, + { + question: 'How do I track QR scans?', + answer: 'QR Master provides a comprehensive analytics dashboard that tracks every scan in real-time. You can monitor scan rates, geographic locations, device types, timestamps, and user behavior. Enable UTM parameters to integrate with Google Analytics for advanced campaign tracking and conversion attribution.', + }, + { + question: 'What security features does QR Master offer?', + answer: 'QR Master employs enterprise-grade security including SSL encryption, link validation to prevent malicious redirects, fraud detection, and GDPR-compliant data handling. All scan analytics are stored securely and access is protected with multi-factor authentication for business accounts.', + }, + { + question: 'Can I generate bulk QR codes for print?', + answer: 'Yes. Our bulk QR generation tool allows you to create thousands of QR codes at once by uploading a CSV file. Each code can be customized with unique URLs, UTM parameters, and branding. Download print-ready files in SVG, PNG, or PDF formats optimized for high-resolution printing.', + }, + { + question: 'How do I brand my QR codes?', + answer: 'QR Master offers full customization options including custom colors, logo embedding, rounded corners, and pattern styles. Branded QR codes maintain scannability while matching your brand identity. Upload your logo, choose your color palette, and preview designs before downloading.', + }, + { + question: 'Is scan analytics GDPR compliant?', + answer: 'Yes. All QR Master analytics are fully GDPR compliant. We collect only necessary data, provide transparent privacy policies, allow users to opt out, and store data securely in EU-compliant data centers. You maintain full control over data retention and deletion.', + }, + { + question: 'Can QR Master track campaigns with UTM?', + answer: 'Absolutely. QR Master supports UTM parameter integration for all dynamic QR codes. Automatically append source, medium, campaign, term, and content parameters to track QR performance in Google Analytics, Adobe Analytics, and other marketing platforms. UTM tracking enables multi-channel attribution and ROI measurement.', + }, + { + question: 'Difference between static and dynamic QR codes?', + answer: 'Static QR codes encode the destination URL directly in the code pattern and cannot be changed after creation. Dynamic QR codes use a short redirect URL, allowing you to update destinations, track scans, enable/disable codes, and gather analytics—all without reprinting. Dynamic codes are essential for professional marketing campaigns.', + }, + { + question: 'How are QR codes used for events?', + answer: 'QR codes streamline event check-ins, ticket validation, attendee tracking, and engagement measurement. Generate unique codes for each ticket, track scan times and locations, enable contactless entry, and analyze attendee behavior. Event organizers use QR analytics to measure session popularity and optimize future events.', + }, + { + question: 'Can I make QR codes for business cards?', + answer: 'Yes. QR codes on business cards provide instant contact sharing via vCard format, link to your portfolio or LinkedIn profile, and track networking effectiveness. Use branded QR codes that match your card design, and leverage scan analytics to see how many contacts engage and when they follow up.', + }, + { + question: 'How do I use QR codes for bulk marketing?', + answer: 'Bulk QR codes enable scalable campaigns across print ads, packaging, direct mail, and retail displays. Generate thousands of codes with unique tracking URLs, distribute them across channels, and use analytics to measure which placements drive the highest engagement. Bulk generation supports CSV upload, API integration, and automated workflows.', + }, + { + question: 'Is API access available for bulk QR generation?', + answer: 'Yes. QR Master offers a developer-friendly REST API for programmatic QR code generation, URL management, and analytics retrieval. Integrate QR creation into your CRM, marketing automation platform, or e-commerce system. API access is included in Business plans and supports bulk operations, webhooks, and real-time updates.', + }, +]; export default function FAQPage() { - const { t } = useTranslation(); - return ( -
- -
+ <> + +
+
+
+
+

+ Frequently Asked Questions +

+

+ Everything you need to know about dynamic QR codes, security, analytics, bulk generation, events, and print quality. +

+
+ +
+ {faqs.map((faq, index) => ( + + +

+ {faq.question} +

+

+ {faq.answer} +

+
+
+ ))} +
+ +
+

+ Still have questions? +

+

+ Our support team is here to help. Contact us at{' '} + + support@qrmaster.com + {' '} + or reach out through our live chat. +

+
+
+
+
+ ); -} \ No newline at end of file +} diff --git a/src/app/(marketing)/layout.tsx b/src/app/(marketing)/layout.tsx index 869a162..16ac5b1 100644 --- a/src/app/(marketing)/layout.tsx +++ b/src/app/(marketing)/layout.tsx @@ -4,7 +4,7 @@ import React, { useState } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { Button } from '@/components/ui/Button'; -import { useTranslation } from '@/hooks/useTranslation'; +import en from '@/i18n/en.json'; export default function MarketingLayout({ children, @@ -12,14 +12,16 @@ export default function MarketingLayout({ children: React.ReactNode; }) { const pathname = usePathname(); - const { t, locale, setLocale } = useTranslation(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + // Always use English for marketing pages + const t = en; + const navigation = [ - { name: t('nav.features'), href: '/#features' }, - { name: t('nav.pricing'), href: '/pricing' }, - { name: t('nav.faq'), href: '/faq' }, - { name: t('nav.blog'), href: '/blog' }, + { name: t.nav.features, href: '/#features' }, + { name: t.nav.pricing, href: '/#pricing' }, + { name: t.nav.faq, href: '/#faq' }, + { name: t.nav.blog, href: '/blog' }, ]; return ( @@ -49,16 +51,8 @@ export default function MarketingLayout({ {/* Right Actions */}
- {/* Language Switcher */} - - - + @@ -96,7 +90,7 @@ export default function MarketingLayout({ ))} setMobileMenuOpen(false)}> - + setMobileMenuOpen(false)}> @@ -128,8 +122,8 @@ export default function MarketingLayout({

Product

  • Features
  • -
  • Pricing
  • -
  • FAQ
  • +
  • Pricing
  • +
  • FAQ
  • Blog
diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index b1860a4..4d7221b 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -1,77 +1,51 @@ -'use client'; - import React from 'react'; -import { Hero } from '@/components/marketing/Hero'; -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 { useTranslation } from '@/hooks/useTranslation'; +import type { Metadata } from 'next'; +import SeoJsonLd from '@/components/SeoJsonLd'; +import { organizationSchema, websiteSchema } from '@/lib/schema'; +import HomePageClient from '@/components/marketing/HomePageClient'; + +function truncateAtWord(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + const truncated = text.slice(0, maxLength); + const lastSpace = truncated.lastIndexOf(' '); + return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated; +} + +export async function generateMetadata(): Promise { + const title = truncateAtWord('QR Master: Dynamic QR Generator', 60); + const description = truncateAtWord( + 'Dynamic QR, branding, bulk generation & analytics for all campaigns.', + 160 + ); + + return { + title, + description, + alternates: { + canonical: 'https://www.qrmaster.com/', + languages: { + 'x-default': 'https://www.qrmaster.com/', + en: 'https://www.qrmaster.com/', + }, + }, + openGraph: { + title, + description, + url: 'https://www.qrmaster.com/', + type: 'website', + }, + twitter: { + title, + description, + }, + }; +} export default function HomePage() { - const { t } = useTranslation(); - - const industries = [ - 'Restaurant Chain', - 'Tech Startup', - 'Real Estate', - 'Event Agency', - 'Retail Store', - 'Healthcare', - ]; - return ( <> - - - - {/* Industry Buttons */} -
-
-
- {industries.map((industry) => ( - - ))} -
-
-
- - - - - - - {/* Pricing Teaser */} -
-
-

- Ready to get started? -

-

- Choose the perfect plan for your needs -

- -
-
- - {/* FAQ Teaser */} -
-
-

- Have questions? -

-

- Check out our frequently asked questions -

- -
-
+ + ); -} \ No newline at end of file +} diff --git a/src/app/(marketing)/pricing/page.tsx b/src/app/(marketing)/pricing/page.tsx new file mode 100644 index 0000000..001ff23 --- /dev/null +++ b/src/app/(marketing)/pricing/page.tsx @@ -0,0 +1,267 @@ +import React from 'react'; +import type { Metadata } from 'next'; +import Link from 'next/link'; +import SeoJsonLd from '@/components/SeoJsonLd'; +import { productSchema } from '@/lib/schema'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; +import { Button } from '@/components/ui/Button'; +import { Badge } from '@/components/ui/Badge'; + +function truncateAtWord(text: string, maxLength: number): string { + if (text.length <= maxLength) return text; + const truncated = text.slice(0, maxLength); + const lastSpace = truncated.lastIndexOf(' '); + return lastSpace > 0 ? truncated.slice(0, lastSpace) : truncated; +} + +export async function generateMetadata(): Promise { + const title = truncateAtWord('QR Master Pricing & Bulk Plans', 60); + const description = truncateAtWord( + 'Choose flexible plans for bulk QR generation, analytics & branding.', + 160 + ); + + return { + title, + description, + alternates: { + canonical: 'https://www.qrmaster.com/pricing', + languages: { + 'x-default': 'https://www.qrmaster.com/pricing', + en: 'https://www.qrmaster.com/pricing', + }, + }, + openGraph: { + title, + description, + url: 'https://www.qrmaster.com/pricing', + type: 'website', + }, + twitter: { + title, + description, + }, + }; +} + +export default function PricingPage() { + const productData = { + name: 'QR Master Plans', + description: 'Flexible pricing for dynamic QR codes, bulk generation, branded designs, and analytics.', + offers: [ + { + name: 'Free Plan', + price: '0', + priceCurrency: 'USD', + availability: 'https://schema.org/InStock', + url: 'https://www.qrmaster.com/pricing', + }, + { + name: 'Pro Plan', + price: '29', + priceCurrency: 'USD', + availability: 'https://schema.org/InStock', + url: 'https://www.qrmaster.com/pricing', + }, + { + name: 'Business Plan', + price: '99', + priceCurrency: 'USD', + availability: 'https://schema.org/InStock', + url: 'https://www.qrmaster.com/pricing', + }, + ], + }; + + const plans = [ + { + name: 'Free', + price: '$0', + interval: '/mo', + description: 'Perfect for personal projects', + features: [ + '10 Dynamic QR Codes', + 'Basic Scan Analytics', + 'Standard QR Design Templates', + 'Community Support', + ], + cta: 'Get Started', + ctaLink: '/signup', + popular: false, + }, + { + name: 'Pro', + price: '$29', + interval: '/mo', + description: 'For professionals and small teams', + features: [ + 'Unlimited Dynamic QR Codes', + 'Advanced Scan Analytics', + 'Branded QR Codes with Custom Colors & Logo', + 'Bulk QR Generation (up to 1,000)', + 'UTM Campaign Tracking', + 'Download as SVG/PNG/PDF', + 'Priority Support', + ], + cta: 'Start Pro Trial', + ctaLink: '/signup?plan=pro', + popular: true, + }, + { + name: 'Business', + price: '$99', + interval: '/mo', + description: 'For agencies and enterprises', + features: [ + 'Everything in Pro', + 'Unlimited Bulk QR Generation', + 'API Access for Automation', + 'White-Label Options', + 'Advanced Security Features', + 'Custom Integrations', + 'Dedicated Account Manager', + 'SLA Guarantee', + ], + cta: 'Contact Sales', + ctaLink: '/signup?plan=business', + popular: false, + }, + ]; + + return ( + <> + +
+
+
+

+ Pricing Plans +

+

+ Choose flexible plans for bulk QR generation, analytics, and branded QR codes. + Start free, upgrade anytime. +

+
+ +
+ {plans.map((plan, index) => ( + + {plan.popular && ( +
+ + Most Popular + +
+ )} + + + {plan.name} +

{plan.description}

+ +
+ {plan.price} + {plan.interval} +
+
+ + +
    + {plan.features.map((feature, fIndex) => ( +
  • + + + + {feature} +
  • + ))} +
+ + + + +
+
+ ))} +
+ + {/* FAQ Section */} +
+

+ Pricing FAQs +

+
+ + +

+ Can I cancel my subscription anytime? +

+

+ Yes, you can cancel your subscription at any time. Your account will remain active + until the end of your current billing period. No refunds for partial months. +

+
+
+ + + +

+ What happens to my QR codes if I downgrade? +

+

+ All your existing QR codes remain active and functional. You won't be able to create + new codes if you exceed your new plan's limit, but existing codes continue working. +

+
+
+ + + +

+ Do you offer annual billing? +

+

+ Yes! Annual billing saves you 17% compared to monthly billing. Contact sales for + custom annual contracts with additional discounts for enterprise customers. +

+
+
+ + + +

+ Is there a free trial for paid plans? +

+

+ Yes, all paid plans include a 14-day free trial. No credit card required to start. + Test all premium features before committing to a paid plan. +

+
+
+
+
+
+
+ + ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9ec7aa0..605d5bb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,24 +3,39 @@ import '@/styles/globals.css'; import { ToastContainer } from '@/components/ui/Toast'; import AuthProvider from '@/components/SessionProvider'; +const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true'; + export const metadata: Metadata = { - title: 'QR Master - Create Custom QR Codes in Seconds', - description: 'Generate static and dynamic QR codes with advanced tracking, professional templates, and seamless integrations.', - keywords: 'QR code, QR generator, dynamic QR, QR tracking, QR analytics', + metadataBase: new URL('https://www.qrmaster.com'), + title: { + default: 'QR Master – Smart QR Generator & Analytics', + template: '%s | QR Master', + }, + description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics.', + keywords: 'QR code, QR generator, dynamic QR, QR tracking, QR analytics, branded QR, bulk QR generator', + robots: isIndexable + ? { index: true, follow: true } + : { index: false, follow: false }, + twitter: { + card: 'summary_large_image', + site: '@qrmaster', + images: ['https://www.qrmaster.com/static/og-image.png'], + }, openGraph: { - title: 'QR Master - Create Custom QR Codes in Seconds', - description: 'Generate static and dynamic QR codes with advanced tracking, professional templates, and seamless integrations.', - url: 'https://qrmaster.com', + type: 'website', siteName: 'QR Master', + title: 'QR Master – Smart QR Generator & Analytics', + description: 'Create dynamic QR codes, track scans, and scale campaigns with secure analytics.', + url: 'https://www.qrmaster.com', images: [ { - url: '/og-image.png', + url: 'https://www.qrmaster.com/static/og-image.png', width: 1200, height: 630, + alt: 'QR Master - Dynamic QR Code Generator and Analytics Platform', }, ], locale: 'en_US', - type: 'website', }, }; diff --git a/src/app/og/route.tsx b/src/app/og/route.tsx new file mode 100644 index 0000000..c595d79 --- /dev/null +++ b/src/app/og/route.tsx @@ -0,0 +1,67 @@ +import { ImageResponse } from 'next/og'; + +export const runtime = 'edge'; + +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const title = searchParams.get('title') || 'QR Master – Smart QR Generator & Analytics'; + + return new ImageResponse( + ( +
+
+

+ {title} +

+

+ Dynamic QR codes with analytics & branding +

+
+
+ ), + { + width: 1200, + height: 630, + } + ); + } catch (e) { + console.error(e); + return new Response('Failed to generate image', { status: 500 }); + } +} diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx new file mode 100644 index 0000000..342830a --- /dev/null +++ b/src/components/Breadcrumbs.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import Link from 'next/link'; + +export interface BreadcrumbItem { + name: string; + url: string; +} + +interface BreadcrumbsProps { + items: BreadcrumbItem[]; +} + +export default function Breadcrumbs({ items }: BreadcrumbsProps) { + return ( + + ); +} + +export { type BreadcrumbItem as BreadcrumbItemType }; diff --git a/src/components/SeoJsonLd.tsx b/src/components/SeoJsonLd.tsx new file mode 100644 index 0000000..cceeb1c --- /dev/null +++ b/src/components/SeoJsonLd.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +interface SeoJsonLdProps { + data: object | object[]; +} + +export default function SeoJsonLd({ data }: SeoJsonLdProps) { + const jsonLdArray = Array.isArray(data) ? data : [data]; + + return ( + <> + {jsonLdArray.map((item, index) => ( +