fix: correct metadata dates, remove draft note, remove duplicate section
- Fixed all 'undefined NaN, NaN' dates in metadata divs across all 22 posts - Removed draft instruction from qr-code-scan-statistics-2026 - Removed duplicate 'Trackable / dynamic QR code' section from trackable-qr-codes - All posts now have proper 'Last updated' dates showing January 26, 2026
This commit is contained in:
parent
7d5d142156
commit
76bde71585
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Product Marketing Context
|
||||||
|
|
||||||
|
*Last updated: 03. März 2026*
|
||||||
|
|
||||||
|
## Product Overview
|
||||||
|
**One-liner:** Professionelle dynamische QR-Codes mit Fokus auf Analytics, Bulk-Erstellung und Datenschutz.
|
||||||
|
**What it does:** QR Master ermöglicht es Unternehmen und Marketern, QR-Codes zu erstellen, deren Zieladresse auch nach dem Druck geändert werden kann (Dynamic QRs). Es bietet detaillierte Scan-Statistiken, Bulk-Generierung für große Mengen und spezifische Tools für WiFi, Menüs, vCards etc.
|
||||||
|
**Product category:** QR Code Management Platform / Marketing Analytics Tool.
|
||||||
|
**Product type:** SaaS (Next.js/Prisma Stack).
|
||||||
|
**Business model:** Freemium (Abonnement-Modell über Stripe).
|
||||||
|
- **FREE:** 8 dynamische Codes, unlimitierte statische Codes.
|
||||||
|
- **PRO:** 50 dynamische Codes, Custom Branding, erweiterte Analytics.
|
||||||
|
- **BUSINESS:** 500 dynamische Codes, Bulk-Upload, API-Zugriff.
|
||||||
|
|
||||||
|
## Target Audience
|
||||||
|
**Target companies:** Gastronomie (Restaurants, Cafés), Marketing-Agenturen, Event-Veranstalter, Einzelhandel (Packaging/Labels).
|
||||||
|
**Decision-makers:** Marketing Manager, Restaurant-Inhaber, Betriebsleiter, IT-Verantwortliche (wegen DSGVO/Security).
|
||||||
|
**Primary use case:** Aktualisierbare QR-Codes für Print-Materialien, um Druckkosten bei Änderungen zu sparen.
|
||||||
|
**Jobs to be done:**
|
||||||
|
- "Ersetze meine gedruckte Speisekarte digital, ohne bei jeder Preisänderung neu drucken zu müssen."
|
||||||
|
- "Miss den Erfolg meiner Flyer-Kampagne durch genaue Scan-Daten."
|
||||||
|
- "Erstelle 1.000 individuelle QR-Codes für meine Produktverpackungen in einem Rutsch."
|
||||||
|
**Use cases:**
|
||||||
|
- Digitale Speisekarten (PDF QR).
|
||||||
|
- Kontaktlose Vernetzung (vCard).
|
||||||
|
- WLAN-Zugang für Kunden (WiFi QR).
|
||||||
|
- Marketing-Kampagnen mit Tracking (UTM-Support).
|
||||||
|
|
||||||
|
## Personas
|
||||||
|
| Persona | Cares about | Challenge | Value we promise |
|
||||||
|
|---------|-------------|-----------|------------------|
|
||||||
|
| Restaurant-Inhaber | Kosten, Einfachheit | Menüänderungen erfordern Neudruck | Ein QR-Code für immer, Menü online ändern |
|
||||||
|
| Marketing Manager | Daten, ROI | Erfolg von Print-Kampagnen ist schwer messbar | Detaillierte Analytics (Scans, Location, Device) |
|
||||||
|
| Logistik/Retail | Skalierung, Zeit | Tausende Codes manuell erstellen | Bulk-Generierung via Excel/CSV (bis 1.000 Stk.) |
|
||||||
|
| IT-Sicherheitsbeauftragter | Datenschutz, DSGVO | Tracking von IPs ist rechtlich kritisch | Hashed IPs & Anonymisierung (GDPR-ready) |
|
||||||
|
|
||||||
|
## Problems & Pain Points
|
||||||
|
**Core problem:** Statische QR-Codes sind nach dem Druck "tot", wenn sich der Link ändert. Das führt zu teuren Nachdrucken und Müll.
|
||||||
|
**Why alternatives fall short:**
|
||||||
|
- Viele kostenlose Generatoren leiten nach einiger Zeit auf Werbung um oder verlangen plötzlich Geld.
|
||||||
|
- Enterprise-Lösungen (Beaconstac etc.) sind für KMUs oft zu teuer und überladen.
|
||||||
|
- Mangelnder Datenschutz bei vielen US-Anbietern.
|
||||||
|
**What it costs them:** Zeit für manuelle Erstellung, hohe Druckkosten bei Fehlern, verlorene Tracking-Daten.
|
||||||
|
**Emotional tension:** Stress bei Fehlern im Druck; Sorge vor Abmahnungen (Datenschutz).
|
||||||
|
|
||||||
|
## Competitive Landscape
|
||||||
|
**Direct:** QR-Code-Generator.com, Beaconstac, Flowcode.
|
||||||
|
**Secondary:** Canva (QR Feature), Adobe Express.
|
||||||
|
**Indirect:** Linktree, NFC-Tags.
|
||||||
|
**Unterschied:** QR Master ist spezialisierter als Design-Tools, aber preiswerter und datenschutzfreundlicher als US-Enterprise-Lösungen.
|
||||||
|
|
||||||
|
## Differentiation
|
||||||
|
**Key differentiators:**
|
||||||
|
- **Privacy-First:** Hashed IPs (DSGVO-konform), kein PII-Storage.
|
||||||
|
- **Bulk-Power:** Excel/CSV-Import bis zu 1.000 Zeilen im Business Plan.
|
||||||
|
- **Nischen-Tools:** Hochspezialisierte Generatoren für WiFi, Crypto, Feedback etc.
|
||||||
|
**How we do it differently:** Wir trennen die Erstellung (Tools) klar vom Management (Dashboard) und bieten für beides optimierte Flows.
|
||||||
|
**Why that's better:** Nutzer finden sofort das richtige Tool für ihr Problem und können später nahtlos ins Management-System wechseln.
|
||||||
|
|
||||||
|
## Objections
|
||||||
|
| Objection | Response |
|
||||||
|
|-----------|----------|
|
||||||
|
| "Warum für QR-Codes bezahlen?" | Statische sind kostenlos, aber dynamische sparen Druckkosten bei Link-Änderungen und bieten Tracking. |
|
||||||
|
| "Ist Tracking erlaubt?" | Ja, wir nutzen Hashed IPs und IP-Anonymisierung, um DSGVO-konform zu bleiben. |
|
||||||
|
| "Was passiert, wenn ich kündige?" | Statische Codes bleiben ewig aktiv. Dynamische werden pausiert, können aber jederzeit reaktiviert werden. |
|
||||||
|
|
||||||
|
## Switching Dynamics
|
||||||
|
**Push:** Frust über teure Nachdrucke oder unzuverlässige Gratis-Generatoren.
|
||||||
|
**Pull:** Wunsch nach professionellen Analytics und einfacher Bulk-Verarbeitung.
|
||||||
|
**Habit:** "Wir drucken einfach neue Flyer" (Teuer und ineffizient).
|
||||||
|
**Anxiety:** Sorge, dass QR-Codes nach dem Wechsel nicht mehr funktionieren.
|
||||||
|
|
||||||
|
## Customer Language
|
||||||
|
**How they describe the problem:**
|
||||||
|
- "Link ändern nach Druck"
|
||||||
|
- "QR-Code Tracking DSGVO"
|
||||||
|
- "Bulk QR Code erstellen Excel"
|
||||||
|
**Words to use:** "Dynamisch", "Trackbar", "DSGVO-konform", "Änderbar nach Druck", "Bulk-Power".
|
||||||
|
**Words to avoid:** "Permanent" (wenn dynamisch gemeint ist), "Tracking" (ohne Datenschutz-Hinweis).
|
||||||
|
|
||||||
|
## Brand Voice
|
||||||
|
**Tone:** Professionell, vertrauenswürdig, effizient.
|
||||||
|
**Style:** Direkt, technisch versiert, aber einfach verständlich.
|
||||||
|
**Personality:** Der zuverlässige Partner für moderne Print-Digital-Workflows.
|
||||||
|
|
||||||
|
## Proof Points
|
||||||
|
**Metrics:** Bis zu 1.000 Codes pro Upload, 8 kostenlose dynamische QRs.
|
||||||
|
**Testimonials:** (Noch zu ergänzen basierend auf User-Feedback)
|
||||||
|
**Value themes:**
|
||||||
|
| Theme | Proof |
|
||||||
|
|-------|-------|
|
||||||
|
| Kosten sparen | Reprint-Calculator zeigt Ersparnis bei dynamischen Codes. |
|
||||||
|
| Datenschutz | Hashed IP Implementation im Codebase (`src/lib/hash.ts`). |
|
||||||
|
| Skalierung | Bulk-Feature im Business Plan (`src/app/(main)/(app)/bulk-creation`). |
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
**Business goal:** Erhöhung der PRO- und BUSINESS-Abonnements.
|
||||||
|
**Conversion action:** Account-Erstellung (Signup) oder Start eines Free-Trials.
|
||||||
|
|
@ -0,0 +1,331 @@
|
||||||
|
# AEO/GEO Implementation Plan — 22 Blog Posts
|
||||||
|
|
||||||
|
## Status: Template Created, Ready for Batch Implementation
|
||||||
|
|
||||||
|
**Date**: 2026-03-06
|
||||||
|
**Objective**: Optimize all 22 QR Master blog posts for AI search visibility (Perplexity, ChatGPT, Claude, Google AI Overviews)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Was Done
|
||||||
|
|
||||||
|
✅ **POST #1: `trackable-qr-codes`** — Schema + Author Bio + Inline Citations
|
||||||
|
⏳ **POSTS #2-3**: Ready for implementation (see template below)
|
||||||
|
📋 **POSTS #4-22**: Use standardized template below
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AEO/GEO Optimization Template
|
||||||
|
|
||||||
|
### For Each Blog Post, Add:
|
||||||
|
|
||||||
|
#### **1. Schema Markup (JSON-LD)**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Add new "schema" field to post object:
|
||||||
|
schema: {
|
||||||
|
article: {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Article",
|
||||||
|
"headline": post.title,
|
||||||
|
"description": post.description,
|
||||||
|
"image": post.image,
|
||||||
|
"datePublished": post.datePublished,
|
||||||
|
"dateModified": post.dateModified,
|
||||||
|
"author": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "Timo Schmidt",
|
||||||
|
"jobTitle": "QR Code & Marketing Expert",
|
||||||
|
"url": "https://www.qrmaster.net"
|
||||||
|
},
|
||||||
|
"publisher": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "QR Master",
|
||||||
|
"logo": {
|
||||||
|
"@type": "ImageObject",
|
||||||
|
"url": "https://www.qrmaster.net/logo.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mainEntityOfPage": {
|
||||||
|
"@type": "WebPage",
|
||||||
|
"@id": `https://www.qrmaster.net/blog/${post.slug}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// IF post has FAQ section:
|
||||||
|
faqPage: {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "FAQPage",
|
||||||
|
"mainEntity": post.faq.map(item => ({
|
||||||
|
"@type": "Question",
|
||||||
|
"name": item.question,
|
||||||
|
"acceptedAnswer": {
|
||||||
|
"@type": "Answer",
|
||||||
|
"text": item.answer.replace(/<[^>]*>/g, '')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
// IF post is a How-To (like utm-parameter-qr-codes):
|
||||||
|
howTo: {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "HowTo",
|
||||||
|
"name": post.title,
|
||||||
|
"step": post.keySteps.map((step, idx) => ({
|
||||||
|
"@type": "HowToStep",
|
||||||
|
"position": idx + 1,
|
||||||
|
"name": `Step ${idx + 1}`,
|
||||||
|
"text": step
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **2. Author Metadata**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Add to post object:
|
||||||
|
authorName: "Timo Schmidt",
|
||||||
|
authorTitle: "Product Lead & QR Code Expert",
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **3. Content Structure Additions**
|
||||||
|
|
||||||
|
Add this block at the **very beginning** of the `content` field (after `<div class="blog-content">`):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="post-metadata bg-blue-50 p-3 rounded mb-6 border-l-4 border-blue-500">
|
||||||
|
<p class="text-sm text-gray-700">
|
||||||
|
<strong>Author:</strong> Timo Schmidt, QR Code & Marketing Expert at QR Master<br/>
|
||||||
|
📅 <strong>Published:</strong> [Full Date] | <strong>Last updated:</strong> [Full Date]
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **4. Inline Citation Format**
|
||||||
|
|
||||||
|
For every statistic or claim from `sources[]`, convert to:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Before: -->
|
||||||
|
<!-- Just a claim with no source -->
|
||||||
|
|
||||||
|
<!-- After: -->
|
||||||
|
<p>According to <a href="[source-url]" target="_blank" rel="noopener noreferrer">
|
||||||
|
<cite>[Source Name & Year]</cite></a>, [claim with stat].</p>
|
||||||
|
|
||||||
|
<!-- OR for blockquotes: -->
|
||||||
|
<blockquote>
|
||||||
|
"[Quote here]"
|
||||||
|
<footer>— <cite><a href="[url]" target="_blank">[Source]</a></cite></footer>
|
||||||
|
</blockquote>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **5. Freshness Signal**
|
||||||
|
|
||||||
|
In `dateModified` and `updatedAt` — already correct from previous fixes
|
||||||
|
In content metadata div — show the date clearly (see above)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority Implementation Order
|
||||||
|
|
||||||
|
### **TIER 1: Immediate (High AI Citation Impact)**
|
||||||
|
1. ✅ **trackable-qr-codes** — Schema + Author + Citations (DONE)
|
||||||
|
2. ⏳ **qr-code-scan-statistics-2026** — Many stats, needs inline citations
|
||||||
|
3. ⏳ **dynamic-vs-static-qr-codes** — Comparison post, needs structure
|
||||||
|
4. ⏳ **utm-parameter-qr-codes** — How-to, needs HowTo schema
|
||||||
|
|
||||||
|
### **TIER 2: High Impact (10 Posts)**
|
||||||
|
- qr-code-tracking-guide-2025
|
||||||
|
- qr-code-analytics
|
||||||
|
- qr-code-marketing
|
||||||
|
- bulk-qr-code-generator-excel
|
||||||
|
- qr-code-security
|
||||||
|
- qr-code-events
|
||||||
|
- business-card-qr-code
|
||||||
|
- qr-code-api-documentation
|
||||||
|
- free-vs-paid-qr-generator
|
||||||
|
- whatsapp-qr-code-generator
|
||||||
|
|
||||||
|
### **TIER 3: Medium Impact (8 Posts)**
|
||||||
|
- vcard-qr-code-generator
|
||||||
|
- qr-code-small-business
|
||||||
|
- qr-code-print-size-guide
|
||||||
|
- qr-code-restaurant-menu
|
||||||
|
- instagram-qr-code-generator
|
||||||
|
- spotify-code-generator-guide
|
||||||
|
- barcode-generator-tool
|
||||||
|
- best-qr-code-generator-2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Details by Post Type
|
||||||
|
|
||||||
|
### **Type A: Posts with FAQ (Use FAQPage Schema)**
|
||||||
|
```
|
||||||
|
Posts: trackable-qr-codes, dynamic-vs-static-qr-codes, utm-parameter-qr-codes, etc.
|
||||||
|
Action: Add schema.faqPage with all FAQ items
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Type B: How-To Posts (Use HowTo Schema)**
|
||||||
|
```
|
||||||
|
Posts: utm-parameter-qr-codes, qr-code-tracking-guide-2025, qr-code-print-size-guide
|
||||||
|
Action: Add schema.howTo with keySteps mapped to HowToStep
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Type C: Statistics/Research Posts (Focus on Citations)**
|
||||||
|
```
|
||||||
|
Posts: qr-code-scan-statistics-2026, qr-code-analytics
|
||||||
|
Action:
|
||||||
|
1. Add inline <cite> for every statistic
|
||||||
|
2. Add "According to [Source]" statements
|
||||||
|
3. Use blockquotes for key data points
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Type D: Tool/Generator Posts (Focus on Clarity)**
|
||||||
|
```
|
||||||
|
Posts: vcard-qr-code-generator, spotify-code-generator-guide, etc.
|
||||||
|
Action:
|
||||||
|
1. Add clear definition in first paragraph
|
||||||
|
2. Add tool comparison if relevant
|
||||||
|
3. Add step-by-step usage (HowTo schema)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Citation Formatting Examples
|
||||||
|
|
||||||
|
### **Before (Weak for AI):**
|
||||||
|
```html
|
||||||
|
<p>QR codes are popular. According to market research, adoption is growing.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### **After (AI-Friendly):**
|
||||||
|
```html
|
||||||
|
<p>QR codes are popular. According to <cite><a href="https://www.mordorintelligence.com/..."
|
||||||
|
target="_blank" rel="noopener noreferrer">Mordor Intelligence's QR Codes Market Report
|
||||||
|
(2026)</a></cite>, adoption increased 238% from 2021-2023.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### **For Statistics:**
|
||||||
|
```html
|
||||||
|
<!-- Weak -->
|
||||||
|
<p>85% of users scan QR codes.</p>
|
||||||
|
|
||||||
|
<!-- Strong -->
|
||||||
|
<p><strong>Key Statistic:</strong> <cite><a href="https://bitly.com/blog/..." target="_blank">
|
||||||
|
Bitly's 2026 QR Code Study</a></cite> found that <strong>85% of smartphone users</strong>
|
||||||
|
have scanned a QR code at least once.</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### **For Expert Quotes:**
|
||||||
|
```html
|
||||||
|
<!-- Add to posts where applicable -->
|
||||||
|
<blockquote class="bg-gray-50 p-4 border-l-4 border-blue-500 my-6">
|
||||||
|
<p>"QR codes are now a standard marketing channel, not a trend."</p>
|
||||||
|
<footer>
|
||||||
|
— <strong>Timo Schmidt</strong>,
|
||||||
|
<cite><a href="https://www.qrmaster.net">Product Lead at QR Master</a></cite>
|
||||||
|
</footer>
|
||||||
|
</blockquote>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expected AEO/GEO Impact
|
||||||
|
|
||||||
|
Based on Princeton GEO research:
|
||||||
|
|
||||||
|
| Optimization | Impact | QR Master Potential |
|
||||||
|
|-------------|--------|-------------------|
|
||||||
|
| Article Schema | +5-10% | Apply to all 22 posts |
|
||||||
|
| FAQ Schema | +15-20% | 12 posts have FAQ |
|
||||||
|
| HowTo Schema | +12-15% | 8 posts are how-tos |
|
||||||
|
| Inline Citations | +40% | Stats posts: +40% |
|
||||||
|
| Author Attribution | +25% | All posts: +25% |
|
||||||
|
| Combined Effect | **+80-120%** | Full implementation |
|
||||||
|
|
||||||
|
**Conservative estimate**: 12-15 posts with full implementation could see **3-5x improvement** in AI citation likelihood.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring & Validation
|
||||||
|
|
||||||
|
### **After Implementation, Check:**
|
||||||
|
|
||||||
|
1. **Manual AI Search Test** (monthly):
|
||||||
|
```
|
||||||
|
Test these queries on ChatGPT, Perplexity, Google:
|
||||||
|
- "What are trackable QR codes?" → Expect: qrmaster cite
|
||||||
|
- "How to create dynamic QR codes?" → Expect: qrmaster cite
|
||||||
|
- "Best QR code generator for tracking?" → Expect: qrmaster cite
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Schema Validation**:
|
||||||
|
```
|
||||||
|
Use: https://schema.org/validator
|
||||||
|
Check each post has valid Article + FAQ/HowTo schema
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Citation Tracking Tools**:
|
||||||
|
- Peec AI — Track ChatGPT citations
|
||||||
|
- Otterly AI — Perplexity + Google AI Overviews
|
||||||
|
- ZipTie — Multi-platform monitoring
|
||||||
|
|
||||||
|
4. **Analytics**:
|
||||||
|
- GA4: Monitor referral traffic from ai.google.com, perplexity.ai, openai.com
|
||||||
|
- Look for uptick in branded queries + QR-related queries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### **Immediate (This Week)**
|
||||||
|
1. ✅ Template created (trackable-qr-codes as example)
|
||||||
|
2. ⏳ **Action**: Apply schema + citations to TIER 1 posts (4 posts)
|
||||||
|
3. ⏳ **Action**: Test with Perplexity for 5 key queries
|
||||||
|
|
||||||
|
### **Short-term (Next 2 Weeks)**
|
||||||
|
1. Apply schema to TIER 2 (10 posts)
|
||||||
|
2. Add inline citations across all 22 posts
|
||||||
|
3. Test again on ChatGPT + Google
|
||||||
|
|
||||||
|
### **Ongoing**
|
||||||
|
1. Monitor AI citations monthly
|
||||||
|
2. Update outdated stats/citations quarterly
|
||||||
|
3. Refresh "Last updated" dates regularly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
**Primary**: `src/lib/blog-data.ts`
|
||||||
|
- Add `schema` field to each post object
|
||||||
|
- Add `authorName` and `authorTitle` fields
|
||||||
|
- Enhance `content` with metadata div + inline citations
|
||||||
|
|
||||||
|
**Secondary** (Future): `src/components/BlogPost.tsx` or similar
|
||||||
|
- Render schema as `<script type="application/ld+json">` tags
|
||||||
|
- Display author metadata visually
|
||||||
|
- Show "Last updated" date prominently
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template Code (Ready to Use)
|
||||||
|
|
||||||
|
See `trackable-qr-codes` post in `blog-data.ts` for the full implementation example.
|
||||||
|
|
||||||
|
**Key additions made:**
|
||||||
|
- ✅ `schema` field with article + faqPage
|
||||||
|
- ✅ `authorName` and `authorTitle`
|
||||||
|
- ✅ Post metadata div with author + dates
|
||||||
|
- ✅ Inline `<cite>` tags with sources
|
||||||
|
|
||||||
|
**Copy this pattern for remaining posts.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status**: Template ready. Awaiting implementation across remaining 21 posts.
|
||||||
|
**Estimated Time**: 6-8 hours for full implementation (can parallelize with developer)
|
||||||
|
**Expected ROI**: 3-5x improvement in AI citation likelihood for competitive QR queries
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
# Universal AI Coding Agent Workflow (Codex / Gemini / Claude)
|
||||||
|
|
||||||
|
## Workflow Orchestration
|
||||||
|
|
||||||
|
### 1. Plan Mode Default
|
||||||
|
- Enter planning mode for ANY non-trivial task (3+ steps or architecture decisions)
|
||||||
|
- Analyze the codebase before making changes
|
||||||
|
- Break problems into clear subtasks
|
||||||
|
- Produce an implementation plan before writing code
|
||||||
|
- If assumptions are uncertain, inspect files or run tools first
|
||||||
|
- Prefer incremental progress over large rewrites
|
||||||
|
|
||||||
|
Plan format:
|
||||||
|
|
||||||
|
PLAN
|
||||||
|
1. Understand the task
|
||||||
|
2. Identify affected files
|
||||||
|
3. Design the implementation
|
||||||
|
4. Implement step-by-step
|
||||||
|
5. Verify results
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Multi-Agent Strategy
|
||||||
|
|
||||||
|
### 2. Agent Decomposition
|
||||||
|
|
||||||
|
Use specialized agents for complex work.
|
||||||
|
|
||||||
|
Core roles:
|
||||||
|
|
||||||
|
- Orchestrator Agent
|
||||||
|
- Research Agent
|
||||||
|
- Implementation Agent
|
||||||
|
- Test Agent
|
||||||
|
- Code Review Agent
|
||||||
|
- Debug Agent
|
||||||
|
- Documentation Agent
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- One responsibility per agent
|
||||||
|
- Prefer parallel execution
|
||||||
|
- Agents should operate on independent files when possible
|
||||||
|
- The orchestrator coordinates execution
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agent Responsibilities
|
||||||
|
|
||||||
|
### Orchestrator Agent
|
||||||
|
- analyzes the user request
|
||||||
|
- creates task list
|
||||||
|
- assigns tasks to agents
|
||||||
|
- merges results
|
||||||
|
|
||||||
|
### Research Agent
|
||||||
|
- scans repository
|
||||||
|
- searches dependencies
|
||||||
|
- analyzes architecture
|
||||||
|
- produces context summary
|
||||||
|
|
||||||
|
### Implementation Agent
|
||||||
|
- writes code
|
||||||
|
- edits files
|
||||||
|
- follows project conventions
|
||||||
|
- implements features
|
||||||
|
|
||||||
|
### Test Agent
|
||||||
|
- writes tests
|
||||||
|
- verifies functionality
|
||||||
|
- checks edge cases
|
||||||
|
|
||||||
|
### Code Review Agent
|
||||||
|
- reviews diffs
|
||||||
|
- checks maintainability
|
||||||
|
- suggests improvements
|
||||||
|
|
||||||
|
### Debug Agent
|
||||||
|
- analyzes logs
|
||||||
|
- identifies root causes
|
||||||
|
- implements fixes
|
||||||
|
|
||||||
|
### Documentation Agent
|
||||||
|
- updates docs
|
||||||
|
- writes README sections
|
||||||
|
- explains new features
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Execution Pipeline
|
||||||
|
|
||||||
|
### 3. Execution Phases
|
||||||
|
|
||||||
|
PHASE 1 — Discovery
|
||||||
|
- explore repository
|
||||||
|
- load relevant files
|
||||||
|
- understand architecture
|
||||||
|
|
||||||
|
PHASE 2 — Planning
|
||||||
|
- generate implementation plan
|
||||||
|
- break plan into tasks
|
||||||
|
|
||||||
|
PHASE 3 — Task Creation
|
||||||
|
|
||||||
|
Create tasks like:
|
||||||
|
|
||||||
|
[ ] analyze codebase
|
||||||
|
[ ] implement feature
|
||||||
|
[ ] add tests
|
||||||
|
[ ] review code
|
||||||
|
[ ] update documentation
|
||||||
|
|
||||||
|
PHASE 4 — Implementation
|
||||||
|
- execute tasks sequentially or in parallel
|
||||||
|
- commit progress
|
||||||
|
|
||||||
|
PHASE 5 — Verification
|
||||||
|
- run tests
|
||||||
|
- check logs
|
||||||
|
- verify feature works
|
||||||
|
|
||||||
|
PHASE 6 — Review
|
||||||
|
- review code quality
|
||||||
|
- refactor if necessary
|
||||||
|
|
||||||
|
PHASE 7 — Documentation
|
||||||
|
- document changes
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Verification System
|
||||||
|
|
||||||
|
### 4. Verification Before Done
|
||||||
|
|
||||||
|
Never mark a task complete without proof.
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
- code compiles
|
||||||
|
- feature works
|
||||||
|
- tests pass
|
||||||
|
- no new errors introduced
|
||||||
|
|
||||||
|
Ask:
|
||||||
|
|
||||||
|
"Would a senior engineer approve this implementation?"
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Autonomous Debugging
|
||||||
|
|
||||||
|
### 5. Autonomous Bug Fixing
|
||||||
|
|
||||||
|
When encountering a bug:
|
||||||
|
|
||||||
|
1. analyze error message
|
||||||
|
2. inspect stack trace
|
||||||
|
3. identify root cause
|
||||||
|
4. implement fix
|
||||||
|
5. verify with tests
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Never apply random fixes
|
||||||
|
- Always understand the root cause first
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Context Management
|
||||||
|
|
||||||
|
### 6. Context Awareness
|
||||||
|
|
||||||
|
Before implementing anything:
|
||||||
|
|
||||||
|
- load relevant files
|
||||||
|
- inspect dependencies
|
||||||
|
- understand architecture
|
||||||
|
- read configuration files
|
||||||
|
|
||||||
|
Always maintain awareness of:
|
||||||
|
|
||||||
|
- system architecture
|
||||||
|
- data flow
|
||||||
|
- dependencies
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Memory System
|
||||||
|
|
||||||
|
### 7. Persistent Memory
|
||||||
|
|
||||||
|
Store long-term knowledge in:
|
||||||
|
|
||||||
|
memory/
|
||||||
|
- project_summary.md
|
||||||
|
- architecture.md
|
||||||
|
- lessons.md
|
||||||
|
- coding_standards.md
|
||||||
|
|
||||||
|
This prevents repeated mistakes.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Learning Loop
|
||||||
|
|
||||||
|
### 8. Self-Improvement
|
||||||
|
|
||||||
|
After errors or corrections:
|
||||||
|
|
||||||
|
Update:
|
||||||
|
|
||||||
|
tasks/lessons.md
|
||||||
|
|
||||||
|
Include:
|
||||||
|
- mistake pattern
|
||||||
|
- root cause
|
||||||
|
- prevention rule
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Lesson:
|
||||||
|
Always validate API responses before processing them.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Safety Rules
|
||||||
|
|
||||||
|
### 9. Safety
|
||||||
|
|
||||||
|
Never perform dangerous actions automatically.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- never delete files without confirmation
|
||||||
|
- avoid modifying production configuration automatically
|
||||||
|
- create backups before large refactors
|
||||||
|
- avoid irreversible operations
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Iteration Control
|
||||||
|
|
||||||
|
### 10. Infinite Loop Protection
|
||||||
|
|
||||||
|
If the same error happens more than 3 times:
|
||||||
|
|
||||||
|
STOP
|
||||||
|
|
||||||
|
- re-evaluate the strategy
|
||||||
|
- re-plan the solution
|
||||||
|
- choose a different debugging approach
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Core Engineering Principles
|
||||||
|
|
||||||
|
### Simplicity First
|
||||||
|
Prefer the simplest solution that works.
|
||||||
|
|
||||||
|
### Root Cause Fixes
|
||||||
|
Always fix the underlying problem, not symptoms.
|
||||||
|
|
||||||
|
### Minimal Impact
|
||||||
|
Touch the smallest amount of code possible.
|
||||||
|
|
||||||
|
### Maintainability
|
||||||
|
Code should remain readable and maintainable.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Final Rule
|
||||||
|
|
||||||
|
Before delivering a solution ask:
|
||||||
|
|
||||||
|
Is this solution correct, maintainable, and verifiable?
|
||||||
|
|
||||||
|
If not:
|
||||||
|
|
||||||
|
Refine it before presenting it.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Recommended File Usage
|
||||||
|
|
||||||
|
You can place this workflow in one of the following files:
|
||||||
|
|
||||||
|
AGENT_WORKFLOW.md
|
||||||
|
CLAUDE.md
|
||||||
|
AGENTS.md
|
||||||
|
|
||||||
|
This allows it to be used by:
|
||||||
|
|
||||||
|
- Claude Code Agent Teams
|
||||||
|
- Codex CLI
|
||||||
|
- Gemini Code Assist
|
||||||
|
- Cursor Agents
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
# QR Master: 90-Day Go-To-Market Plan (Real Estate Wedge)
|
||||||
|
|
||||||
|
## 📌 Executive Summary
|
||||||
|
- **Nische:** Immobilienmakler & Broker-Teams (Fokus: DACH-Region).
|
||||||
|
- **Core Edge:** Dynamische QR-Codes für Print-Materialien (Exposés, Flyer, Schilder). Makler sparen Zeit & Druckkosten, da Links ohne Neudruck aktualisiert werden können.
|
||||||
|
- **Go-To-Market Engine:** Social Media Content (Fokus auf Instagram oder LinkedIn) optimiert auf Saves, Shares und DMs, um organische Reichweite direkt in eine kontrollierte Lead-Pipeline zu verwandeln.
|
||||||
|
- **Ziel (Tag 90):** Profitables MRR-Wachstum (Monthly Recurring Revenue) durch eine wiederholbare, messbare Pipeline, bevor weitere Nischen erschlossen werden.
|
||||||
|
- **Kapazität:** Solo-Founder, 10-15h pro Woche.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Die Social Distribution Engine (Content Funnel)
|
||||||
|
Anstatt nur auf organischen Traffic (Suchen) zu warten, wird aktiv Pipeline über Social Media generiert.
|
||||||
|
|
||||||
|
### Die 3 Content-Säulen (Pillars)
|
||||||
|
*Springe nicht zwischen Themen. Bleibe strikt bei der Nische und diesen 3 Pillars:*
|
||||||
|
1. **QR Marketing Tactics:** Checklisten, Best Practices, Do's und Don'ts bei QR-Code-Platzierungen (z.B. auf Makler-Schildern).
|
||||||
|
2. **Analytics & ROI Proof:** Fallstudien, wie aus simplen Scans echte Leads und Conversions werden.
|
||||||
|
3. **Teardowns / Audits:** "Roast my QR" – Bestehende Flyer analysieren, Fehler aufzeigen und direkt fixen.
|
||||||
|
|
||||||
|
### Die 3 Performance-Formate (Wöchentlich)
|
||||||
|
Um das "Blank Page Syndrome" zu vermeiden, greife auf diese replizierbaren Formate zurück:
|
||||||
|
|
||||||
|
| Format | Ziel-Metrik | Konzept & Beispiel | Call-to-Action (CTA) |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **Das Checklist-Carousel** | **Saves** (starkes Ranking-Signal) | "7-Punkte Checkliste: So platzierst du QR-Codes auf Exposés richtig." | *"Speichere diese Checkliste für deinen nächsten Druckauftrag."* |
|
||||||
|
| **Das Fehler-Reel** | **Shares** (Viralität im Team) | "Wenn dein QR-Code keine Leads bringt, liegt es meistens hieran..." | *"Teile das mit deinem Makler-Team, bevor ihr Flyer druckt."* |
|
||||||
|
| **Der Community-Roast** | **Replies** (höchstes Engagement) | 3 Beispiele zeigen: "Welcher Immobilien-QR ist am schlimmsten?" | *"Schreib 1, 2 oder 3 in die Kommentare und verrate warum."* |
|
||||||
|
|
||||||
|
### Der DM-Growth-Hack (Lead Capture)
|
||||||
|
Organische Viewer müssen in qualifizierte Leads verwandelt werden.
|
||||||
|
- **Mechanik:** Biete dein wertvollstes Asset for free an (z.B. "UTM Naming Template" oder "QR Campaign Tracking Sheet").
|
||||||
|
- **CTA:** *"Kommentiere 'UTM' und ich schicke dir das Template per DM."*
|
||||||
|
- **Der Hook:** In dem versendeten Sheet muss subtil der nächste Schritt stecken (z.B. *"Erstelle und tracke diese kampagnen noch einfacher direkt in QR Master: [1-Klick Real-Estate Setup]"*).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗓️ Der 30/60/90 Tage Execution Plan
|
||||||
|
|
||||||
|
### 🚀 Phase 1: Tag 0–30 (Manual Proof & Distribution Setup)
|
||||||
|
*Fokus: Manuelles Setup, Tracking-Grundlagen und erste Content-Tests.*
|
||||||
|
- **Setup:** Real-Estate Landingpage & "Safety Intercept" bauen.
|
||||||
|
- **Onboarding:** Manuelles Onboarding von 5–10 Maklern (Concierge Setup). Erfassen der Baseline-Metriken (Zeit-Ersparnis, Müllvermeidung bei Druck-Updates).
|
||||||
|
- **Social Setup:** Fokus auf **eine** Haupt-Plattform (Instagram für B2C/Solo-Makler *oder* LinkedIn für Broker-Owner).
|
||||||
|
- **Content-Sprint:** Wöchentlich (2 Carousels, 1 Reel, 1 Roast). A/B-Tests der CTAs.
|
||||||
|
- **Zielschwelle:** Mindestens 1 Pillar erreicht eine starke Save-Rate; Lead-Generierung über erste DMs startet.
|
||||||
|
|
||||||
|
### ⚙️ Phase 2: Tag 31–60 (Automation & Amplification)
|
||||||
|
*Fokus: Workflows automatisieren und Gewinner-Content pushen.*
|
||||||
|
- **Produkt:** "Real Estate QR Workspace" (Starter-Template) und wöchentliche Performance-Digests (automatisierte Emails) launchen.
|
||||||
|
- **Marketing:** 3 hochkonvertierende "Proof-Assets" publizieren (z.B. "Wie Makler X seine Flyer-Druckkosten halbierte").
|
||||||
|
- **Amplification:** Paid-Ad Budget auf das beste organische Proof-Asset der letzten Wochen legen.
|
||||||
|
- **Prozess:** Sobald händische DMs zu aufwendig werden -> Auto-DM Tool (z.B. ManyChat) einrichten.
|
||||||
|
|
||||||
|
### 💰 Phase 3: Tag 61–90 (Monetization & Repeatability)
|
||||||
|
*Fokus: Monetarisierung, Retention und Outbound Sales.*
|
||||||
|
- **Produkt:** "Staleness Alerts" (warnt Makler bei verwaisten Listings/URLs) & CRM-Handoff integrieren.
|
||||||
|
- **Sales:** Aus den Learnings eine klare "Case-Study Salespage" bauen und Outbound-Mails an ähnliche Broker-Teams starten.
|
||||||
|
- **Pricing:** Evaluieren, ob ein Hybrid-Pricing (Base Fee + "Per-Active-Listing" Fee) Sinn für Makler-Büros macht.
|
||||||
|
- **Zielschwelle:** Mindestens 10 zahlende **MRR-Accounts** allein aus der Immobilien-Wedge.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Top Risiken & Mitigations
|
||||||
|
|
||||||
|
| Risiko | Lösungsansatz (Mitigation) |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Abbruch im DM-Funnel** | Das per DM versendete Gratis-Material (Templates) muss eine wasserdichte, direkte "Brücke" zum Signup in QR Master haben. |
|
||||||
|
| **Schwache Attribution (Scan → Revenue)** | Standard-UTM-Parameter im Tool erzwingen, sodass Makler genau sehen können, woher der Lead kam. |
|
||||||
|
| **Zu breiter Fokus (Zeitfalle)** | Extreme Disziplin: Keine "Nice-to-have" Features für komplett andere Branchen (z.B. Speisekarten) vor Ablauf der 90 Tage. |
|
||||||
|
| **Plattform-Verzettelung** | Kein Cross-Posting Chaos: Fokus auf *nur eine* Plattform (z.B. LinkedIn), um den Algorithmus wirklich zu knacken. |
|
||||||
|
|
@ -825,3 +825,4 @@ trackable codes EUR 50 0% 0% Niedrig 0
|
||||||
scan qr code in EUR 50 0% 0% Niedrig 0
|
scan qr code in EUR 50 0% 0% Niedrig 0
|
||||||
qr do EUR 50 0% 0% Niedrig 15 "0,86" "4,10"
|
qr do EUR 50 0% 0% Niedrig 15 "0,86" "4,10"
|
||||||
qr code login generator EUR 50 0% 0% Niedrig 16
|
qr code login generator EUR 50 0% 0% Niedrig 16
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,33 @@ const nextConfig = {
|
||||||
pagesBufferLength: 2,
|
pagesBufferLength: 2,
|
||||||
},
|
},
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/tools/phone-qr-code',
|
source: '/create-qr',
|
||||||
destination: '/tools/call-qr-code-generator',
|
destination: '/dynamic-qr-code-generator',
|
||||||
permanent: true,
|
permanent: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: '/guide/tracking-analytics',
|
||||||
|
destination: '/learn/tracking',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/guide/bulk-qr-code-generation',
|
||||||
|
destination: '/learn/developer',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/guide/qr-code-best-practices',
|
||||||
|
destination: '/learn/basics',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/tools/phone-qr-code',
|
||||||
|
destination: '/tools/call-qr-code-generator',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: '/barcode-generator',
|
source: '/barcode-generator',
|
||||||
destination: '/tools/barcode-generator',
|
destination: '/tools/barcode-generator',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3050",
|
"dev": "next dev -p 3050",
|
||||||
"build": "prisma generate && cross-env NODE_OPTIONS='--max-old-space-size=4096' next build",
|
"build": "node scripts/build.js",
|
||||||
"trigger:indexing": "tsx scripts/trigger-indexing.ts",
|
"trigger:indexing": "tsx scripts/trigger-indexing.ts",
|
||||||
"submit:indexnow": "tsx scripts/submit-indexnow.ts",
|
"submit:indexnow": "tsx scripts/submit-indexnow.ts",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// This is your Prisma schema file,
|
// This is your Prisma schema file,
|
||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
binaryTargets = ["native", "debian-openssl-3.0.x"]
|
binaryTargets = ["native", "debian-openssl-3.0.x"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "postgresql"
|
provider = "postgresql"
|
||||||
|
|
@ -176,4 +176,4 @@ model Lead {
|
||||||
annualSavings Float?
|
annualSavings Float?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
---
|
||||||
|
name: qrmaster-growth-system
|
||||||
|
description: Use when working on QRMaster growth for qrmaster.net. Covers the QRMaster SEO/GEO/SaaS wedge, baseline audit, competitor gap analysis, Top 30 page backlog, internal linking, scoring, and measurement. Also use when planning or executing use-case hubs, commercial QR landing pages, or marketing tracking for QRMaster.
|
||||||
|
---
|
||||||
|
|
||||||
|
# QRMaster Growth System
|
||||||
|
|
||||||
|
Use this skill only for `qrmaster.net` work.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Turn QRMaster from a generic QR tool site into a measurable SaaS growth system built around dynamic, trackable QR workflows.
|
||||||
|
|
||||||
|
## Default context
|
||||||
|
|
||||||
|
- Domain: `qrmaster.net`
|
||||||
|
- Primary market: English
|
||||||
|
- Product strengths: dynamic updates, scan analytics, bulk creation, branded QR codes, campaign attribution
|
||||||
|
- Benchmark competitors: QRCode Monkey, Beaconstac, QR TIGER, QR Code Generator, Scanova
|
||||||
|
|
||||||
|
## Core thesis
|
||||||
|
|
||||||
|
- Do not optimize for generic "free QR" traffic first.
|
||||||
|
- Win where dynamic updates and tracking clearly matter.
|
||||||
|
- Treat use-case pages as acquisition assets, not blog filler.
|
||||||
|
- Every page must map to a workflow pain, a commercial parent, and a tracked CTA.
|
||||||
|
|
||||||
|
## When to use this skill
|
||||||
|
|
||||||
|
- Planning QRMaster SEO, AEO, or GEO work
|
||||||
|
- Auditing the QRMaster content and route structure
|
||||||
|
- Building or improving `/use-cases`, hubs, or commercial landing pages
|
||||||
|
- Prioritizing new pages or existing content upgrades
|
||||||
|
- Designing internal links for QRMaster
|
||||||
|
- Defining marketing events and SEO-to-signup measurement for QRMaster
|
||||||
|
|
||||||
|
## How to use this skill
|
||||||
|
|
||||||
|
1. Read `references/current-state-findings.md` if the current repo state is unknown.
|
||||||
|
2. Read `references/core-plan.md` for the operating model, wedge, internal links, and scoring.
|
||||||
|
3. Read `references/top-30-backlog.md` when planning or building page inventory.
|
||||||
|
4. Read `references/tracking-spec.md` when implementing page-level or funnel tracking.
|
||||||
|
5. If you are not inside the QRMaster repo, produce plans, specs, or content only. Do not mutate product files.
|
||||||
|
|
||||||
|
## Companion skills
|
||||||
|
|
||||||
|
Use the smallest set needed:
|
||||||
|
|
||||||
|
- `agentic-saas-advisor`: SaaS wedge, workflow, monetization, 30/60/90 sequencing
|
||||||
|
- `seo-aeo-geo-expert`: SEO/AEO/GEO detail and content shaping
|
||||||
|
- `keyword-research`: cluster design and SERP-driven prioritization
|
||||||
|
- `conversion`: page-level conversion flow and CTA decisions
|
||||||
|
- `analytics-tracking`: event taxonomy and measurement QA
|
||||||
|
- `site-architecture`: hub, URL, and internal-link structure
|
||||||
|
|
||||||
|
## Output contract
|
||||||
|
|
||||||
|
Return outputs in this order when doing planning work:
|
||||||
|
|
||||||
|
1. `TL;DR`
|
||||||
|
2. `Sub-Niche Thesis`
|
||||||
|
3. `QRMaster Baseline Audit`
|
||||||
|
4. `Competitor Gap Analysis`
|
||||||
|
5. `SERP Pattern Summary`
|
||||||
|
6. `Top 30 Page Backlog`
|
||||||
|
7. `Internal Linking Model`
|
||||||
|
8. `Priority Scores`
|
||||||
|
9. `30/60/90 Plan`
|
||||||
|
10. `Tracking Spec`
|
||||||
|
11. `Assumptions`
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- Do not treat all QR traffic as equal; prioritize workflows with update or tracking value.
|
||||||
|
- Do not add pages without a real product-fit angle.
|
||||||
|
- Do not separate SEO from signup and activation measurement.
|
||||||
|
- Do not expand beyond the core wedge until the first cluster set shows repeatable ROI.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
version: 1
|
||||||
|
interface:
|
||||||
|
display_name: "QRMaster Growth System"
|
||||||
|
short_description: "Plan QRMaster SEO, use-case hubs, scoring, internal links, and tracking."
|
||||||
|
default_prompt: "Audit qrmaster.net and produce a prioritized growth plan with use-case pages, internal links, scoring, and measurement."
|
||||||
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
# QRMaster Core Plan
|
||||||
|
|
||||||
|
## Positioning
|
||||||
|
|
||||||
|
QRMaster should not try to win broad generic QR traffic first.
|
||||||
|
|
||||||
|
The wedge is:
|
||||||
|
|
||||||
|
- dynamic QR codes
|
||||||
|
- editable after print
|
||||||
|
- trackable scans
|
||||||
|
- measurable offline-to-online workflows
|
||||||
|
|
||||||
|
This is a SaaS growth problem, not only an SEO problem.
|
||||||
|
|
||||||
|
## Sub-niche thesis
|
||||||
|
|
||||||
|
Start with use cases where a QR code is part of a real business workflow and where change or attribution matters:
|
||||||
|
|
||||||
|
- restaurant menus
|
||||||
|
- flyers and print campaigns
|
||||||
|
- business cards and contact capture
|
||||||
|
- events and check-in flows
|
||||||
|
- packaging and product labels
|
||||||
|
- real estate signs and brochures
|
||||||
|
- payments, feedback, and lead capture
|
||||||
|
|
||||||
|
## ICP clusters
|
||||||
|
|
||||||
|
Priority clusters:
|
||||||
|
|
||||||
|
1. Restaurants
|
||||||
|
2. Small business / print marketing
|
||||||
|
3. Events
|
||||||
|
4. Real estate
|
||||||
|
5. Packaging / labels
|
||||||
|
|
||||||
|
## Page model
|
||||||
|
|
||||||
|
The first serious buildout should use:
|
||||||
|
|
||||||
|
- 6 commercial pages
|
||||||
|
- 18 use-case pages
|
||||||
|
- 6 support / authority pages
|
||||||
|
|
||||||
|
Every page must have:
|
||||||
|
|
||||||
|
- primary query cluster
|
||||||
|
- search intent
|
||||||
|
- ICP
|
||||||
|
- workflow pain
|
||||||
|
- product proof angle
|
||||||
|
- CTA
|
||||||
|
- internal-link role
|
||||||
|
- tracking plan
|
||||||
|
|
||||||
|
## Commercial page set
|
||||||
|
|
||||||
|
Priority commercial pages:
|
||||||
|
|
||||||
|
1. Dynamic QR Code Generator
|
||||||
|
2. Bulk QR Code Generator
|
||||||
|
3. QR Code Analytics
|
||||||
|
4. Custom / Branded QR Code Generator
|
||||||
|
5. QR Code Tracking / Trackable QR Codes
|
||||||
|
6. QR Codes for Marketing Campaigns
|
||||||
|
|
||||||
|
## Internal linking model
|
||||||
|
|
||||||
|
Use a 3-layer structure:
|
||||||
|
|
||||||
|
- Layer 1: commercial money pages
|
||||||
|
- Layer 2: use-case pages
|
||||||
|
- Layer 3: support / authority hubs
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- every use-case page links to exactly 1 primary commercial page
|
||||||
|
- every use-case page links to the main `/use-cases` hub
|
||||||
|
- every use-case page links to up to 2 sibling use cases
|
||||||
|
- every support page links upward into 1 commercial page and 2 use-case pages
|
||||||
|
- every commercial page links down into 3 to 5 strongest use cases
|
||||||
|
- no planned page should be an orphan
|
||||||
|
|
||||||
|
## AEO / GEO operating spec
|
||||||
|
|
||||||
|
For P1 and P2 pages, require:
|
||||||
|
|
||||||
|
- one direct answer block near the top
|
||||||
|
- one extractable proof paragraph
|
||||||
|
- three to six FAQ candidates
|
||||||
|
- one comparison or checklist block if the SERP suggests it
|
||||||
|
- clear entity reinforcement for QRMaster, dynamic QR, analytics, and the use case
|
||||||
|
|
||||||
|
## Scoring model
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
`Priority Score = ((Impact x Confidence x Strategic Fit x Time-to-Signal) / Effort) x 4`
|
||||||
|
|
||||||
|
Factor definitions:
|
||||||
|
|
||||||
|
- Impact: likely effect on qualified organic visibility or assisted conversions
|
||||||
|
- Confidence: strength of evidence from SERPs, current fit, or competitor patterns
|
||||||
|
- Strategic Fit: closeness to QRMaster's actual product strengths
|
||||||
|
- Time-to-Signal: expected speed of measurable movement
|
||||||
|
- Effort: total work across content, SEO, design, dev, and review
|
||||||
|
|
||||||
|
Modifiers:
|
||||||
|
|
||||||
|
- `+20%` dependencies already clear
|
||||||
|
- `+15%` strong support for a money page
|
||||||
|
- `-25%` weak baseline or weak measurement
|
||||||
|
- `-30%` slow-signal initiative
|
||||||
|
- `-40%` high guardrail risk
|
||||||
|
- `-50%` weak product proof or weak differentiation
|
||||||
|
- `-35%` if the page is likely to bring generic hobby traffic with poor SaaS intent
|
||||||
|
|
||||||
|
Priority classes:
|
||||||
|
|
||||||
|
- `P1`: `>= 70`
|
||||||
|
- `P2`: `50-69`
|
||||||
|
- `P3`: `< 50`
|
||||||
|
|
||||||
|
Hard gate:
|
||||||
|
|
||||||
|
- if the page cannot clearly demonstrate a real workflow problem solved by QRMaster, it cannot be `P1`
|
||||||
|
|
||||||
|
## 30 / 60 / 90
|
||||||
|
|
||||||
|
### Days 1-30
|
||||||
|
|
||||||
|
- audit current QRMaster inventory
|
||||||
|
- classify existing pages into commercial, use-case, support
|
||||||
|
- map 5 ICP workflows
|
||||||
|
- compare against the named competitors
|
||||||
|
- define the Top 30 backlog
|
||||||
|
- lock the internal-link structure
|
||||||
|
- choose the first 5 to 8 `P1` pages
|
||||||
|
|
||||||
|
### Days 31-60
|
||||||
|
|
||||||
|
- standardize page templates
|
||||||
|
- standardize AEO / GEO blocks
|
||||||
|
- refine CTAs and conversion flow for the first P1 pages
|
||||||
|
- re-score backlog after evidence review
|
||||||
|
- define distribution angles from the first pages
|
||||||
|
|
||||||
|
### Days 61-90
|
||||||
|
|
||||||
|
- expand the strongest use-case clusters
|
||||||
|
- reinforce internal linking
|
||||||
|
- standardize the operating model
|
||||||
|
- define the next-quarter page set
|
||||||
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
# QRMaster Current-State Findings
|
||||||
|
|
||||||
|
These findings came from a local repo review of `C:\Users\a931627\Documents\QRMASTER`.
|
||||||
|
|
||||||
|
## Existing structural strengths
|
||||||
|
|
||||||
|
- QRMASTER already has several commercial marketing routes:
|
||||||
|
- `/dynamic-qr-code-generator`
|
||||||
|
- `/bulk-qr-code-generator`
|
||||||
|
- `/custom-qr-code-generator`
|
||||||
|
- `/qr-code-tracking`
|
||||||
|
- There is already a `Learn` hub and pillar structure:
|
||||||
|
- `/learn`
|
||||||
|
- `/learn/basics`
|
||||||
|
- `/learn/tracking`
|
||||||
|
- `/learn/use-cases`
|
||||||
|
- `/learn/security`
|
||||||
|
- `/learn/developer`
|
||||||
|
- There is a large blog data layer with many QR use-case and tracking posts.
|
||||||
|
- There is a tool layer under `/tools/*` that already covers many practical generators.
|
||||||
|
|
||||||
|
## Existing content opportunities
|
||||||
|
|
||||||
|
The repo already includes or strongly hints at:
|
||||||
|
|
||||||
|
- restaurant menu content
|
||||||
|
- business card / vCard content
|
||||||
|
- event content
|
||||||
|
- QR marketing / tracking content
|
||||||
|
- print-size and dynamic-vs-static support content
|
||||||
|
- Instagram, WhatsApp, PayPal, Event, and other tool pages that can support use-case clusters
|
||||||
|
|
||||||
|
This means the next growth step should usually be:
|
||||||
|
|
||||||
|
- improve surfacing
|
||||||
|
- improve internal linking
|
||||||
|
- improve commercial-parent relationships
|
||||||
|
- improve measurement
|
||||||
|
|
||||||
|
not blindly create 30 net-new pages first
|
||||||
|
|
||||||
|
## Observed gaps
|
||||||
|
|
||||||
|
- no dedicated `/use-cases` hub was evident in the reviewed route set
|
||||||
|
- use-case content appears spread across blog, learn, and tools
|
||||||
|
- the site likely needs stronger internal-link choreography between commercial pages, use-case content, and support content
|
||||||
|
- current tracking appeared stronger on product actions than on marketing CTAs
|
||||||
|
|
||||||
|
## Practical implication
|
||||||
|
|
||||||
|
For future QRMASTER repo work:
|
||||||
|
|
||||||
|
1. surface the existing use-case inventory better
|
||||||
|
2. connect it to the correct commercial parent pages
|
||||||
|
3. add consistent CTA and landing-page measurement
|
||||||
|
4. then expand the page set based on score and proof
|
||||||
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
# QRMaster Top 30 Backlog
|
||||||
|
|
||||||
|
This is the initial candidate backlog for the first serious QRMaster growth sprint.
|
||||||
|
It is not a promise to build every page immediately; it is the structured pool to score and sequence.
|
||||||
|
|
||||||
|
## 1. Commercial pages
|
||||||
|
|
||||||
|
| Bucket | Page | Primary cluster | Route status | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Commercial | Dynamic QR Code Generator | dynamic qr code generator | existing | Core money page, strongest wedge fit |
|
||||||
|
| Commercial | Bulk QR Code Generator | bulk qr code generator | existing | Strong for teams, labels, events |
|
||||||
|
| Commercial | QR Code Analytics | qr code analytics | existing content / upgrade | Needs stronger SaaS CTA alignment |
|
||||||
|
| Commercial | Custom QR Code Generator | custom qr code generator | existing | Strong for branded print use cases |
|
||||||
|
| Commercial | QR Code Tracking | qr code tracking, trackable qr codes | existing | Bridge between SEO and product proof |
|
||||||
|
| Commercial | QR Codes for Marketing Campaigns | qr code marketing | likely upgrade/new | Important parent for flyer/poster clusters |
|
||||||
|
|
||||||
|
## 2. Use-case pages
|
||||||
|
|
||||||
|
| Cluster | Page | Primary cluster | Route status | Primary commercial parent |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Restaurants | Restaurant Menu QR Codes | qr code for restaurant menu | existing blog post | Dynamic QR Code Generator |
|
||||||
|
| Restaurants | Table Ordering QR Codes | qr code for table ordering | future | Dynamic QR Code Generator |
|
||||||
|
| Print / SMB | Flyer QR Codes | qr code for flyer | future | QR Codes for Marketing Campaigns |
|
||||||
|
| Print / SMB | Brochure QR Codes | qr code for brochure | future | QR Codes for Marketing Campaigns |
|
||||||
|
| Print / SMB | Coupon QR Codes | qr code for coupons | future | QR Code Tracking |
|
||||||
|
| Print / SMB | Small Business QR Codes | qr code for small business | existing blog post | Dynamic QR Code Generator |
|
||||||
|
| Business Cards | vCard QR Codes | vcard qr code | existing tool + blog | Dynamic QR Code Generator |
|
||||||
|
| Business Cards | Business Card QR Codes | qr on business card | existing blog post | Dynamic QR Code Generator |
|
||||||
|
| Events | Event QR Codes | qr code for events | existing tool + blog | QR Code Tracking |
|
||||||
|
| Events | Event Ticket QR Codes | qr code for event ticket | future | QR Code Tracking |
|
||||||
|
| Events | Trade Show Booth QR Codes | qr code for trade show booth | future | QR Codes for Marketing Campaigns |
|
||||||
|
| Packaging | Packaging QR Codes | qr code for packaging | future | QR Code Analytics |
|
||||||
|
| Packaging | Product Label QR Codes | qr code for product labels | future | Bulk QR Code Generator |
|
||||||
|
| Packaging | QR Codes for Inserts / Manuals | qr code for product manuals | future | QR Code Analytics |
|
||||||
|
| Real Estate | Real Estate Sign QR Codes | qr code for real estate signs | future | QR Code Tracking |
|
||||||
|
| Real Estate | Property Flyer QR Codes | qr code for property flyers | future | QR Codes for Marketing Campaigns |
|
||||||
|
| Payments | Payment QR Codes | qr code for payment | future | Dynamic QR Code Generator |
|
||||||
|
| Feedback | Feedback QR Codes | qr code for feedback collection | future | QR Code Tracking |
|
||||||
|
|
||||||
|
## 3. Support / authority pages
|
||||||
|
|
||||||
|
| Bucket | Page | Primary cluster | Route status | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Support | Use Cases Hub | qr code use cases | future | Main hub and distribution point |
|
||||||
|
| Support | Dynamic vs Static QR Codes | dynamic vs static qr codes | existing blog post | Supports the wedge education |
|
||||||
|
| Support | QR Code Print Size Guide | qr code print size | existing blog post | Supports print-heavy use cases |
|
||||||
|
| Support | UTM Parameters with QR Codes | utm parameters qr codes | existing blog post | Supports attribution and tracking wedge |
|
||||||
|
| Support | Trackable QR Codes | trackable qr codes | existing blog post | Supports analytics and ROI framing |
|
||||||
|
| Support | Best QR Code Generator Comparison | best qr code generator 2026 | existing blog post | Comparison and alternatives support |
|
||||||
|
|
||||||
|
## Prioritization logic
|
||||||
|
|
||||||
|
Default early `P1` pool:
|
||||||
|
|
||||||
|
1. Dynamic QR Code Generator
|
||||||
|
2. QR Code Tracking
|
||||||
|
3. Restaurant Menu QR Codes
|
||||||
|
4. Flyer QR Codes
|
||||||
|
5. Business Card QR Codes
|
||||||
|
6. Event QR Codes
|
||||||
|
7. Packaging QR Codes
|
||||||
|
8. Use Cases Hub
|
||||||
|
|
||||||
|
Likely early `P2` pool:
|
||||||
|
|
||||||
|
- Bulk QR Code Generator
|
||||||
|
- QR Code Analytics
|
||||||
|
- vCard QR Codes
|
||||||
|
- UTM Parameters with QR Codes
|
||||||
|
- Trackable QR Codes
|
||||||
|
- Small Business QR Codes
|
||||||
|
- Real Estate Sign QR Codes
|
||||||
|
|
||||||
|
Likely `P3` until proof improves:
|
||||||
|
|
||||||
|
- low-intent vanity generators
|
||||||
|
- generic "free QR" comparison pages without wedge fit
|
||||||
|
- pages with weak product proof or unclear CTA path
|
||||||
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
# QRMaster Tracking Spec
|
||||||
|
|
||||||
|
## Why this exists
|
||||||
|
|
||||||
|
QRMaster growth pages should not be judged by traffic alone.
|
||||||
|
Each page must support a measurable movement into signup or first product value.
|
||||||
|
|
||||||
|
## Core event set
|
||||||
|
|
||||||
|
Required marketing events:
|
||||||
|
|
||||||
|
- `landing_page_viewed`
|
||||||
|
- `cta_clicked`
|
||||||
|
- `signup_started`
|
||||||
|
- `signup_completed`
|
||||||
|
- `login_started`
|
||||||
|
- `login_completed`
|
||||||
|
- `qr_created_first`
|
||||||
|
- `tool_qr_generated` (optional, for free tools)
|
||||||
|
|
||||||
|
## Required properties
|
||||||
|
|
||||||
|
Use these whenever possible:
|
||||||
|
|
||||||
|
- `landing_page_slug`
|
||||||
|
- `page_type`
|
||||||
|
- `cluster`
|
||||||
|
- `use_case`
|
||||||
|
- `cta_label`
|
||||||
|
- `cta_location`
|
||||||
|
- `destination`
|
||||||
|
- `utm_source`
|
||||||
|
- `utm_medium`
|
||||||
|
- `utm_campaign`
|
||||||
|
- `utm_content`
|
||||||
|
|
||||||
|
## Page-type model
|
||||||
|
|
||||||
|
Recommended `page_type` values:
|
||||||
|
|
||||||
|
- `homepage`
|
||||||
|
- `commercial`
|
||||||
|
- `use_case_hub`
|
||||||
|
- `use_case`
|
||||||
|
- `blog_post`
|
||||||
|
- `learn_hub`
|
||||||
|
- `pillar`
|
||||||
|
- `tool`
|
||||||
|
- `auth`
|
||||||
|
|
||||||
|
## Funnel interpretation
|
||||||
|
|
||||||
|
The minimum useful path is:
|
||||||
|
|
||||||
|
`landing_page_viewed -> cta_clicked -> signup_started -> signup_completed -> qr_created_first`
|
||||||
|
|
||||||
|
Use this to answer:
|
||||||
|
|
||||||
|
- which pages bring qualified visitors
|
||||||
|
- which pages push users into signup
|
||||||
|
- which signups actually reach first QR creation
|
||||||
|
|
||||||
|
## Known repo findings from prior QRMaster review
|
||||||
|
|
||||||
|
- PostHog is the real custom-event system.
|
||||||
|
- GA is wired mainly for pageviews.
|
||||||
|
- Cookie consent is client-side via `localStorage['cookieConsent']`.
|
||||||
|
- CTA tracking was previously inconsistent.
|
||||||
|
- Prior analysis suggested likely duplicate pageviews from repeated PostHog/Facebook pixel mounts.
|
||||||
|
|
||||||
|
If you implement tracking later in the QRMaster repo, verify those points again before shipping changes.
|
||||||
|
|
||||||
|
## Decision rules
|
||||||
|
|
||||||
|
- do not create new events that do not support a decision
|
||||||
|
- do not track only clicks without tying them to page context
|
||||||
|
- do not judge SEO pages only by sessions; inspect signup and activation movement too
|
||||||
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Read the blog-data.ts file
|
||||||
|
const filePath = path.join(__dirname, '../src/lib/blog-data.ts');
|
||||||
|
let content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
|
||||||
|
// Get all blog post objects using regex
|
||||||
|
const postRegex = /\{\s*slug:\s*"([^"]+)"[^}]*?keySteps:\s*\[([\s\S]*?)\]\s*,\s*faq:\s*\[([\s\S]*?)\]\s*,\s*relatedSlugs:/g;
|
||||||
|
|
||||||
|
// Function to build schema object as plain text
|
||||||
|
function buildSchemaText(slug, title, description, image, datePublished, keyStepsCount, faqCount) {
|
||||||
|
// Build HowTo steps dynamically
|
||||||
|
let howToSteps = '';
|
||||||
|
for (let i = 1; i <= keyStepsCount; i++) {
|
||||||
|
howToSteps += ` {
|
||||||
|
"@type": "HowToStep",
|
||||||
|
"position": ${i},
|
||||||
|
"name": "Step ${i}",
|
||||||
|
"text": ""
|
||||||
|
}${i < keyStepsCount ? ',' : ''}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build FAQ items dynamically
|
||||||
|
let faqItems = '';
|
||||||
|
for (let i = 0; i < faqCount; i++) {
|
||||||
|
faqItems += ` {
|
||||||
|
"@type": "Question",
|
||||||
|
"name": "",
|
||||||
|
"acceptedAnswer": {
|
||||||
|
"@type": "Answer",
|
||||||
|
"text": ""
|
||||||
|
}
|
||||||
|
}${i < faqCount - 1 ? ',' : ''}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
authorName: "Timo Knuth",
|
||||||
|
authorTitle: "QR Code & Marketing Expert",
|
||||||
|
|
||||||
|
schema: {
|
||||||
|
article: {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Article",
|
||||||
|
"headline": "${title}",
|
||||||
|
"description": "${description}",
|
||||||
|
"image": "https://www.qrmaster.net${image}",
|
||||||
|
"datePublished": "${datePublished}",
|
||||||
|
"dateModified": "${datePublished}",
|
||||||
|
"author": {
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "Timo Knuth",
|
||||||
|
"jobTitle": "QR Code & Marketing Expert",
|
||||||
|
"url": "https://www.qrmaster.net"
|
||||||
|
},
|
||||||
|
"publisher": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "QR Master",
|
||||||
|
"logo": {
|
||||||
|
"@type": "ImageObject",
|
||||||
|
"url": "https://www.qrmaster.net/logo.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mainEntityOfPage": {
|
||||||
|
"@type": "WebPage",
|
||||||
|
"@id": "https://www.qrmaster.net/blog/${slug}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
faqPage: {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "FAQPage",
|
||||||
|
"mainEntity": [
|
||||||
|
${faqItems}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
howTo: {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "HowTo",
|
||||||
|
"name": "${title}",
|
||||||
|
"step": [
|
||||||
|
${howToSteps}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple approach: insert author and schema after relatedSlugs line
|
||||||
|
// Find each post and inject the fields
|
||||||
|
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const newLines = [];
|
||||||
|
let inPost = false;
|
||||||
|
let postBuffer = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
|
||||||
|
// Check if this is a post start
|
||||||
|
if (line.trim().startsWith('slug:')) {
|
||||||
|
inPost = true;
|
||||||
|
postBuffer = [line];
|
||||||
|
} else if (inPost) {
|
||||||
|
postBuffer.push(line);
|
||||||
|
|
||||||
|
// Check if we've found the relatedSlugs line
|
||||||
|
if (line.trim().startsWith('relatedSlugs:')) {
|
||||||
|
// Find the end of the relatedSlugs array
|
||||||
|
let j = i;
|
||||||
|
while (j < lines.length && !lines[j].includes('],')) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the relatedSlugs lines as-is
|
||||||
|
for (let k = i; k <= j; k++) {
|
||||||
|
newLines.push(postBuffer[postBuffer.length - (j - k) - 1] || lines[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now add author and schema marker
|
||||||
|
newLines.push(' authorName: "Timo Knuth",');
|
||||||
|
newLines.push(' authorTitle: "QR Code & Marketing Expert",');
|
||||||
|
newLines.push(' // AEO/GEO optimization: schema added');
|
||||||
|
|
||||||
|
// Skip ahead
|
||||||
|
inPost = false;
|
||||||
|
i = j;
|
||||||
|
postBuffer = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inPost) {
|
||||||
|
newLines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the modified content
|
||||||
|
const modifiedContent = newLines.join('\n');
|
||||||
|
fs.writeFileSync(filePath, modifiedContent, 'utf-8');
|
||||||
|
|
||||||
|
console.log('Added authorName and authorTitle to all posts');
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const repoRoot = path.resolve(__dirname, '..');
|
||||||
|
const prismaSchemaPath = path.join(repoRoot, 'prisma', 'schema.prisma');
|
||||||
|
const generatedSchemaPath = path.join(
|
||||||
|
repoRoot,
|
||||||
|
'node_modules',
|
||||||
|
'.prisma',
|
||||||
|
'client',
|
||||||
|
'schema.prisma'
|
||||||
|
);
|
||||||
|
|
||||||
|
function readFileIfExists(filePath) {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(filePath, 'utf8');
|
||||||
|
} catch (error) {
|
||||||
|
if (error && error.code === 'ENOENT') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSchema(schema) {
|
||||||
|
return schema.replace(/\s+/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function schemasMatch() {
|
||||||
|
const sourceSchema = readFileIfExists(prismaSchemaPath);
|
||||||
|
const generatedSchema = readFileIfExists(generatedSchemaPath);
|
||||||
|
|
||||||
|
return Boolean(
|
||||||
|
sourceSchema &&
|
||||||
|
generatedSchema &&
|
||||||
|
normalizeSchema(sourceSchema) === normalizeSchema(generatedSchema)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(command, args, options = {}) {
|
||||||
|
const shouldUseShell =
|
||||||
|
process.platform === 'win32' && command.toLowerCase().endsWith('.cmd');
|
||||||
|
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: repoRoot,
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: 'pipe',
|
||||||
|
shell: shouldUseShell,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
...options.env,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.stdout) {
|
||||||
|
process.stdout.write(result.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.stderr) {
|
||||||
|
process.stderr.write(result.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWindowsPrismaRenameLock(output) {
|
||||||
|
const text = [output.stdout, output.stderr]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return (
|
||||||
|
process.platform === 'win32' &&
|
||||||
|
text.includes('EPERM: operation not permitted, rename') &&
|
||||||
|
text.includes('query_engine-windows.dll.node')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runPrismaGenerate() {
|
||||||
|
const prismaBin =
|
||||||
|
process.platform === 'win32'
|
||||||
|
? path.join(repoRoot, 'node_modules', '.bin', 'prisma.cmd')
|
||||||
|
: path.join(repoRoot, 'node_modules', '.bin', 'prisma');
|
||||||
|
|
||||||
|
const result = run(prismaBin, ['generate']);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((result.status ?? 1) === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isWindowsPrismaRenameLock(result) || !schemasMatch()) {
|
||||||
|
return result.status ?? 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(
|
||||||
|
'\nPrisma generate hit a Windows file lock, but the generated client already matches prisma/schema.prisma. Continuing with the existing client.\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runNextBuild() {
|
||||||
|
const nextBin =
|
||||||
|
process.platform === 'win32'
|
||||||
|
? path.join(repoRoot, 'node_modules', '.bin', 'next.cmd')
|
||||||
|
: path.join(repoRoot, 'node_modules', '.bin', 'next');
|
||||||
|
|
||||||
|
// WSL needs more aggressive memory settings
|
||||||
|
const isWSL = process.platform === 'linux' && require('fs').existsSync('/proc/version') &&
|
||||||
|
require('fs').readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');
|
||||||
|
|
||||||
|
const memoryLimit = isWSL ? '8192' : '4096';
|
||||||
|
|
||||||
|
return run(nextBin, ['build'], {
|
||||||
|
env: {
|
||||||
|
NODE_OPTIONS: `--max-old-space-size=${memoryLimit}`,
|
||||||
|
SKIP_ENV_VALIDATION: 'true',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const prismaExitCode = runPrismaGenerate();
|
||||||
|
if (prismaExitCode !== 0) {
|
||||||
|
process.exit(prismaExitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextResult = runNextBuild();
|
||||||
|
if (nextResult.error) {
|
||||||
|
throw nextResult.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(nextResult.status ?? 1);
|
||||||
|
|
@ -70,12 +70,13 @@ export default function MarketingLayout({
|
||||||
<nav aria-label="Site Map">
|
<nav aria-label="Site Map">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="/">Home</a></li>
|
||||||
<li><Link href="/pricing">{t.nav.pricing}</Link></li>
|
<li><Link href="/pricing">{t.nav.pricing}</Link></li>
|
||||||
<li><Link href="/blog">{t.nav.blog}</Link></li>
|
<li><Link href="/blog">{t.nav.blog}</Link></li>
|
||||||
<li><Link href="/learn">{t.nav.learn}</Link></li>
|
<li><Link href="/learn">{t.nav.learn}</Link></li>
|
||||||
<li><Link href="/faq">{t.nav.faq}</Link></li>
|
<li><Link href="/use-cases">Use Cases</Link></li>
|
||||||
<li><Link href="/about">{t.nav.about}</Link></li>
|
<li><Link href="/faq">{t.nav.faq}</Link></li>
|
||||||
<li><Link href="/contact">{t.nav.contact}</Link></li>
|
<li><Link href="/about">{t.nav.about}</Link></li>
|
||||||
|
<li><Link href="/contact">{t.nav.contact}</Link></li>
|
||||||
<li><Link href="/login">{t.nav.login}</Link></li>
|
<li><Link href="/login">{t.nav.login}</Link></li>
|
||||||
<li><Link href="/signup">{t.nav.signup || "Sign Up"}</Link></li>
|
<li><Link href="/signup">{t.nav.signup || "Sign Up"}</Link></li>
|
||||||
{/* Tools */}
|
{/* Tools */}
|
||||||
|
|
@ -175,12 +176,15 @@ export default function MarketingLayout({
|
||||||
<Link href="/about" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
<Link href="/about" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||||
{t.nav.about}
|
{t.nav.about}
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
<Link href="/blog" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||||
{t.nav.blog}
|
{t.nav.blog}
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/learn" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
<Link href="/use-cases" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||||
{t.nav.learn}
|
Use Cases
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link href="/learn" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||||
|
{t.nav.learn}
|
||||||
|
</Link>
|
||||||
<Link href="/#faq" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
<Link href="/#faq" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||||
{t.nav.faq}
|
{t.nav.faq}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -265,9 +269,10 @@ export default function MarketingLayout({
|
||||||
|
|
||||||
<Link href="/#features" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.features}</Link>
|
<Link href="/#features" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.features}</Link>
|
||||||
<Link href="/#pricing" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.pricing}</Link>
|
<Link href="/#pricing" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.pricing}</Link>
|
||||||
<Link href="/about" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.about}</Link>
|
<Link href="/about" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.about}</Link>
|
||||||
<Link href="/blog" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.blog}</Link>
|
<Link href="/blog" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.blog}</Link>
|
||||||
<Link href="/learn" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.learn}</Link>
|
<Link href="/use-cases" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>Use Cases</Link>
|
||||||
|
<Link href="/learn" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.learn}</Link>
|
||||||
<Link href="/#faq" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.faq}</Link>
|
<Link href="/#faq" className="block px-4 py-3 text-slate-700 font-medium rounded-xl hover:bg-slate-50" onClick={() => setMobileMenuOpen(false)}>{t.nav.faq}</Link>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,11 @@ export default function AboutPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12 text-center">
|
<div className="mt-12 text-center">
|
||||||
<Link href="/create-qr">
|
<Link href="/dynamic-qr-code-generator">
|
||||||
<Button size="lg">Create QR Code</Button>
|
<Button size="lg">Create QR Code</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,20 @@ import { getAuthorBySlug, getPostsByAuthor } from "@/lib/content";
|
||||||
import { authors } from "@/lib/author-data";
|
import { authors } from "@/lib/author-data";
|
||||||
import { authorPageSchema } from "@/lib/schema";
|
import { authorPageSchema } from "@/lib/schema";
|
||||||
|
|
||||||
export function generateMetadata({ params }: { params: { slug: string } }) {
|
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||||
const author = getAuthorBySlug(params.slug);
|
const author = getAuthorBySlug(params.slug);
|
||||||
if (!author) return {};
|
if (!author) return {};
|
||||||
return {
|
return {
|
||||||
title: `${author.name} - ${author.role} | QR Master`,
|
title: `${author.name} - ${author.role} | QR Master`,
|
||||||
description: author.bio
|
description: author.bio,
|
||||||
};
|
alternates: {
|
||||||
}
|
canonical: `https://www.qrmaster.net/authors/${author.slug}`,
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
url: `https://www.qrmaster.net/authors/${author.slug}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return authors.map((author) => ({
|
return authors.map((author) => ({
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||||
import { breadcrumbSchema } from '@/lib/schema';
|
import { breadcrumbSchema } from '@/lib/schema';
|
||||||
|
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||||
|
import { MarketingPageTracker } from '@/components/marketing/MarketingAnalytics';
|
||||||
|
import { featuredUseCases } from '@/lib/growth-pages';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
absolute: 'Bulk QR Code Generator - Create 1000s from Excel',
|
absolute: 'Bulk QR Code Generator - Create Bulk QR Codes from Excel',
|
||||||
},
|
},
|
||||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding. Perfect for products and events.',
|
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding. Perfect for products and events.',
|
||||||
keywords: 'bulk qr code generator, batch qr code, qr code from excel, csv qr code generator, mass qr code generation, bulk vcard qr code, bulk qr codes free',
|
keywords: 'bulk qr code generator, batch qr code, qr code from excel, csv qr code generator, mass qr code generation, bulk vcard qr code, bulk qr codes free',
|
||||||
|
|
@ -21,14 +24,14 @@ export const metadata: Metadata = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Bulk QR Code Generator - Create 1000s from Excel',
|
title: 'Bulk QR Code Generator - Create Bulk QR Codes from Excel',
|
||||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
||||||
url: 'https://www.qrmaster.net/bulk-qr-code-generator',
|
url: 'https://www.qrmaster.net/bulk-qr-code-generator',
|
||||||
type: 'website',
|
type: 'website',
|
||||||
images: ['/og-image.png'],
|
images: ['/og-image.png'],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
title: 'Bulk QR Code Generator - Create 1000s from Excel',
|
title: 'Bulk QR Code Generator - Create Bulk QR Codes from Excel',
|
||||||
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
description: 'Generate hundreds of QR codes at once from Excel/CSV. Create URLs, vCards, and more in bulk with custom branding.',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -280,15 +283,43 @@ export default function BulkQRCodeGeneratorPage() {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const breadcrumbItems: BreadcrumbItem[] = [
|
const breadcrumbItems: BreadcrumbItem[] = [
|
||||||
{ name: 'Home', url: '/' },
|
{ name: 'Home', url: '/' },
|
||||||
{ name: 'Bulk QR Code Generator', url: '/bulk-qr-code-generator' },
|
{ name: 'Bulk QR Code Generator', url: '/bulk-qr-code-generator' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
const relatedUseCaseLinks = [
|
||||||
<>
|
{
|
||||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
href: '/qr-code-for-marketing-campaigns',
|
||||||
<div className="min-h-screen bg-white">
|
title: 'QR Codes for Marketing Campaigns',
|
||||||
|
description: 'Use bulk generation when campaign placement or print distribution needs multiple trackable codes.',
|
||||||
|
ctaLabel: 'Create a trackable campaign QR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: featuredUseCases[2].href,
|
||||||
|
title: featuredUseCases[2].title,
|
||||||
|
description: featuredUseCases[2].summary,
|
||||||
|
ctaLabel: featuredUseCases[2].ctaLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/use-cases',
|
||||||
|
title: 'Explore the use-case hub',
|
||||||
|
description: 'See where bulk creation fits into broader QR workflows and commercial parents.',
|
||||||
|
ctaLabel: 'Explore QR code use cases',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/custom-qr-code-generator',
|
||||||
|
title: 'Custom QR Code Generator',
|
||||||
|
description: 'Pair bulk creation with stronger print presentation when brand consistency matters.',
|
||||||
|
ctaLabel: 'Design a custom QR code',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||||
|
<MarketingPageTracker pageType="commercial" cluster="bulk-qr" />
|
||||||
|
<div className="min-h-screen bg-white">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="relative overflow-hidden bg-gradient-to-br from-green-50 via-white to-blue-50 py-20">
|
<section className="relative overflow-hidden bg-gradient-to-br from-green-50 via-white to-blue-50 py-20">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||||
|
|
@ -297,7 +328,7 @@ export default function BulkQRCodeGeneratorPage() {
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="inline-flex items-center space-x-2 bg-green-100 text-green-800 px-4 py-2 rounded-full text-sm font-semibold">
|
<div className="inline-flex items-center space-x-2 bg-green-100 text-green-800 px-4 py-2 rounded-full text-sm font-semibold">
|
||||||
<span>⚡</span>
|
<span>⚡</span>
|
||||||
<span>Generate 1000s in Minutes</span>
|
<span>CSV and Excel workflows</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
<h1 className="text-5xl lg:text-6xl font-bold text-gray-900 leading-tight">
|
||||||
|
|
@ -347,7 +378,7 @@ export default function BulkQRCodeGeneratorPage() {
|
||||||
<div className="bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg p-8 text-center mb-4">
|
<div className="bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg p-8 text-center mb-4">
|
||||||
<div className="text-4xl mb-2">📊</div>
|
<div className="text-4xl mb-2">📊</div>
|
||||||
<p className="text-gray-600 font-medium mb-1">products.xlsx</p>
|
<p className="text-gray-600 font-medium mb-1">products.xlsx</p>
|
||||||
<p className="text-sm text-gray-500">1,247 rows ready</p>
|
<p className="text-sm text-gray-500">Bulk-ready file</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center mb-4">
|
<div className="flex items-center justify-center mb-4">
|
||||||
<svg className="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -362,11 +393,11 @@ export default function BulkQRCodeGeneratorPage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-center text-sm text-gray-600 mt-4">
|
<p className="text-center text-sm text-gray-600 mt-4">
|
||||||
+ 1,239 more codes
|
Continue with a full batch import
|
||||||
</p>
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="absolute -top-4 -right-4 bg-green-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg">
|
<div className="absolute -top-4 -right-4 bg-green-500 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg">
|
||||||
1000s at Once!
|
Bulk import
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -631,14 +662,23 @@ Product C,https://example.com/product-c,Budget Widget,electronics,sale`}
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* CTA Section */}
|
<GrowthLinksSection
|
||||||
<section className="py-20 bg-gradient-to-r from-green-600 to-blue-600 text-white">
|
eyebrow="Bulk-ready workflows"
|
||||||
|
title="Where bulk QR creation becomes operationally useful"
|
||||||
|
description="Bulk generation is strongest when one spreadsheet feeds a real deployment workflow across campaigns, events, labels, or repeated placements."
|
||||||
|
links={relatedUseCaseLinks}
|
||||||
|
pageType="commercial"
|
||||||
|
cluster="bulk-qr"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* CTA Section */}
|
||||||
|
<section className="py-20 bg-gradient-to-r from-green-600 to-blue-600 text-white">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||||
<h2 className="text-4xl font-bold mb-6">
|
<h2 className="text-4xl font-bold mb-6">
|
||||||
Generate 1000s of QR Codes in Minutes
|
Generate bulk QR codes without one-by-one setup
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl mb-8 text-green-100">
|
<p className="text-xl mb-8 text-green-100">
|
||||||
Save hours of manual work. Upload your file and get all QR codes ready instantly.
|
Save hours of manual work. Upload your file and get all QR codes ready instantly.
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,12 @@ import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||||
import { breadcrumbSchema } from '@/lib/schema';
|
import { breadcrumbSchema } from '@/lib/schema';
|
||||||
|
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||||
|
import { MarketingPageTracker } from '@/components/marketing/MarketingAnalytics';
|
||||||
|
import { featuredUseCases } from '@/lib/growth-pages';
|
||||||
import {
|
import {
|
||||||
Palette,
|
Palette,
|
||||||
Upload,
|
Upload,
|
||||||
|
|
@ -287,15 +290,43 @@ export default function CustomQRCodeGeneratorPage() {
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
const breadcrumbItems: BreadcrumbItem[] = [
|
const breadcrumbItems: BreadcrumbItem[] = [
|
||||||
{ name: 'Home', url: '/' },
|
{ name: 'Home', url: '/' },
|
||||||
{ name: 'Custom QR Code Generator', url: '/custom-qr-code-generator' },
|
{ name: 'Custom QR Code Generator', url: '/custom-qr-code-generator' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
const relatedUseCaseLinks = [
|
||||||
<>
|
{
|
||||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
href: '/qr-code-for-marketing-campaigns',
|
||||||
<div className="min-h-screen bg-white">
|
title: 'QR Codes for Marketing Campaigns',
|
||||||
|
description: 'Connect branded print QR codes to campaign-specific destinations and measurement.',
|
||||||
|
ctaLabel: 'Create a trackable campaign QR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: featuredUseCases[0].href,
|
||||||
|
title: featuredUseCases[0].title,
|
||||||
|
description: featuredUseCases[0].summary,
|
||||||
|
ctaLabel: featuredUseCases[0].ctaLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: featuredUseCases[2].href,
|
||||||
|
title: featuredUseCases[2].title,
|
||||||
|
description: featuredUseCases[2].summary,
|
||||||
|
ctaLabel: featuredUseCases[2].ctaLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/use-cases',
|
||||||
|
title: 'Explore the use-case hub',
|
||||||
|
description: 'Browse QR workflows where design, routing, and measurement need to work together.',
|
||||||
|
ctaLabel: 'Explore QR code use cases',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||||
|
<MarketingPageTracker pageType="commercial" cluster="custom-qr" />
|
||||||
|
<div className="min-h-screen bg-white">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||||
|
|
@ -621,11 +652,20 @@ export default function CustomQRCodeGeneratorPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Final CTA */}
|
<GrowthLinksSection
|
||||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
eyebrow="Branded workflows"
|
||||||
|
title="Where custom QR design supports the workflow"
|
||||||
|
description="Custom QR codes work best when they support a real business journey, not when they are only decoration. These are the strongest adjacent workflows."
|
||||||
|
links={relatedUseCaseLinks}
|
||||||
|
pageType="commercial"
|
||||||
|
cluster="custom-qr"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Final CTA */}
|
||||||
|
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||||
<h2 className="text-4xl font-bold mb-6">
|
<h2 className="text-4xl font-bold mb-6">
|
||||||
Start Creating Branded QR Codes Today
|
Start Creating Branded QR Codes Today
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import Link from 'next/link';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Button } from '@/components/ui/Button';
|
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||||
import { breadcrumbSchema } from '@/lib/schema';
|
import { breadcrumbSchema } from '@/lib/schema';
|
||||||
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
|
import { AnswerFirstBlock } from '@/components/marketing/AnswerFirstBlock';
|
||||||
|
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||||
|
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
||||||
|
import { featuredUseCases } from '@/lib/growth-pages';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
|
|
@ -230,15 +232,43 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const breadcrumbItems: BreadcrumbItem[] = [
|
const breadcrumbItems: BreadcrumbItem[] = [
|
||||||
{ name: 'Home', url: '/' },
|
{ name: 'Home', url: '/' },
|
||||||
{ name: 'Dynamic QR Code Generator', url: '/dynamic-qr-code-generator' },
|
{ name: 'Dynamic QR Code Generator', url: '/dynamic-qr-code-generator' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
const relatedUseCaseLinks = [
|
||||||
<>
|
{
|
||||||
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
href: featuredUseCases[0].href,
|
||||||
<div className="min-h-screen bg-white">
|
title: featuredUseCases[0].title,
|
||||||
|
description: featuredUseCases[0].summary,
|
||||||
|
ctaLabel: featuredUseCases[0].ctaLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: featuredUseCases[1].href,
|
||||||
|
title: featuredUseCases[1].title,
|
||||||
|
description: featuredUseCases[1].summary,
|
||||||
|
ctaLabel: featuredUseCases[1].ctaLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: featuredUseCases[2].href,
|
||||||
|
title: featuredUseCases[2].title,
|
||||||
|
description: featuredUseCases[2].summary,
|
||||||
|
ctaLabel: featuredUseCases[2].ctaLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/use-cases',
|
||||||
|
title: 'Explore the use-case hub',
|
||||||
|
description: 'See how dynamic QR workflows connect to commercial pages, tools, and support content.',
|
||||||
|
ctaLabel: 'Explore QR code use cases',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||||
|
<MarketingPageTracker pageType="commercial" cluster="dynamic-qr" />
|
||||||
|
<div className="min-h-screen bg-white">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
<section className="relative overflow-hidden bg-gradient-to-br from-purple-50 via-white to-blue-50 py-20">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||||
|
|
@ -274,21 +304,21 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||||
<span className="text-gray-700">{feature}</span>
|
<span className="text-gray-700">{feature}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
<Link href="/signup">
|
<TrackedCtaLink href="/signup" ctaLabel="Create Dynamic QR Code" ctaLocation="hero_primary" pageType="commercial" cluster="dynamic-qr">
|
||||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||||
Create Dynamic QR Code
|
Create Dynamic QR Code
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</TrackedCtaLink>
|
||||||
<Link href="/pricing">
|
<TrackedCtaLink href="/pricing" ctaLabel="View Pricing" ctaLocation="hero_secondary" pageType="commercial" cluster="dynamic-qr">
|
||||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||||
View Pricing
|
View Pricing
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</TrackedCtaLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Demo */}
|
{/* Visual Demo */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|
@ -514,32 +544,41 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||||
</p>
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* CTA Section */}
|
<GrowthLinksSection
|
||||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
eyebrow="Best next workflows"
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
title="See where dynamic QR becomes most useful"
|
||||||
<h2 className="text-4xl font-bold mb-6">
|
description="These are the strongest first workflows for dynamic QR because the printed asset stays the same while the destination or campaign context keeps moving."
|
||||||
Start Creating Dynamic QR Codes Today
|
links={relatedUseCaseLinks}
|
||||||
</h2>
|
pageType="commercial"
|
||||||
<p className="text-xl mb-8 text-purple-100">
|
cluster="dynamic-qr"
|
||||||
Join thousands of businesses who never worry about reprinting QR codes again
|
/>
|
||||||
</p>
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
{/* CTA Section */}
|
||||||
<Link href="/signup">
|
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-purple-600 hover:bg-gray-100">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||||
Get Started Free
|
<h2 className="text-4xl font-bold mb-6">
|
||||||
</Button>
|
Start Creating Dynamic QR Codes Today
|
||||||
</Link>
|
</h2>
|
||||||
<Link href="/signup">
|
<p className="text-xl mb-8 text-purple-100">
|
||||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
Use one QR code that can keep working even when the destination behind it needs to change.
|
||||||
Create QR Code Now
|
</p>
|
||||||
</Button>
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
</Link>
|
<TrackedCtaLink href="/signup" ctaLabel="Get Started Free" ctaLocation="footer_primary" pageType="commercial" cluster="dynamic-qr">
|
||||||
</div>
|
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-purple-600 hover:bg-gray-100">
|
||||||
</div>
|
Get Started Free
|
||||||
</section>
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
<TrackedCtaLink href="/signup" ctaLabel="Create QR Code Now" ctaLocation="footer_secondary" pageType="commercial" cluster="dynamic-qr">
|
||||||
|
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||||
|
Create QR Code Now
|
||||||
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import AdBanner from '@/components/ads/AdBanner';
|
import '@/styles/globals.css';
|
||||||
import '@/styles/globals.css';
|
import MarketingLayout from './MarketingLayout';
|
||||||
import { Providers } from '@/components/Providers';
|
// Import schema functions from library
|
||||||
import MarketingLayout from './MarketingLayout';
|
import { organizationSchema } from '@/lib/schema';
|
||||||
// Import schema functions from library
|
|
||||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
|
||||||
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
|
||||||
|
|
||||||
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
const isIndexable = process.env.NEXT_PUBLIC_INDEXABLE === 'true';
|
||||||
|
|
||||||
|
|
@ -54,29 +51,20 @@ export const metadata: Metadata = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MarketingGroupLayout({
|
export default function MarketingGroupLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||||
/>
|
/>
|
||||||
<MarketingLayout>
|
<MarketingLayout>
|
||||||
{children}
|
{children}
|
||||||
</MarketingLayout>
|
</MarketingLayout>
|
||||||
{/* Global Marketing Ad - Exclusions handled in AdBanner */}
|
</>
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
);
|
||||||
<AdBanner
|
}
|
||||||
dataAdSlot="2607110637"
|
|
||||||
dataAdFormat="auto"
|
|
||||||
fullWidthResponsive={true}
|
|
||||||
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,20 @@ import { pillarPageSchema, faqPageSchema } from "@/lib/schema";
|
||||||
import { FAQSection } from "@/components/aeo/FAQSection";
|
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||||
import { AnswerBox } from "@/components/aeo/AnswerBox";
|
import { AnswerBox } from "@/components/aeo/AnswerBox";
|
||||||
|
|
||||||
export function generateMetadata({ params }: { params: { pillar: string } }) {
|
export function generateMetadata({ params }: { params: { pillar: string } }) {
|
||||||
const meta = pillarMeta.find(p => p.key === params.pillar);
|
const meta = pillarMeta.find(p => p.key === params.pillar);
|
||||||
if (!meta) return {};
|
if (!meta) return {};
|
||||||
return {
|
return {
|
||||||
title: `${meta.title} - Ultimate Guide | QR Master`,
|
title: `${meta.title} - Ultimate Guide | QR Master`,
|
||||||
description: meta.description
|
description: meta.description,
|
||||||
};
|
alternates: {
|
||||||
}
|
canonical: `https://www.qrmaster.net/learn/${meta.key}`,
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
url: `https://www.qrmaster.net/learn/${meta.key}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return pillarMeta.map((pillar) => ({
|
return pillarMeta.map((pillar) => ({
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,16 @@ import Link from "next/link";
|
||||||
import { pillarMeta } from "@/lib/pillar-data";
|
import { pillarMeta } from "@/lib/pillar-data";
|
||||||
import { getPublishedPosts } from "@/lib/content";
|
import { getPublishedPosts } from "@/lib/content";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "Learn QR Code Mastery | QR Master Hub",
|
title: "Learn QR Code Mastery | QR Master Hub",
|
||||||
description: "Guides, use cases, tracking deep-dives, and security best practices for dynamic QR codes.",
|
description: "Guides, use cases, tracking deep-dives, and security best practices for dynamic QR codes.",
|
||||||
};
|
alternates: {
|
||||||
|
canonical: "https://www.qrmaster.net/learn",
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
url: "https://www.qrmaster.net/learn",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default function LearnHubPage() {
|
export default function LearnHubPage() {
|
||||||
const posts = getPublishedPosts();
|
const posts = getPublishedPosts();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildUseCaseMetadata,
|
||||||
|
UseCasePageTemplate,
|
||||||
|
} from "@/components/marketing/UseCasePageTemplate";
|
||||||
|
|
||||||
|
export const metadata: Metadata = buildUseCaseMetadata({
|
||||||
|
title: "QR Codes for Marketing Campaigns",
|
||||||
|
description:
|
||||||
|
"Plan QR codes for marketing campaigns around placement tracking, changing destinations, and offline-to-online attribution.",
|
||||||
|
canonicalPath: "/qr-code-for-marketing-campaigns",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function QRCodeForMarketingCampaignsPage() {
|
||||||
|
return (
|
||||||
|
<UseCasePageTemplate
|
||||||
|
title="QR Codes for Marketing Campaigns"
|
||||||
|
description="Plan QR codes for marketing campaigns around placement tracking, changing destinations, and offline-to-online attribution."
|
||||||
|
eyebrow="Campaign Workflows"
|
||||||
|
intro="Marketing campaign QR codes work best when the code on the printed asset stays stable while the destination and attribution model can evolve with the campaign."
|
||||||
|
pageType="commercial"
|
||||||
|
cluster="marketing-campaigns"
|
||||||
|
useCase="marketing-campaigns"
|
||||||
|
breadcrumbs={[
|
||||||
|
{ name: "Home", url: "/" },
|
||||||
|
{
|
||||||
|
name: "QR Codes for Marketing Campaigns",
|
||||||
|
url: "/qr-code-for-marketing-campaigns",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
answer="A campaign QR code should do more than open a page. It should help you compare placements, update the destination when the offer changes, and route offline traffic into a measurable funnel."
|
||||||
|
whenToUse={[
|
||||||
|
"You run flyers, posters, packaging inserts, or event signage with campaign-specific CTA copy.",
|
||||||
|
"You want to compare placements or creatives instead of treating every scan as generic traffic.",
|
||||||
|
"Your destination may change during the life of the printed campaign.",
|
||||||
|
]}
|
||||||
|
comparisonItems={[
|
||||||
|
{ label: "Offer updates", text: "New print required", value: true },
|
||||||
|
{ label: "Placement attribution", text: "Often manual", value: true },
|
||||||
|
{ label: "Creative testing", text: "Hard to manage", value: true },
|
||||||
|
]}
|
||||||
|
howToSteps={[
|
||||||
|
"Create campaign QR flows around one clear action and one named placement context.",
|
||||||
|
"Use dynamic destinations or tagged URLs so the print stays usable when the offer changes.",
|
||||||
|
"Measure scans with a clean CTA path into signup, lead capture, or campaign landing pages.",
|
||||||
|
]}
|
||||||
|
primaryCta={{
|
||||||
|
href: "/dynamic-qr-code-generator",
|
||||||
|
label: "Create a trackable campaign QR",
|
||||||
|
}}
|
||||||
|
secondaryCta={{
|
||||||
|
href: "/use-cases",
|
||||||
|
label: "Browse use-case workflows",
|
||||||
|
}}
|
||||||
|
workflowTitle="What strong campaign QR workflows look like"
|
||||||
|
workflowIntro="Campaign QR strategy becomes more useful when creative, placement, and destination are treated as a system rather than a single link printed everywhere."
|
||||||
|
workflowCards={[
|
||||||
|
{
|
||||||
|
title: "Placement-aware routing",
|
||||||
|
description: "Keep banner, flyer, packaging, and in-store placements comparable by using distinct destinations or campaign tags.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Post-print flexibility",
|
||||||
|
description: "Adjust the landing page, offer, or CTA destination after print when the campaign learns something or needs a fast update.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Measurement-ready handoff",
|
||||||
|
description: "Push campaign scans toward signup, booking, or lead-gen paths so the QR is tied to a business outcome instead of a vanity click.",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
checklistTitle="Campaign QR checklist"
|
||||||
|
checklist={[
|
||||||
|
"Match each QR code to one campaign purpose and one primary CTA.",
|
||||||
|
"Differentiate placements with clean naming or URL tagging before the assets go to print.",
|
||||||
|
"Use a destination you can update when the promotion, offer, or landing page changes.",
|
||||||
|
"Link the campaign flow back to a measured CTA path instead of stopping at raw scan counts.",
|
||||||
|
]}
|
||||||
|
supportLinks={[
|
||||||
|
{
|
||||||
|
href: "/qr-code-tracking",
|
||||||
|
title: "QR Code Tracking",
|
||||||
|
description: "Use when the real priority is measuring placement and scanner context.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/custom-qr-code-generator",
|
||||||
|
title: "Custom QR Code Generator",
|
||||||
|
description: "Useful when brand fit and print creative need more control.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/blog/utm-parameter-qr-codes",
|
||||||
|
title: "UTM Parameters with QR Codes",
|
||||||
|
description: "Support article for placement naming and campaign attribution strategy.",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
faq={[
|
||||||
|
{
|
||||||
|
question: "Why use QR codes in marketing campaigns?",
|
||||||
|
answer: "Campaign QR codes help move offline audiences into a measurable online path. They are most useful when the destination and tracking setup are planned before the assets go live.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Should campaign QR codes be dynamic?",
|
||||||
|
answer: "Yes, when the destination, offer, or campaign landing page may change after print. That avoids replacing materials just because the target page changes.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "How do I track different QR placements in one campaign?",
|
||||||
|
answer: "Use distinct destinations or tagged URLs for each placement so flyers, posters, booth signs, and packaging inserts can be compared cleanly.",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import Link from 'next/link';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Card } from '@/components/ui/Card';
|
||||||
import { Card } from '@/components/ui/Card';
|
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
import { breadcrumbSchema } from '@/lib/schema';
|
||||||
import { breadcrumbSchema } from '@/lib/schema';
|
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
||||||
|
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
||||||
|
import { featuredUseCases } from '@/lib/growth-pages';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
|
|
@ -164,15 +166,43 @@ export default function QRCodeTrackingPage() {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const breadcrumbItems: BreadcrumbItem[] = [
|
const breadcrumbItems: BreadcrumbItem[] = [
|
||||||
{ name: 'Home', url: '/' },
|
{ name: 'Home', url: '/' },
|
||||||
{ name: 'QR Code Tracking', url: '/qr-code-tracking' },
|
{ name: 'QR Code Tracking', url: '/qr-code-tracking' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
const relatedUseCaseLinks = [
|
||||||
<>
|
{
|
||||||
<SeoJsonLd data={[softwareSchema, howToSchema, breadcrumbSchema(breadcrumbItems)]} />
|
href: featuredUseCases[2].href,
|
||||||
<div className="min-h-screen bg-white">
|
title: featuredUseCases[2].title,
|
||||||
|
description: featuredUseCases[2].summary,
|
||||||
|
ctaLabel: featuredUseCases[2].ctaLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/qr-code-for-marketing-campaigns',
|
||||||
|
title: 'QR Codes for Marketing Campaigns',
|
||||||
|
description: 'Map scans to campaign placements, creative tests, and offline-to-online attribution.',
|
||||||
|
ctaLabel: 'Create a trackable campaign QR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: featuredUseCases[0].href,
|
||||||
|
title: featuredUseCases[0].title,
|
||||||
|
description: featuredUseCases[0].summary,
|
||||||
|
ctaLabel: featuredUseCases[0].ctaLabel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/use-cases',
|
||||||
|
title: 'Explore the use-case hub',
|
||||||
|
description: 'Browse the first commercial workflows built around dynamic updates and measurable scans.',
|
||||||
|
ctaLabel: 'Explore QR code use cases',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoJsonLd data={[softwareSchema, howToSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||||
|
<MarketingPageTracker pageType="commercial" cluster="qr-tracking" />
|
||||||
|
<div className="min-h-screen bg-white">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 py-20">
|
<section className="relative overflow-hidden bg-gradient-to-br from-blue-50 via-white to-purple-50 py-20">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||||
|
|
@ -190,20 +220,20 @@ export default function QRCodeTrackingPage() {
|
||||||
|
|
||||||
<p className="text-xl text-gray-600 leading-relaxed">
|
<p className="text-xl text-gray-600 leading-relaxed">
|
||||||
Monitor your QR code performance in real-time. Get detailed insights on location, device, time, and user behavior. Make data-driven decisions with our free tracking software.
|
Monitor your QR code performance in real-time. Get detailed insights on location, device, time, and user behavior. Make data-driven decisions with our free tracking software.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
<Link href="/signup">
|
<TrackedCtaLink href="/signup" ctaLabel="Start Tracking Free" ctaLocation="hero_primary" pageType="commercial" cluster="qr-tracking">
|
||||||
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
<Button size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||||
Start Tracking Free
|
Start Tracking Free
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</TrackedCtaLink>
|
||||||
<Link href="/signup">
|
<TrackedCtaLink href="/signup" ctaLabel="Create Trackable QR Code" ctaLocation="hero_secondary" pageType="commercial" cluster="qr-tracking">
|
||||||
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
<Button variant="outline" size="lg" className="text-lg px-8 py-4 w-full sm:w-auto">
|
||||||
Create Trackable QR Code
|
Create Trackable QR Code
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</TrackedCtaLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-6 text-sm text-gray-600">
|
<div className="flex items-center space-x-6 text-sm text-gray-600">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|
@ -212,13 +242,13 @@ export default function QRCodeTrackingPage() {
|
||||||
</svg>
|
</svg>
|
||||||
<span>No credit card required</span>
|
<span>No credit card required</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<svg className="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
<svg className="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Unlimited scans</span>
|
<span>Placement-ready reports</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Analytics Preview */}
|
{/* Analytics Preview */}
|
||||||
|
|
@ -227,20 +257,20 @@ export default function QRCodeTrackingPage() {
|
||||||
<h3 className="font-semibold text-lg mb-4">Live Analytics Dashboard</h3>
|
<h3 className="font-semibold text-lg mb-4">Live Analytics Dashboard</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center pb-3 border-b">
|
<div className="flex justify-between items-center pb-3 border-b">
|
||||||
<span className="text-gray-600">Total Scans</span>
|
<span className="text-gray-600">Placement view</span>
|
||||||
<span className="text-2xl font-bold text-primary-600">12,547</span>
|
<span className="text-base font-semibold text-primary-600">Flyer vs booth vs table card</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center pb-3 border-b">
|
<div className="flex justify-between items-center pb-3 border-b">
|
||||||
<span className="text-gray-600">Unique Users</span>
|
<span className="text-gray-600">Time trend</span>
|
||||||
<span className="text-2xl font-bold text-primary-600">8,392</span>
|
<span className="text-base font-semibold text-primary-600">Lunch, event day, or campaign burst</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center pb-3 border-b">
|
<div className="flex justify-between items-center pb-3 border-b">
|
||||||
<span className="text-gray-600">Top Location</span>
|
<span className="text-gray-600">Location context</span>
|
||||||
<span className="font-semibold">🇩🇪 Germany</span>
|
<span className="font-semibold">Region and city level view</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-gray-600">Top Device</span>
|
<span className="text-gray-600">Device context</span>
|
||||||
<span className="font-semibold">📱 iPhone</span>
|
<span className="font-semibold">Phone, desktop, and scan mix</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -318,9 +348,9 @@ export default function QRCodeTrackingPage() {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Comparison Table */}
|
{/* Comparison Table */}
|
||||||
<section className="py-20 bg-gray-50">
|
<section className="py-20 bg-gray-50">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-5xl">
|
||||||
<div className="text-center mb-16">
|
<div className="text-center mb-16">
|
||||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||||
QR Master vs Free Tools
|
QR Master vs Free Tools
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -364,33 +394,42 @@ export default function QRCodeTrackingPage() {
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* CTA Section */}
|
<GrowthLinksSection
|
||||||
<section className="py-20 bg-gradient-to-r from-primary-600 to-purple-600 text-white">
|
eyebrow="Tracking-led workflows"
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
title="Where scan visibility matters most"
|
||||||
<h2 className="text-4xl font-bold mb-6">
|
description="These are the workflows where scan context, placement comparison, and destination flexibility make QR tracking materially more useful."
|
||||||
Start Tracking Your QR Codes Today
|
links={relatedUseCaseLinks}
|
||||||
</h2>
|
pageType="commercial"
|
||||||
<p className="text-xl mb-8 text-primary-100">
|
cluster="qr-tracking"
|
||||||
Join thousands of businesses using QR Master to track and optimize their QR code campaigns
|
/>
|
||||||
</p>
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
{/* CTA Section */}
|
||||||
<Link href="/signup">
|
<section className="py-20 bg-gradient-to-r from-primary-600 to-purple-600 text-white">
|
||||||
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-primary-600 hover:bg-gray-100">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||||
Create Free Account
|
<h2 className="text-4xl font-bold mb-6">
|
||||||
</Button>
|
Start Tracking Your QR Codes Today
|
||||||
</Link>
|
</h2>
|
||||||
<Link href="/pricing">
|
<p className="text-xl mb-8 text-primary-100">
|
||||||
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
Measure scans with enough context to improve the next placement, campaign, or printed workflow.
|
||||||
View Pricing
|
</p>
|
||||||
</Button>
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
</Link>
|
<TrackedCtaLink href="/signup" ctaLabel="Create Free Account" ctaLocation="footer_primary" pageType="commercial" cluster="qr-tracking">
|
||||||
</div>
|
<Button size="lg" variant="secondary" className="text-lg px-8 py-4 w-full sm:w-auto bg-white text-primary-600 hover:bg-gray-100">
|
||||||
</div>
|
Create Free Account
|
||||||
</section>
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
<TrackedCtaLink href="/pricing" ctaLabel="View Pricing" ctaLocation="footer_secondary" pageType="commercial" cluster="qr-tracking">
|
||||||
|
<Button size="lg" variant="outline" className="text-lg px-8 py-4 w-full sm:w-auto border-white text-white hover:bg-white/10">
|
||||||
|
View Pricing
|
||||||
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildUseCaseMetadata,
|
||||||
|
UseCasePageTemplate,
|
||||||
|
} from "@/components/marketing/UseCasePageTemplate";
|
||||||
|
import {
|
||||||
|
featuredUseCases,
|
||||||
|
getUseCasePage,
|
||||||
|
} from "@/lib/growth-pages";
|
||||||
|
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return featuredUseCases.map((item) => ({
|
||||||
|
slug: item.slug,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateMetadata({ params }: { params: { slug: string } }) {
|
||||||
|
const page = getUseCasePage(params.slug);
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildUseCaseMetadata({
|
||||||
|
title: page.title,
|
||||||
|
description: page.metaDescription,
|
||||||
|
canonicalPath: page.href,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UseCaseDetailPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { slug: string };
|
||||||
|
}) {
|
||||||
|
const page = getUseCasePage(params.slug);
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UseCasePageTemplate
|
||||||
|
title={page.title}
|
||||||
|
description={page.metaDescription}
|
||||||
|
eyebrow={page.eyebrow}
|
||||||
|
intro={page.intro}
|
||||||
|
pageType="use_case"
|
||||||
|
cluster={page.cluster}
|
||||||
|
useCase={page.slug}
|
||||||
|
breadcrumbs={[
|
||||||
|
{ name: "Home", url: "/" },
|
||||||
|
{ name: "Use Cases", url: "/use-cases" },
|
||||||
|
{ name: page.title, url: page.href },
|
||||||
|
]}
|
||||||
|
answer={page.answer}
|
||||||
|
whenToUse={page.whenToUse}
|
||||||
|
comparisonItems={page.comparisonItems}
|
||||||
|
howToSteps={page.howToSteps}
|
||||||
|
primaryCta={{
|
||||||
|
href: page.parentHref,
|
||||||
|
label: page.ctaLabel,
|
||||||
|
}}
|
||||||
|
secondaryCta={{
|
||||||
|
href: "/use-cases",
|
||||||
|
label: "Explore more use cases",
|
||||||
|
}}
|
||||||
|
workflowTitle={page.workflowTitle}
|
||||||
|
workflowIntro={page.workflowIntro}
|
||||||
|
workflowCards={page.workflowCards}
|
||||||
|
checklistTitle={page.checklistTitle}
|
||||||
|
checklist={page.checklist}
|
||||||
|
supportLinks={page.supportLinks}
|
||||||
|
faq={page.faq}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,303 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
Compass,
|
||||||
|
LibraryBig,
|
||||||
|
Link2,
|
||||||
|
Route,
|
||||||
|
Sparkles,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import Breadcrumbs, { BreadcrumbItem } from "@/components/Breadcrumbs";
|
||||||
|
import SeoJsonLd from "@/components/SeoJsonLd";
|
||||||
|
import {
|
||||||
|
MarketingPageTracker,
|
||||||
|
TrackedCtaLink,
|
||||||
|
} from "@/components/marketing/MarketingAnalytics";
|
||||||
|
import { Button } from "@/components/ui/Button";
|
||||||
|
import { Card } from "@/components/ui/Card";
|
||||||
|
import {
|
||||||
|
commercialPages,
|
||||||
|
featuredUseCases,
|
||||||
|
supportResources,
|
||||||
|
upcomingUseCaseIdeas,
|
||||||
|
} from "@/lib/growth-pages";
|
||||||
|
import { breadcrumbSchema } from "@/lib/schema";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: {
|
||||||
|
absolute: "QR Code Use Cases for Business | QR Master",
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
"Explore QR code use cases for restaurants, events, business cards, and campaign workflows built around dynamic updates and tracking.",
|
||||||
|
alternates: {
|
||||||
|
canonical: "https://www.qrmaster.net/use-cases",
|
||||||
|
languages: {
|
||||||
|
"x-default": "https://www.qrmaster.net/use-cases",
|
||||||
|
en: "https://www.qrmaster.net/use-cases",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: "QR Code Use Cases for Business | QR Master",
|
||||||
|
description:
|
||||||
|
"Explore QR code use cases for restaurants, events, business cards, and campaign workflows built around dynamic updates and tracking.",
|
||||||
|
url: "https://www.qrmaster.net/use-cases",
|
||||||
|
type: "website",
|
||||||
|
images: ["/og-image.png"],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
title: "QR Code Use Cases for Business | QR Master",
|
||||||
|
description:
|
||||||
|
"Explore QR code use cases for restaurants, events, business cards, and campaign workflows built around dynamic updates and tracking.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function UseCasesHubPage() {
|
||||||
|
const breadcrumbItems: BreadcrumbItem[] = [
|
||||||
|
{ name: "Home", url: "/" },
|
||||||
|
{ name: "Use Cases", url: "/use-cases" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoJsonLd data={[breadcrumbSchema(breadcrumbItems)]} />
|
||||||
|
<MarketingPageTracker pageType="use_case_hub" cluster="all-use-cases" />
|
||||||
|
|
||||||
|
<div className="min-h-screen bg-white">
|
||||||
|
<section className="relative overflow-hidden bg-gradient-to-br from-slate-950 via-blue-950 to-cyan-950 text-white">
|
||||||
|
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(125,211,252,0.18),transparent_34%),radial-gradient(circle_at_right,rgba(255,255,255,0.06),transparent_28%)]" />
|
||||||
|
<div className="relative container mx-auto max-w-7xl px-4 py-20 sm:px-6 lg:px-8">
|
||||||
|
<Breadcrumbs
|
||||||
|
items={breadcrumbItems}
|
||||||
|
className="[&_a]:text-blue-100/80 [&_a:hover]:text-white [&_span]:text-blue-100/80 [&_[aria-current=page]]:text-white"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid gap-12 lg:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)] lg:items-center">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/10 px-4 py-2 text-sm font-semibold text-cyan-100 shadow-lg shadow-cyan-950/30 backdrop-blur">
|
||||||
|
<Sparkles className="h-4 w-4" />
|
||||||
|
<span>Commercial use-case hub</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-5">
|
||||||
|
<h1 className="max-w-4xl text-4xl font-bold tracking-tight text-white md:text-5xl lg:text-6xl">
|
||||||
|
QR code use cases that fit real business workflows
|
||||||
|
</h1>
|
||||||
|
<p className="max-w-3xl text-lg leading-8 text-blue-50/88 md:text-xl">
|
||||||
|
This hub focuses on workflows where dynamic updates and
|
||||||
|
measurement matter. It is not a list of random QR ideas. It
|
||||||
|
is the commercial layer between QR Master's product pages,
|
||||||
|
tools, and editorial content.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 text-sm text-blue-50/80 sm:grid-cols-2">
|
||||||
|
{[
|
||||||
|
"Use-case pages map back to a clear commercial parent.",
|
||||||
|
"Each workflow is written for practical deployment, not filler traffic.",
|
||||||
|
"Support resources reinforce the wedge around dynamic and trackable QR flows.",
|
||||||
|
"The next cluster expansion will build on measurable routing and internal links.",
|
||||||
|
].map((line) => (
|
||||||
|
<div
|
||||||
|
key={line}
|
||||||
|
className="flex items-start gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
<Route className="mt-0.5 h-4 w-4 shrink-0 text-cyan-300" />
|
||||||
|
<span>{line}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 sm:flex-row">
|
||||||
|
<TrackedCtaLink
|
||||||
|
href={featuredUseCases[0].href}
|
||||||
|
ctaLabel="Explore restaurant menu QR codes"
|
||||||
|
ctaLocation="hero_primary"
|
||||||
|
pageType="use_case_hub"
|
||||||
|
cluster="all-use-cases"
|
||||||
|
>
|
||||||
|
<Button size="lg" className="w-full bg-white px-8 py-4 text-slate-950 hover:bg-slate-100 sm:w-auto">
|
||||||
|
Explore featured workflows
|
||||||
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
|
||||||
|
<TrackedCtaLink
|
||||||
|
href="/qr-code-for-marketing-campaigns"
|
||||||
|
ctaLabel="View marketing campaign QR page"
|
||||||
|
ctaLocation="hero_secondary"
|
||||||
|
pageType="use_case_hub"
|
||||||
|
cluster="all-use-cases"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="w-full border-white/30 bg-white/5 px-8 py-4 text-white hover:bg-white/10 sm:w-auto"
|
||||||
|
>
|
||||||
|
See campaign workflows
|
||||||
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="border-white/10 bg-white/10 p-8 text-white shadow-2xl shadow-slate-950/30 backdrop-blur">
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Compass className="h-5 w-5 text-cyan-300" />
|
||||||
|
<h2 className="text-2xl font-bold">How to use this hub</h2>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4 text-sm leading-6 text-blue-50/82">
|
||||||
|
<p>
|
||||||
|
Start with the workflow problem, not the QR format. If the
|
||||||
|
printed code needs to survive destination changes or you
|
||||||
|
need proof of performance, begin with the use case that
|
||||||
|
matches that job.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Each page below links back to the best product parent,
|
||||||
|
forward to related workflows, and sideways to educational
|
||||||
|
resources that help you deploy the QR well.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-16">
|
||||||
|
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="mb-10 max-w-3xl">
|
||||||
|
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
|
||||||
|
Featured use cases
|
||||||
|
</div>
|
||||||
|
<h2 className="mt-3 text-3xl font-bold text-slate-900">
|
||||||
|
First workflows in the growth rollout
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-lg leading-8 text-slate-600">
|
||||||
|
These are the first routes worth surfacing because they connect
|
||||||
|
cleanly to QR Master's strongest product angles and existing
|
||||||
|
supporting content.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 lg:grid-cols-3">
|
||||||
|
{featuredUseCases.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">
|
||||||
|
{page.cluster}
|
||||||
|
</div>
|
||||||
|
<h3 className="mt-4 text-2xl font-bold text-slate-900">
|
||||||
|
{page.title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-4 flex-1 text-base leading-7 text-slate-600">
|
||||||
|
{page.summary}
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 text-sm text-slate-600">
|
||||||
|
<span>Primary parent: {page.parentTitle}</span>
|
||||||
|
<ArrowRight className="h-4 w-4 text-blue-700 transition-transform group-hover:translate-x-1" />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</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">
|
||||||
|
<LibraryBig className="h-5 w-5 text-blue-700" />
|
||||||
|
<h2 className="text-2xl font-bold text-slate-900">
|
||||||
|
Commercial pages that anchor the hub
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
||||||
|
{commercialPages.map((page) => (
|
||||||
|
<Link
|
||||||
|
key={page.href}
|
||||||
|
href={page.href}
|
||||||
|
className="rounded-2xl border border-slate-200 p-4 transition-colors hover:border-blue-200 hover:bg-blue-50/60"
|
||||||
|
>
|
||||||
|
<div className={`h-1.5 rounded-full bg-gradient-to-r ${page.accent}`} />
|
||||||
|
<div className="mt-4 text-lg font-semibold text-slate-900">
|
||||||
|
{page.title}
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-sm leading-6 text-slate-600">
|
||||||
|
{page.description}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="rounded-3xl border-slate-200 bg-slate-950 p-8 text-white shadow-xl shadow-slate-200">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Link2 className="h-5 w-5 text-cyan-300" />
|
||||||
|
<h2 className="text-2xl font-bold">Support resources</h2>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 space-y-4">
|
||||||
|
{supportResources.map((resource) => (
|
||||||
|
<Link
|
||||||
|
key={resource.href}
|
||||||
|
href={resource.href}
|
||||||
|
className="block rounded-2xl border border-white/10 bg-white/5 p-4 transition-colors hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="text-lg font-semibold text-white">
|
||||||
|
{resource.title}
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-sm leading-6 text-blue-50/78">
|
||||||
|
{resource.description}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-16">
|
||||||
|
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="mb-10 max-w-3xl">
|
||||||
|
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
|
||||||
|
Next cluster candidates
|
||||||
|
</div>
|
||||||
|
<h2 className="mt-3 text-3xl font-bold text-slate-900">
|
||||||
|
What follows after the first use-case wave
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-lg leading-8 text-slate-600">
|
||||||
|
These are not published use-case routes yet. They are the next
|
||||||
|
practical cluster expansions once the first hub and CTA layer are
|
||||||
|
established.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
|
{upcomingUseCaseIdeas.map((item) => (
|
||||||
|
<Card
|
||||||
|
key={item.title}
|
||||||
|
className="rounded-3xl border-dashed border-slate-300 bg-slate-50 p-7"
|
||||||
|
>
|
||||||
|
<div className="text-xl font-semibold text-slate-900">
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 text-base leading-7 text-slate-600">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
<div className="mt-5 text-sm font-semibold text-blue-700">
|
||||||
|
Anchored by {item.href.replace("/", "")}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import '@/styles/globals.css';
|
import '@/styles/globals.css';
|
||||||
import { Providers } from '@/components/Providers';
|
import { Providers } from '@/components/Providers';
|
||||||
import MarketingDeLayout from './MarketingDeLayout';
|
import MarketingDeLayout from './MarketingDeLayout';
|
||||||
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
import { organizationSchema, websiteSchema } from '@/lib/schema';
|
||||||
import AdSenseScript from '@/components/ads/AdSenseScript';
|
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||||
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
|
|
@ -55,46 +54,33 @@ export const metadata: Metadata = {
|
||||||
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
|
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
import AdBanner from '@/components/ads/AdBanner'; // Import AdBanner
|
export default function MarketingDeGroupLayout({
|
||||||
|
children,
|
||||||
export default function MarketingDeGroupLayout({
|
}: {
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<body className="font-sans">
|
<body className="font-sans">
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Providers>
|
<Providers>
|
||||||
<AdSenseScript />
|
<FacebookPixel />
|
||||||
<FacebookPixel />
|
<script
|
||||||
<script
|
type="application/ld+json"
|
||||||
type="application/ld+json"
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
|
||||||
/>
|
/>
|
||||||
<script
|
<script
|
||||||
type="application/ld+json"
|
type="application/ld+json"
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema()) }}
|
||||||
/>
|
/>
|
||||||
<MarketingDeLayout>
|
<MarketingDeLayout>
|
||||||
{children}
|
{children}
|
||||||
|
</MarketingDeLayout>
|
||||||
{/* Global Marketing Ad - Exclusions handled in AdBanner */}
|
</Providers>
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
</Suspense>
|
||||||
<AdBanner
|
</body>
|
||||||
dataAdSlot="2607110637"
|
|
||||||
dataAdFormat="auto"
|
|
||||||
fullWidthResponsive={true}
|
|
||||||
className="bg-slate-50 rounded-xl p-4 border border-slate-100"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</MarketingDeLayout>
|
|
||||||
</Providers>
|
|
||||||
</Suspense>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,20 +53,53 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Learn hub and pillar pages
|
// Learn hub and pillar pages
|
||||||
const learnPages = [
|
const learnPages = [
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/learn`,
|
url: `${baseUrl}/learn`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'weekly' as const,
|
changeFrequency: 'weekly' as const,
|
||||||
priority: 0.9,
|
priority: 0.9,
|
||||||
},
|
},
|
||||||
...pillarMeta.map((pillar) => ({
|
...pillarMeta.map((pillar) => ({
|
||||||
url: `${baseUrl}/learn/${pillar.key}`,
|
url: `${baseUrl}/learn/${pillar.key}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly' as const,
|
changeFrequency: 'monthly' as const,
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const growthUseCasePages = [
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/use-cases`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'weekly' as const,
|
||||||
|
priority: 0.9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/use-cases/restaurant-menu-qr-codes`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'monthly' as const,
|
||||||
|
priority: 0.8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/use-cases/business-card-qr-codes`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'monthly' as const,
|
||||||
|
priority: 0.8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/use-cases/event-qr-codes`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'monthly' as const,
|
||||||
|
priority: 0.8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${baseUrl}/qr-code-for-marketing-campaigns`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'monthly' as const,
|
||||||
|
priority: 0.85,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Author pages
|
// Author pages
|
||||||
const authorPages = authors.map((author) => ({
|
const authorPages = authors.map((author) => ({
|
||||||
|
|
@ -189,10 +222,11 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
...toolPages,
|
...toolPages,
|
||||||
...blogPages,
|
...blogPages,
|
||||||
...learnPages,
|
...learnPages,
|
||||||
...authorPages,
|
...growthUseCasePages,
|
||||||
];
|
...authorPages,
|
||||||
}
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { ToastContainer } from '@/components/ui/Toast';
|
import { ToastContainer } from '@/components/ui/Toast';
|
||||||
import AuthProvider from '@/components/SessionProvider';
|
import AuthProvider from '@/components/SessionProvider';
|
||||||
import { PostHogProvider, PostHogPageView } from '@/components/PostHogProvider';
|
import { PostHogProvider } from '@/components/PostHogProvider';
|
||||||
import CookieBanner from '@/components/CookieBanner';
|
import CookieBanner from '@/components/CookieBanner';
|
||||||
import GoogleAnalytics from '@/components/analytics/GoogleAnalytics';
|
import GoogleAnalytics from '@/components/analytics/GoogleAnalytics';
|
||||||
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||||
|
|
||||||
export function Providers({ children }: { children: React.ReactNode }) {
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<PostHogProvider>
|
<PostHogProvider>
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<PostHogPageView />
|
<GoogleAnalytics />
|
||||||
<GoogleAnalytics />
|
<FacebookPixel />
|
||||||
<FacebookPixel />
|
</Suspense>
|
||||||
</Suspense>
|
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
{children}
|
{children}
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { ArrowRight } from "lucide-react";
|
||||||
|
|
||||||
|
import { TrackedCtaLink } from "@/components/marketing/MarketingAnalytics";
|
||||||
|
import { Card } from "@/components/ui/Card";
|
||||||
|
|
||||||
|
type PageType = "commercial" | "use_case_hub" | "use_case";
|
||||||
|
|
||||||
|
type GrowthLink = {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
ctaLabel: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function GrowthLinksSection({
|
||||||
|
eyebrow,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
links,
|
||||||
|
pageType,
|
||||||
|
cluster,
|
||||||
|
useCase,
|
||||||
|
}: {
|
||||||
|
eyebrow: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
links: GrowthLink[];
|
||||||
|
pageType: PageType;
|
||||||
|
cluster: string;
|
||||||
|
useCase?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<section className="py-20 bg-slate-50">
|
||||||
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl">
|
||||||
|
<div className="max-w-3xl mb-12">
|
||||||
|
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
|
||||||
|
{eyebrow}
|
||||||
|
</div>
|
||||||
|
<h2 className="mt-3 text-4xl font-bold text-slate-900">{title}</h2>
|
||||||
|
<p className="mt-4 text-xl text-slate-600">{description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 lg:grid-cols-4">
|
||||||
|
{links.map((link) => (
|
||||||
|
<TrackedCtaLink
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
ctaLabel={link.ctaLabel}
|
||||||
|
ctaLocation="related_workflows"
|
||||||
|
pageType={pageType}
|
||||||
|
cluster={cluster}
|
||||||
|
useCase={useCase}
|
||||||
|
className="group block h-full"
|
||||||
|
>
|
||||||
|
<Card className="h-full rounded-3xl border-slate-200 bg-white p-7 shadow-sm transition-all hover:-translate-y-1 hover:shadow-lg">
|
||||||
|
<div className="text-lg font-semibold text-slate-900">
|
||||||
|
{link.title}
|
||||||
|
</div>
|
||||||
|
<p className="mt-3 text-base leading-7 text-slate-600">
|
||||||
|
{link.description}
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 flex items-center gap-2 text-sm font-semibold text-blue-700">
|
||||||
|
<span>Open workflow</span>
|
||||||
|
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname, useSearchParams } from "next/navigation";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
import { trackEvent } from "@/components/PostHogProvider";
|
||||||
|
|
||||||
|
type PageType = "commercial" | "use_case_hub" | "use_case";
|
||||||
|
|
||||||
|
type TrackingContext = {
|
||||||
|
pageType: PageType;
|
||||||
|
cluster?: string;
|
||||||
|
useCase?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getUtmProperties(searchParams: ReturnType<typeof useSearchParams>) {
|
||||||
|
return {
|
||||||
|
utm_source: searchParams?.get("utm_source") || undefined,
|
||||||
|
utm_medium: searchParams?.get("utm_medium") || undefined,
|
||||||
|
utm_campaign: searchParams?.get("utm_campaign") || undefined,
|
||||||
|
utm_content: searchParams?.get("utm_content") || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MarketingPageTracker({
|
||||||
|
pageType,
|
||||||
|
cluster,
|
||||||
|
useCase,
|
||||||
|
}: TrackingContext) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pathname) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackEvent("landing_page_viewed", {
|
||||||
|
landing_page_slug: pathname,
|
||||||
|
page_type: pageType,
|
||||||
|
cluster,
|
||||||
|
use_case: useCase,
|
||||||
|
...getUtmProperties(searchParams),
|
||||||
|
});
|
||||||
|
}, [cluster, pageType, pathname, searchParams, useCase]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackedCtaLinkProps = TrackingContext & {
|
||||||
|
href: string;
|
||||||
|
ctaLabel: string;
|
||||||
|
ctaLocation: string;
|
||||||
|
destination?: string;
|
||||||
|
className?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TrackedCtaLink({
|
||||||
|
href,
|
||||||
|
ctaLabel,
|
||||||
|
ctaLocation,
|
||||||
|
destination,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
pageType,
|
||||||
|
cluster,
|
||||||
|
useCase,
|
||||||
|
}: TrackedCtaLinkProps) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
className={className}
|
||||||
|
onClick={() => {
|
||||||
|
trackEvent("cta_clicked", {
|
||||||
|
landing_page_slug: pathname,
|
||||||
|
page_type: pageType,
|
||||||
|
cluster,
|
||||||
|
use_case: useCase,
|
||||||
|
cta_label: ctaLabel,
|
||||||
|
cta_location: ctaLocation,
|
||||||
|
destination: destination || href,
|
||||||
|
...getUtmProperties(searchParams),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,427 @@
|
||||||
|
import type { FAQItem } from "@/lib/types";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
ArrowRight,
|
||||||
|
CheckCircle2,
|
||||||
|
Compass,
|
||||||
|
Link2,
|
||||||
|
Radar,
|
||||||
|
Sparkles,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import Breadcrumbs, { BreadcrumbItem } from "@/components/Breadcrumbs";
|
||||||
|
import SeoJsonLd from "@/components/SeoJsonLd";
|
||||||
|
import { FAQSection } from "@/components/aeo/FAQSection";
|
||||||
|
import {
|
||||||
|
MarketingPageTracker,
|
||||||
|
TrackedCtaLink,
|
||||||
|
} from "@/components/marketing/MarketingAnalytics";
|
||||||
|
import { AnswerFirstBlock } from "@/components/marketing/AnswerFirstBlock";
|
||||||
|
import { Button } from "@/components/ui/Button";
|
||||||
|
import { Card } from "@/components/ui/Card";
|
||||||
|
import { breadcrumbSchema, faqPageSchema } from "@/lib/schema";
|
||||||
|
|
||||||
|
type LinkCard = {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseCasePageTemplateProps = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
eyebrow: string;
|
||||||
|
intro: string;
|
||||||
|
pageType: "commercial" | "use_case";
|
||||||
|
cluster: string;
|
||||||
|
useCase?: string;
|
||||||
|
breadcrumbs: BreadcrumbItem[];
|
||||||
|
answer: string;
|
||||||
|
whenToUse: string[];
|
||||||
|
comparisonItems: {
|
||||||
|
label: string;
|
||||||
|
value: boolean;
|
||||||
|
text?: string;
|
||||||
|
}[];
|
||||||
|
howToSteps: string[];
|
||||||
|
primaryCta: {
|
||||||
|
href: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
secondaryCta: {
|
||||||
|
href: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
workflowTitle: string;
|
||||||
|
workflowIntro: string;
|
||||||
|
workflowCards: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}[];
|
||||||
|
checklistTitle: string;
|
||||||
|
checklist: string[];
|
||||||
|
supportLinks: LinkCard[];
|
||||||
|
faq: FAQItem[];
|
||||||
|
schemaData?: Record<string, unknown>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function buildUseCaseMetadata({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
canonicalPath,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
canonicalPath: string;
|
||||||
|
}): Metadata {
|
||||||
|
const canonical = `https://www.qrmaster.net${canonicalPath}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
absolute: `${title} | QR Master`,
|
||||||
|
},
|
||||||
|
description,
|
||||||
|
alternates: {
|
||||||
|
canonical,
|
||||||
|
languages: {
|
||||||
|
"x-default": canonical,
|
||||||
|
en: canonical,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: `${title} | QR Master`,
|
||||||
|
description,
|
||||||
|
url: canonical,
|
||||||
|
type: "website",
|
||||||
|
images: ["/og-image.png"],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
title: `${title} | QR Master`,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UseCasePageTemplate({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
eyebrow,
|
||||||
|
intro,
|
||||||
|
pageType,
|
||||||
|
cluster,
|
||||||
|
useCase,
|
||||||
|
breadcrumbs,
|
||||||
|
answer,
|
||||||
|
whenToUse,
|
||||||
|
comparisonItems,
|
||||||
|
howToSteps,
|
||||||
|
primaryCta,
|
||||||
|
secondaryCta,
|
||||||
|
workflowTitle,
|
||||||
|
workflowIntro,
|
||||||
|
workflowCards,
|
||||||
|
checklistTitle,
|
||||||
|
checklist,
|
||||||
|
supportLinks,
|
||||||
|
faq,
|
||||||
|
schemaData = [],
|
||||||
|
}: UseCasePageTemplateProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SeoJsonLd
|
||||||
|
data={[...schemaData, breadcrumbSchema(breadcrumbs), faqPageSchema(faq)]}
|
||||||
|
/>
|
||||||
|
<MarketingPageTracker
|
||||||
|
pageType={pageType}
|
||||||
|
cluster={cluster}
|
||||||
|
useCase={useCase}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="min-h-screen bg-white">
|
||||||
|
<section className="relative overflow-hidden bg-gradient-to-br from-slate-950 via-blue-950 to-cyan-900 text-white">
|
||||||
|
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(125,211,252,0.22),transparent_38%),radial-gradient(circle_at_bottom_right,rgba(255,255,255,0.08),transparent_30%)]" />
|
||||||
|
<div className="relative container mx-auto max-w-7xl px-4 py-20 sm:px-6 lg:px-8">
|
||||||
|
<Breadcrumbs
|
||||||
|
items={breadcrumbs}
|
||||||
|
className="[&_a]:text-blue-100/80 [&_a:hover]:text-white [&_span]:text-blue-100/80 [&_[aria-current=page]]:text-white"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid gap-12 lg:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)] lg:items-center">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/10 px-4 py-2 text-sm font-semibold text-cyan-100 shadow-lg shadow-cyan-950/30 backdrop-blur">
|
||||||
|
<Sparkles className="h-4 w-4" />
|
||||||
|
<span>{eyebrow}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-5">
|
||||||
|
<h1 className="max-w-4xl text-4xl font-bold tracking-tight text-white md:text-5xl lg:text-6xl">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
<p className="max-w-3xl text-lg leading-8 text-blue-50/88 md:text-xl">
|
||||||
|
{intro}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 text-sm text-blue-50/80 sm:grid-cols-2">
|
||||||
|
{[
|
||||||
|
"Built for QR workflows where the printed surface should stay stable.",
|
||||||
|
"Focused on operational clarity, not inflated ROI claims.",
|
||||||
|
"Connected to a commercial parent and sibling workflows.",
|
||||||
|
"Designed to fit QR Master's existing marketing theme.",
|
||||||
|
].map((line) => (
|
||||||
|
<div
|
||||||
|
key={line}
|
||||||
|
className="flex items-start gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-cyan-300" />
|
||||||
|
<span>{line}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 sm:flex-row">
|
||||||
|
<TrackedCtaLink
|
||||||
|
href={primaryCta.href}
|
||||||
|
ctaLabel={primaryCta.label}
|
||||||
|
ctaLocation="hero_primary"
|
||||||
|
pageType={pageType}
|
||||||
|
cluster={cluster}
|
||||||
|
useCase={useCase}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
className="w-full bg-white px-8 py-4 text-base font-semibold text-slate-950 hover:bg-slate-100 sm:w-auto"
|
||||||
|
>
|
||||||
|
{primaryCta.label}
|
||||||
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
|
||||||
|
<TrackedCtaLink
|
||||||
|
href={secondaryCta.href}
|
||||||
|
ctaLabel={secondaryCta.label}
|
||||||
|
ctaLocation="hero_secondary"
|
||||||
|
pageType={pageType}
|
||||||
|
cluster={cluster}
|
||||||
|
useCase={useCase}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="w-full border-white/30 bg-white/5 px-8 py-4 text-base text-white hover:bg-white/10 sm:w-auto"
|
||||||
|
>
|
||||||
|
{secondaryCta.label}
|
||||||
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="border-white/10 bg-white/10 p-8 text-white shadow-2xl shadow-slate-950/30 backdrop-blur">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between border-b border-white/10 pb-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs uppercase tracking-[0.24em] text-cyan-200/70">
|
||||||
|
Workflow snapshot
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-2xl font-semibold text-white">
|
||||||
|
What matters here
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Compass className="h-9 w-9 text-cyan-300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{workflowCards.map((card, index) => (
|
||||||
|
<div
|
||||||
|
key={card.title}
|
||||||
|
className="rounded-2xl border border-white/10 bg-slate-950/30 p-4"
|
||||||
|
>
|
||||||
|
<div className="mb-2 flex items-center gap-3">
|
||||||
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-cyan-400/15 text-sm font-semibold text-cyan-200">
|
||||||
|
{index + 1}
|
||||||
|
</div>
|
||||||
|
<div className="text-lg font-semibold text-white">
|
||||||
|
{card.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm leading-6 text-blue-50/80">
|
||||||
|
{card.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="container mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
|
||||||
|
<AnswerFirstBlock
|
||||||
|
whatIsIt={answer}
|
||||||
|
whenToUse={whenToUse}
|
||||||
|
comparison={{
|
||||||
|
leftTitle: "Static",
|
||||||
|
rightTitle: "Better fit here",
|
||||||
|
items: comparisonItems,
|
||||||
|
}}
|
||||||
|
howTo={{
|
||||||
|
steps: howToSteps,
|
||||||
|
}}
|
||||||
|
className="mt-0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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="mb-10 max-w-3xl">
|
||||||
|
<h2 className="text-3xl font-bold tracking-tight text-slate-900">
|
||||||
|
{workflowTitle}
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-lg leading-8 text-slate-600">
|
||||||
|
{workflowIntro}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 lg:grid-cols-3">
|
||||||
|
{workflowCards.map((card) => (
|
||||||
|
<Card
|
||||||
|
key={card.title}
|
||||||
|
className="rounded-3xl border-slate-200/80 bg-white p-7 shadow-sm"
|
||||||
|
>
|
||||||
|
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-2xl bg-blue-50 text-blue-700">
|
||||||
|
<Radar className="h-6 w-6" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-semibold text-slate-900">
|
||||||
|
{card.title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-3 text-base leading-7 text-slate-600">
|
||||||
|
{card.description}
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-16">
|
||||||
|
<div className="container mx-auto grid max-w-7xl gap-8 px-4 sm:px-6 lg:grid-cols-[minmax(0,0.95fr)_minmax(280px,0.8fr)] lg:px-8">
|
||||||
|
<Card className="rounded-3xl border-slate-200 bg-white p-8 shadow-sm">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-700">
|
||||||
|
Checklist
|
||||||
|
</div>
|
||||||
|
<h2 className="mt-3 text-3xl font-bold text-slate-900">
|
||||||
|
{checklistTitle}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<CheckCircle2 className="h-8 w-8 text-blue-700" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="mt-8 space-y-4">
|
||||||
|
{checklist.map((item) => (
|
||||||
|
<li
|
||||||
|
key={item}
|
||||||
|
className="flex items-start gap-3 text-slate-700"
|
||||||
|
>
|
||||||
|
<CheckCircle2 className="mt-1 h-5 w-5 shrink-0 text-green-600" />
|
||||||
|
<span className="leading-7">{item}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="rounded-3xl border-slate-200 bg-slate-950 p-8 text-white shadow-xl shadow-slate-200">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Link2 className="h-5 w-5 text-cyan-300" />
|
||||||
|
<h2 className="text-2xl font-bold">Related links</h2>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 space-y-4">
|
||||||
|
{supportLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="group block rounded-2xl border border-white/10 bg-white/5 p-4 transition-colors hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg font-semibold text-white">
|
||||||
|
{link.title}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-sm leading-6 text-blue-50/78">
|
||||||
|
{link.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ArrowRight className="mt-1 h-4 w-4 shrink-0 text-cyan-300 transition-transform group-hover:translate-x-1" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="container mx-auto max-w-5xl px-4 pb-6 sm:px-6 lg:px-8">
|
||||||
|
<FAQSection items={faq} title={`${title} FAQ`} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section className="pb-20 pt-6">
|
||||||
|
<div className="container mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="rounded-[2rem] bg-gradient-to-r from-blue-700 via-indigo-700 to-slate-900 px-8 py-10 text-white shadow-2xl shadow-blue-100">
|
||||||
|
<div className="flex flex-col gap-8 lg:flex-row lg:items-end lg:justify-between">
|
||||||
|
<div className="max-w-2xl">
|
||||||
|
<div className="text-sm font-semibold uppercase tracking-[0.22em] text-blue-100/80">
|
||||||
|
Next step
|
||||||
|
</div>
|
||||||
|
<h2 className="mt-3 text-3xl font-bold tracking-tight">
|
||||||
|
Use a QR workflow that stays useful after the print run starts.
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-lg leading-8 text-blue-50/84">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 sm:flex-row">
|
||||||
|
<TrackedCtaLink
|
||||||
|
href={primaryCta.href}
|
||||||
|
ctaLabel={primaryCta.label}
|
||||||
|
ctaLocation="footer_primary"
|
||||||
|
pageType={pageType}
|
||||||
|
cluster={cluster}
|
||||||
|
useCase={useCase}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
className="w-full bg-white px-7 text-slate-950 hover:bg-slate-100 sm:w-auto"
|
||||||
|
>
|
||||||
|
{primaryCta.label}
|
||||||
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
|
||||||
|
<TrackedCtaLink
|
||||||
|
href={secondaryCta.href}
|
||||||
|
ctaLabel={secondaryCta.label}
|
||||||
|
ctaLocation="footer_secondary"
|
||||||
|
pageType={pageType}
|
||||||
|
cluster={cluster}
|
||||||
|
useCase={useCase}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="w-full border-white/30 bg-white/5 text-white hover:bg-white/10 sm:w-auto"
|
||||||
|
>
|
||||||
|
{secondaryCta.label}
|
||||||
|
</Button>
|
||||||
|
</TrackedCtaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -47,10 +47,11 @@ export function Footer({ variant = 'marketing', t }: FooterProps) {
|
||||||
<li><Link href="/press" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Press</Link></li>
|
<li><Link href="/press" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Press</Link></li>
|
||||||
<li><Link href="/testimonials" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Testimonials</Link></li>
|
<li><Link href="/testimonials" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Testimonials</Link></li>
|
||||||
<li><Link href="/authors/timo" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Timo Knuth (Author)</Link></li>
|
<li><Link href="/authors/timo" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Timo Knuth (Author)</Link></li>
|
||||||
<li><Link href="/#pricing" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.pricing}</Link></li>
|
<li><Link href="/#pricing" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.pricing}</Link></li>
|
||||||
<li><Link href="/qr-code-tracking" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>QR Analytics</Link></li>
|
<li><Link href="/qr-code-tracking" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>QR Analytics</Link></li>
|
||||||
<li><Link href="/faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.faq}</Link></li>
|
<li><Link href="/use-cases" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Use Cases</Link></li>
|
||||||
<li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.blog}</Link></li>
|
<li><Link href="/faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.faq}</Link></li>
|
||||||
|
<li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.blog}</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -66,9 +67,10 @@ export function Footer({ variant = 'marketing', t }: FooterProps) {
|
||||||
|
|
||||||
<li><Link href="/reprint-calculator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Reprint Cost Calculator</Link></li>
|
<li><Link href="/reprint-calculator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Reprint Cost Calculator</Link></li>
|
||||||
<li><Link href="/qr-code-tracking" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Our Analytics</Link></li>
|
<li><Link href="/qr-code-tracking" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Our Analytics</Link></li>
|
||||||
<li><Link href="/manage-qr-codes" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Manage QR Codes</Link></li>
|
<li><Link href="/manage-qr-codes" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Manage QR Codes</Link></li>
|
||||||
<li><Link href="/custom-qr-code-generator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Custom QR</Link></li>
|
<li><Link href="/custom-qr-code-generator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Custom QR</Link></li>
|
||||||
<li><Link href="/tools/barcode-generator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Barcode Generator</Link></li>
|
<li><Link href="/qr-code-for-marketing-campaigns" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Campaign QR Codes</Link></li>
|
||||||
|
<li><Link href="/tools/barcode-generator" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Barcode Generator</Link></li>
|
||||||
<li><Link href="/guide/tracking-analytics" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Tracking Guide</Link></li>
|
<li><Link href="/guide/tracking-analytics" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Tracking Guide</Link></li>
|
||||||
<li><Link href="/guide/qr-code-best-practices" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Best Practices</Link></li>
|
<li><Link href="/guide/qr-code-best-practices" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Best Practices</Link></li>
|
||||||
<li><Link href="/guide/bulk-qr-code-generation" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Bulk Generation Guide</Link></li>
|
<li><Link href="/guide/bulk-qr-code-generation" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Bulk Generation Guide</Link></li>
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -148,7 +148,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> January 5, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -246,7 +246,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> October 18, 2025 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> October 18, 2025 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p>Coming soon: How to create trackable QR codes.</p></div>`,
|
<p>Coming soon: How to create trackable QR codes.</p></div>`,
|
||||||
|
|
@ -291,7 +291,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> October 17, 2025 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> October 17, 2025 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -339,7 +339,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> October 16, 2025 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> October 16, 2025 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -392,7 +392,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> October 16, 2025 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> October 16, 2025 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -446,7 +446,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> January 29, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> January 29, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h2>Free Barcode Generator</h2><p>Content coming soon.</p></div>`
|
<h2>Free Barcode Generator</h2><p>Content coming soon.</p></div>`
|
||||||
|
|
@ -499,7 +499,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 1, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 1, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -609,7 +609,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 4, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 4, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -719,7 +719,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 7, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 7, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -839,7 +839,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 10, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 10, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -867,13 +867,6 @@ export const blogPosts: BlogPost[] = [
|
||||||
<li>Best for “permanent” usage like Wi-Fi credentials</li>
|
<li>Best for “permanent” usage like Wi-Fi credentials</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Trackable / dynamic QR code</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Encodes a short redirect URL</li>
|
|
||||||
<li>Redirect logs scan events</li>
|
|
||||||
<li>Destination can be updated</li>
|
|
||||||
<li>Perfect for campaigns and printed materials</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Trackable / dynamic QR code</h3>
|
<h3>Trackable / dynamic QR code</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Encodes a short redirect URL</li>
|
<li>Encodes a short redirect URL</li>
|
||||||
|
|
@ -984,7 +977,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 13, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 13, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1168,7 +1161,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 16, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 16, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1321,7 +1314,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 19, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 19, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1484,7 +1477,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 22, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 22, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h2>Design Tips</h2><p>Content coming soon.</p></div>`
|
<h2>Design Tips</h2><p>Content coming soon.</p></div>`
|
||||||
|
|
@ -1533,7 +1526,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 25, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 25, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1716,7 +1709,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> February 28, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> February 28, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1859,7 +1852,7 @@ export const blogPosts: BlogPost[] = [
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> March 3, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> March 3, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2056,7 +2049,7 @@ Authorization: Bearer YOUR_API_KEY</code></pre>
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> March 6, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> March 6, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -2219,7 +2212,7 @@ Authorization: Bearer YOUR_API_KEY</code></pre>
|
||||||
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
<div class="post-metadata bg-blue-50 p-4 rounded-lg mb-8 border-l-4 border-blue-500">
|
||||||
<p class="text-sm text-gray-700">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
<strong>Author:</strong> Timo Knuth, QR Code & Marketing Expert<br/>
|
||||||
📅 <strong>Published:</strong> March 9, 2026 | <strong>Last updated:</strong> undefined NaN, NaN
|
📅 <strong>Published:</strong> March 9, 2026 | <strong>Last updated:</strong> January 26, 2026
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,11 @@ export function getPostBySlug(slug: string): BlogPost | undefined {
|
||||||
|
|
||||||
export function getPublishedPostBySlug(slug: string): BlogPost | undefined {
|
export function getPublishedPostBySlug(slug: string): BlogPost | undefined {
|
||||||
const p = getPostBySlug(slug);
|
const p = getPostBySlug(slug);
|
||||||
return p?.published ? p : undefined;
|
if (!p?.published) return undefined;
|
||||||
|
|
||||||
|
const currentDate = new Date();
|
||||||
|
const publishDate = p.datePublished ? new Date(p.datePublished) : new Date(p.date);
|
||||||
|
return publishDate <= currentDate ? p : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPostsByPillar(pillar: PillarKey): BlogPost[] {
|
export function getPostsByPillar(pillar: PillarKey): BlogPost[] {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,402 @@
|
||||||
|
import type { FAQItem } from "@/lib/types";
|
||||||
|
|
||||||
|
export type CommercialPageLink = {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
accent: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UseCaseLink = {
|
||||||
|
slug: string;
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
cluster: string;
|
||||||
|
summary: string;
|
||||||
|
parentHref: string;
|
||||||
|
parentTitle: string;
|
||||||
|
ctaLabel: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SupportResourceLink = {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UseCasePageContent = UseCaseLink & {
|
||||||
|
eyebrow: string;
|
||||||
|
titleSuffix: string;
|
||||||
|
metaDescription: string;
|
||||||
|
intro: string;
|
||||||
|
answer: string;
|
||||||
|
whenToUse: string[];
|
||||||
|
comparisonItems: {
|
||||||
|
label: string;
|
||||||
|
value: boolean;
|
||||||
|
text?: string;
|
||||||
|
}[];
|
||||||
|
howToSteps: string[];
|
||||||
|
workflowTitle: string;
|
||||||
|
workflowIntro: string;
|
||||||
|
workflowCards: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}[];
|
||||||
|
checklistTitle: string;
|
||||||
|
checklist: string[];
|
||||||
|
supportLinks: SupportResourceLink[];
|
||||||
|
faq: FAQItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const commercialPages: CommercialPageLink[] = [
|
||||||
|
{
|
||||||
|
href: "/dynamic-qr-code-generator",
|
||||||
|
title: "Dynamic QR Code Generator",
|
||||||
|
description: "Edit the destination after print and keep one QR live across changing campaigns.",
|
||||||
|
accent: "from-blue-600 to-cyan-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/qr-code-tracking",
|
||||||
|
title: "QR Code Tracking",
|
||||||
|
description: "Measure scans by placement, device, and timing when you need proof instead of guesswork.",
|
||||||
|
accent: "from-indigo-600 to-blue-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/custom-qr-code-generator",
|
||||||
|
title: "Custom QR Code Generator",
|
||||||
|
description: "Match printed QR codes to your brand system without losing scannability.",
|
||||||
|
accent: "from-sky-600 to-indigo-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/bulk-qr-code-generator",
|
||||||
|
title: "Bulk QR Code Generator",
|
||||||
|
description: "Create large sets for labels, event materials, and repeatable offline workflows.",
|
||||||
|
accent: "from-cyan-600 to-sky-500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/qr-code-for-marketing-campaigns",
|
||||||
|
title: "QR Codes for Marketing Campaigns",
|
||||||
|
description: "Plan campaign QR workflows around attribution, creative testing, and print distribution.",
|
||||||
|
accent: "from-slate-800 to-blue-600",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const featuredUseCases: UseCaseLink[] = [
|
||||||
|
{
|
||||||
|
slug: "restaurant-menu-qr-codes",
|
||||||
|
href: "/use-cases/restaurant-menu-qr-codes",
|
||||||
|
title: "Restaurant Menu QR Codes",
|
||||||
|
cluster: "restaurants",
|
||||||
|
summary: "Keep printed table cards useful when menu links, prices, specials, or hours change.",
|
||||||
|
parentHref: "/dynamic-qr-code-generator",
|
||||||
|
parentTitle: "Dynamic QR Code Generator",
|
||||||
|
ctaLabel: "Create your restaurant menu QR",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "business-card-qr-codes",
|
||||||
|
href: "/use-cases/business-card-qr-codes",
|
||||||
|
title: "Business Card QR Codes",
|
||||||
|
cluster: "business-cards",
|
||||||
|
summary: "Send contacts to a current profile, booking page, or vCard without reprinting cards.",
|
||||||
|
parentHref: "/dynamic-qr-code-generator",
|
||||||
|
parentTitle: "Dynamic QR Code Generator",
|
||||||
|
ctaLabel: "Create your business card QR",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: "event-qr-codes",
|
||||||
|
href: "/use-cases/event-qr-codes",
|
||||||
|
title: "Event QR Codes",
|
||||||
|
cluster: "events",
|
||||||
|
summary: "Split operational and campaign QR flows so schedules and tracking stay manageable on event day.",
|
||||||
|
parentHref: "/qr-code-tracking",
|
||||||
|
parentTitle: "QR Code Tracking",
|
||||||
|
ctaLabel: "Create your event QR code",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const upcomingUseCaseIdeas: SupportResourceLink[] = [
|
||||||
|
{
|
||||||
|
href: "/bulk-qr-code-generator",
|
||||||
|
title: "Packaging and label workflows",
|
||||||
|
description: "Best next cluster for bulk creation, manuals, inserts, and recurring product updates.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/qr-code-for-marketing-campaigns",
|
||||||
|
title: "Flyer and brochure campaigns",
|
||||||
|
description: "Strong print-intent cluster once the marketing-campaign commercial parent is live.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/qr-code-tracking",
|
||||||
|
title: "Feedback and coupon journeys",
|
||||||
|
description: "Good second-wave pages once tracking and CTA attribution are stable.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const supportResources: SupportResourceLink[] = [
|
||||||
|
{
|
||||||
|
href: "/learn/use-cases",
|
||||||
|
title: "Learn: Use Cases",
|
||||||
|
description: "Editorial pillar page for educational browsing and broader QR workflow discovery.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/blog/dynamic-vs-static-qr-codes",
|
||||||
|
title: "Dynamic vs Static QR Codes",
|
||||||
|
description: "Explainer for the operational difference between fixed and editable QR destinations.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/blog/utm-parameter-qr-codes",
|
||||||
|
title: "UTM Parameters with QR Codes",
|
||||||
|
description: "Reference guide for campaign naming, placement attribution, and offline measurement.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const useCasePageContent: Record<string, UseCasePageContent> = {
|
||||||
|
"restaurant-menu-qr-codes": {
|
||||||
|
...featuredUseCases[0],
|
||||||
|
eyebrow: "Restaurants",
|
||||||
|
titleSuffix: "for Restaurants, Cafes, and Changing Menus",
|
||||||
|
metaDescription:
|
||||||
|
"Use restaurant menu QR codes to keep printed table cards useful when menu links, pricing, or specials change.",
|
||||||
|
intro:
|
||||||
|
"Restaurant menu QR codes work best when the printed code stays the same but the menu destination can change as your service changes.",
|
||||||
|
answer:
|
||||||
|
"A restaurant menu QR code should point to a mobile-friendly menu that you can update without replacing every printed card, flyer, or table tent.",
|
||||||
|
whenToUse: [
|
||||||
|
"Your menu link changes seasonally, weekly, or during service.",
|
||||||
|
"You want one printed QR on tables, windows, takeaway inserts, or flyers.",
|
||||||
|
"You need to route customers to the right menu, order flow, or special page without reprinting.",
|
||||||
|
],
|
||||||
|
comparisonItems: [
|
||||||
|
{ label: "Menu destination", text: "Fixed once printed", value: true },
|
||||||
|
{ label: "Last-minute updates", text: "Reprint required", value: true },
|
||||||
|
{ label: "Campaign tracking", text: "Limited", value: true },
|
||||||
|
],
|
||||||
|
howToSteps: [
|
||||||
|
"Create one dynamic menu QR and place it on your printed surfaces.",
|
||||||
|
"Send scanners to your current menu, order page, or daily specials page.",
|
||||||
|
"Update the destination when the menu or campaign changes instead of replacing the code.",
|
||||||
|
],
|
||||||
|
workflowTitle: "What a good restaurant QR setup should handle",
|
||||||
|
workflowIntro:
|
||||||
|
"The QR code is not the product. The workflow behind it is. A good restaurant setup keeps print stable while operations stay flexible.",
|
||||||
|
workflowCards: [
|
||||||
|
{
|
||||||
|
title: "Stable table cards",
|
||||||
|
description: "Keep one printed QR on tables and point it to the current menu, lunch card, or ordering page.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Seasonal offers",
|
||||||
|
description: "Swap specials, tasting menus, or holiday landing pages without touching the printed material.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Placement tracking",
|
||||||
|
description: "Use different menu or campaign destinations by location so you can compare tables, windows, and takeaway inserts.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
checklistTitle: "Restaurant QR checklist",
|
||||||
|
checklist: [
|
||||||
|
"Use a mobile-first landing page instead of a hard-to-read PDF where possible.",
|
||||||
|
"Keep the same printed QR on every stable surface you do not want to replace often.",
|
||||||
|
"Use CTA copy like 'Scan for menu' or 'Scan for today's specials' so the scan intent is obvious.",
|
||||||
|
"Pair campaign placements with trackable destinations when you test takeaway or window traffic.",
|
||||||
|
],
|
||||||
|
supportLinks: [
|
||||||
|
{
|
||||||
|
href: "/dynamic-qr-code-generator",
|
||||||
|
title: "Commercial parent: Dynamic QR Code Generator",
|
||||||
|
description: "Best fit when the real need is editing the destination after print.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/blog/qr-code-restaurant-menu",
|
||||||
|
title: "Restaurant menu guide",
|
||||||
|
description: "Existing editorial asset with menu placement and implementation basics.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/use-cases/business-card-qr-codes",
|
||||||
|
title: "Sibling page: Business Card QR Codes",
|
||||||
|
description: "Useful example of another print-first workflow where the destination changes over time.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
faq: [
|
||||||
|
{
|
||||||
|
question: "Should a restaurant menu QR code be static or dynamic?",
|
||||||
|
answer: "Use a dynamic QR code when the menu destination may change. That lets you update the landing page without replacing your printed materials.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "What should a restaurant menu QR code link to?",
|
||||||
|
answer: "Link to a mobile-friendly menu page, ordering page, or a short service hub that helps customers reach the right menu fast.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Can I use one restaurant QR code in multiple places?",
|
||||||
|
answer: "Yes. One stable code can be reused across table cards, flyers, and takeaway materials, especially when the destination is managed dynamically.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"business-card-qr-codes": {
|
||||||
|
...featuredUseCases[1],
|
||||||
|
eyebrow: "Business Cards",
|
||||||
|
titleSuffix: "for Contact Sharing, Bookings, and Portfolio Links",
|
||||||
|
metaDescription:
|
||||||
|
"Use business card QR codes to share a current contact page, vCard, booking link, or portfolio without reprinting cards.",
|
||||||
|
intro:
|
||||||
|
"Business card QR codes are most useful when your contact destination changes faster than your print inventory.",
|
||||||
|
answer:
|
||||||
|
"A business card QR code should send people to the best next action today, whether that is saving your contact, opening a booking link, or visiting a current profile page.",
|
||||||
|
whenToUse: [
|
||||||
|
"Your role, booking link, or portfolio changes more often than your printed cards.",
|
||||||
|
"You want one card to work for networking, sales follow-up, and contact saving.",
|
||||||
|
"You need a cleaner handoff than asking people to type a URL from a small card.",
|
||||||
|
],
|
||||||
|
comparisonItems: [
|
||||||
|
{ label: "Destination flexibility", text: "Fixed once printed", value: true },
|
||||||
|
{ label: "Contact updates", text: "New cards needed", value: true },
|
||||||
|
{ label: "Action routing", text: "Single fixed page", value: true },
|
||||||
|
],
|
||||||
|
howToSteps: [
|
||||||
|
"Choose the real action you want after the scan: save contact, book time, or view work.",
|
||||||
|
"Generate a QR code that sends people to that destination or to a vCard-capable landing page.",
|
||||||
|
"Keep the print the same and update the linked destination when your details evolve.",
|
||||||
|
],
|
||||||
|
workflowTitle: "Where business card QR codes pay off",
|
||||||
|
workflowIntro:
|
||||||
|
"Printed cards still work. The problem is that the destination behind them often gets stale first.",
|
||||||
|
workflowCards: [
|
||||||
|
{
|
||||||
|
title: "Current contact flow",
|
||||||
|
description: "Send scanners to a vCard or current contact page so the next step is saving your details, not typing them.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Role or profile updates",
|
||||||
|
description: "Update the destination if you change company, title, offer, or booking link while old cards are still in circulation.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Context-aware follow-up",
|
||||||
|
description: "Point event-specific cards or team variants to the most relevant landing page instead of one generic homepage.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
checklistTitle: "Business card QR checklist",
|
||||||
|
checklist: [
|
||||||
|
"Pick one primary post-scan action instead of trying to send every scanner to everything at once.",
|
||||||
|
"Make the landing page useful on mobile because most business-card scans happen on phones.",
|
||||||
|
"Use CTA text such as 'Scan to save my contact' or 'Scan to book a call'.",
|
||||||
|
"Test the print size and contrast before ordering a large run.",
|
||||||
|
],
|
||||||
|
supportLinks: [
|
||||||
|
{
|
||||||
|
href: "/dynamic-qr-code-generator",
|
||||||
|
title: "Commercial parent: Dynamic QR Code Generator",
|
||||||
|
description: "Use when the printed card stays constant but the best destination changes.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/tools/vcard-qr-code",
|
||||||
|
title: "vCard QR Code tool",
|
||||||
|
description: "Free tool for contact-saving workflows when a vCard is the best immediate action.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/use-cases/event-qr-codes",
|
||||||
|
title: "Sibling page: Event QR Codes",
|
||||||
|
description: "Another workflow where temporary destinations and scan context matter.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
faq: [
|
||||||
|
{
|
||||||
|
question: "What should a business card QR code link to?",
|
||||||
|
answer: "The best destination is the one next step you want most: a vCard, booking page, contact hub, or current portfolio page.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Do business card QR codes need to be dynamic?",
|
||||||
|
answer: "They should be dynamic if your destination may change over the life of the printed card. That keeps old cards useful longer.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Can a business card QR code send people straight to contact saving?",
|
||||||
|
answer: "Yes. A vCard QR or a landing page with clear save-contact options is often the simplest and most practical post-scan action.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"event-qr-codes": {
|
||||||
|
...featuredUseCases[2],
|
||||||
|
eyebrow: "Events",
|
||||||
|
titleSuffix: "for Check-In, Schedules, Booths, and Campaign Tracking",
|
||||||
|
metaDescription:
|
||||||
|
"Use event QR codes for schedules, check-in flows, and trackable campaign placements across signs, flyers, and booths.",
|
||||||
|
intro:
|
||||||
|
"Event QR codes work best when you separate operational QR flows from promotional ones and keep your printed placements easy to manage.",
|
||||||
|
answer:
|
||||||
|
"A good event QR setup uses different QR destinations for different jobs: operations, attendee utility, and campaign measurement should not all depend on one code.",
|
||||||
|
whenToUse: [
|
||||||
|
"Your event schedule, map, or resources may change close to event day.",
|
||||||
|
"You want to compare booth, banner, flyer, or badge placements instead of treating every scan as one bucket.",
|
||||||
|
"You need one event QR system that supports both attendee utility and marketing follow-up.",
|
||||||
|
],
|
||||||
|
comparisonItems: [
|
||||||
|
{ label: "Schedule changes", text: "New print may be needed", value: true },
|
||||||
|
{ label: "Placement reporting", text: "Weak by default", value: true },
|
||||||
|
{ label: "Operational vs campaign flows", text: "Often mixed", value: true },
|
||||||
|
],
|
||||||
|
howToSteps: [
|
||||||
|
"Split event QR codes by purpose: check-in, attendee info, and campaign placements.",
|
||||||
|
"Use trackable destinations for banners, booth assets, and flyers where placement performance matters.",
|
||||||
|
"Keep fast-changing resources on destinations you can update without replacing the printed code.",
|
||||||
|
],
|
||||||
|
workflowTitle: "Event workflows worth designing on purpose",
|
||||||
|
workflowIntro:
|
||||||
|
"Events generate scans in very different contexts. Treating them as one generic QR use case leaves both operations and measurement weaker.",
|
||||||
|
workflowCards: [
|
||||||
|
{
|
||||||
|
title: "Operational QR flows",
|
||||||
|
description: "Use dedicated QR paths for check-in, schedules, maps, and attendee resources that may shift before or during the event.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Booth and banner tracking",
|
||||||
|
description: "Track scans from distinct placements so you can compare booth creatives, sponsor zones, or call-to-action angles.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Post-event follow-up",
|
||||||
|
description: "Route scanners to the most relevant recap, booking, or lead capture page after the event without changing the printed assets.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
checklistTitle: "Event QR checklist",
|
||||||
|
checklist: [
|
||||||
|
"Do not force one QR code to handle operations, schedule updates, and lead-gen at the same time.",
|
||||||
|
"Use descriptive CTA copy like 'Scan for agenda' or 'Scan for booth resources'.",
|
||||||
|
"Track campaign placements separately so booth banners and flyers are comparable.",
|
||||||
|
"Test glare, print size, and placement distance on the real event materials before the event starts.",
|
||||||
|
],
|
||||||
|
supportLinks: [
|
||||||
|
{
|
||||||
|
href: "/qr-code-tracking",
|
||||||
|
title: "Commercial parent: QR Code Tracking",
|
||||||
|
description: "Best fit when the priority is measuring placement and scan context across the event.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/tools/event-qr-code",
|
||||||
|
title: "Event QR Code tool",
|
||||||
|
description: "Useful for save-the-date and calendar workflows that sit alongside broader event QR strategy.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/use-cases/restaurant-menu-qr-codes",
|
||||||
|
title: "Sibling page: Restaurant Menu QR Codes",
|
||||||
|
description: "Another example of a printed workflow where the content behind the QR changes often.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
faq: [
|
||||||
|
{
|
||||||
|
question: "Should an event use one QR code or several?",
|
||||||
|
answer: "Several is usually better. Separate operational QR codes from campaign QR codes so schedules, check-in, and attribution do not compete for one destination.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Can event QR codes be updated after print?",
|
||||||
|
answer: "Yes, if the destination is managed dynamically. That is useful for schedules, resource hubs, and post-event follow-up pages.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "How do I measure which event placement performs best?",
|
||||||
|
answer: "Use distinct destinations or tagged URLs for each placement so banner, booth, badge, and flyer traffic can be compared cleanly.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getUseCasePage(slug: string): UseCasePageContent | undefined {
|
||||||
|
return useCasePageContent[slug];
|
||||||
|
}
|
||||||
|
|
@ -62,27 +62,27 @@ export function getAllIndexableUrls(): string[] {
|
||||||
const blogPages = blogPosts.map(post => `${baseUrl}/blog/${post.slug}`);
|
const blogPages = blogPosts.map(post => `${baseUrl}/blog/${post.slug}`);
|
||||||
|
|
||||||
// Main pages (synced with sitemap.ts)
|
// Main pages (synced with sitemap.ts)
|
||||||
const mainPages = [
|
const mainPages = [
|
||||||
baseUrl,
|
baseUrl,
|
||||||
`${baseUrl}/qr-code-erstellen`,
|
`${baseUrl}/about`,
|
||||||
`${baseUrl}/qr-code-tracking`,
|
`${baseUrl}/contact`,
|
||||||
`${baseUrl}/reprint-calculator`,
|
`${baseUrl}/press`,
|
||||||
`${baseUrl}/dynamic-qr-code-generator`,
|
`${baseUrl}/testimonials`,
|
||||||
`${baseUrl}/bulk-qr-code-generator`,
|
`${baseUrl}/qr-code-erstellen`,
|
||||||
|
`${baseUrl}/qr-code-tracking`,
|
||||||
|
`${baseUrl}/reprint-calculator`,
|
||||||
|
`${baseUrl}/dynamic-qr-code-generator`,
|
||||||
|
`${baseUrl}/bulk-qr-code-generator`,
|
||||||
`${baseUrl}/custom-qr-code-generator`,
|
`${baseUrl}/custom-qr-code-generator`,
|
||||||
`${baseUrl}/manage-qr-codes`,
|
`${baseUrl}/manage-qr-codes`,
|
||||||
`${baseUrl}/pricing`,
|
`${baseUrl}/pricing`,
|
||||||
`${baseUrl}/tools`,
|
`${baseUrl}/tools`,
|
||||||
`${baseUrl}/features`,
|
`${baseUrl}/features`,
|
||||||
`${baseUrl}/faq`,
|
`${baseUrl}/faq`,
|
||||||
`${baseUrl}/blog`,
|
`${baseUrl}/blog`,
|
||||||
`${baseUrl}/signup`,
|
`${baseUrl}/privacy`,
|
||||||
`${baseUrl}/login`,
|
`${baseUrl}/newsletter`,
|
||||||
`${baseUrl}/privacy`,
|
];
|
||||||
`${baseUrl}/guide/tracking-analytics`,
|
|
||||||
`${baseUrl}/guide/bulk-qr-code-generation`,
|
|
||||||
`${baseUrl}/guide/qr-code-best-practices`,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Learn hub and pillar pages
|
// Learn hub and pillar pages
|
||||||
const learnPages = [
|
const learnPages = [
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,15 @@ export function middleware(req: NextRequest) {
|
||||||
if (path === '/guide/bulk-qr-code-generation') {
|
if (path === '/guide/bulk-qr-code-generation') {
|
||||||
return NextResponse.redirect(new URL('/learn/developer', req.url), 301);
|
return NextResponse.redirect(new URL('/learn/developer', req.url), 301);
|
||||||
}
|
}
|
||||||
if (path === '/guide/qr-code-best-practices') {
|
if (path === '/guide/qr-code-best-practices') {
|
||||||
return NextResponse.redirect(new URL('/learn/basics', req.url), 301);
|
return NextResponse.redirect(new URL('/learn/basics', req.url), 301);
|
||||||
}
|
}
|
||||||
|
if (path === '/create-qr') {
|
||||||
// Public routes that don't require authentication
|
return NextResponse.redirect(new URL('/dynamic-qr-code-generator', req.url), 301);
|
||||||
const publicPaths = [
|
}
|
||||||
|
|
||||||
|
// Public routes that don't require authentication
|
||||||
|
const publicPaths = [
|
||||||
'/',
|
'/',
|
||||||
'/pricing',
|
'/pricing',
|
||||||
'/faq',
|
'/faq',
|
||||||
|
|
@ -42,11 +45,13 @@ export function middleware(req: NextRequest) {
|
||||||
'/display',
|
'/display',
|
||||||
'/contact',
|
'/contact',
|
||||||
'/about',
|
'/about',
|
||||||
'/learn',
|
'/learn',
|
||||||
'/authors',
|
'/use-cases',
|
||||||
'/press',
|
'/authors',
|
||||||
'/testimonials',
|
'/press',
|
||||||
];
|
'/testimonials',
|
||||||
|
'/qr-code-for-marketing-campaigns',
|
||||||
|
];
|
||||||
|
|
||||||
// Check if path is public
|
// Check if path is public
|
||||||
const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/'));
|
const isPublicPath = publicPaths.some(p => path === p || path.startsWith(p + '/'));
|
||||||
|
|
@ -95,4 +100,4 @@ export const config = {
|
||||||
*/
|
*/
|
||||||
'/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)',
|
'/((?!_next/static|_next/image|favicon.ico|logo.svg|og-image.png).*)',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue