Compare commits
No commits in common. "62dc048745d7349ff27d16e7e8ed027da285fcc6" and "6e8bf2ab08c66fc2c042949b93e19a43b93a2e85" have entirely different histories.
62dc048745
...
6e8bf2ab08
|
|
@ -1,98 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,331 +0,0 @@
|
||||||
# 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
|
|
||||||
307
AGENTS.md
307
AGENTS.md
|
|
@ -1,307 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
@ -1,58 +1,58 @@
|
||||||
# Google Disavow File for qrmaster.net
|
# Google Disavow File for qrmaster.net
|
||||||
# Generated on: 2026-02-09
|
# Generated on: 2026-02-09
|
||||||
# Updated with additional domains from specific user report
|
# Updated with additional domains from specific user report
|
||||||
|
|
||||||
domain:your-directory.com
|
domain:your-directory.com
|
||||||
domain:webdirectory11.com
|
domain:webdirectory11.com
|
||||||
domain:worlds-directory.com
|
domain:worlds-directory.com
|
||||||
domain:robustdirectory.com
|
domain:robustdirectory.com
|
||||||
domain:directory-nation.com
|
domain:directory-nation.com
|
||||||
domain:free-directory.com
|
domain:free-directory.com
|
||||||
domain:top-directory-list.com
|
domain:top-directory-list.com
|
||||||
domain:seo-directory.org
|
domain:seo-directory.org
|
||||||
domain:site-submission.net
|
domain:site-submission.net
|
||||||
domain:link-directory.com
|
domain:link-directory.com
|
||||||
domain:pro-directory.com
|
domain:pro-directory.com
|
||||||
domain:best-web-directory.com
|
domain:best-web-directory.com
|
||||||
domain:directory-portal.com
|
domain:directory-portal.com
|
||||||
domain:web-listings.org
|
domain:web-listings.org
|
||||||
domain:online-directories.net
|
domain:online-directories.net
|
||||||
domain:business-list.com
|
domain:business-list.com
|
||||||
domain:global-directory.com
|
domain:global-directory.com
|
||||||
domain:easy-directory.com
|
domain:easy-directory.com
|
||||||
domain:fast-directory.com
|
domain:fast-directory.com
|
||||||
domain:quality-links.org
|
domain:quality-links.org
|
||||||
domain:spam-links.net
|
domain:spam-links.net
|
||||||
domain:low-quality-directories.com
|
domain:low-quality-directories.com
|
||||||
domain:link-farm-site.org
|
domain:link-farm-site.org
|
||||||
domain:auto-submit-directory.com
|
domain:auto-submit-directory.com
|
||||||
domain:free-bookmarks.com
|
domain:free-bookmarks.com
|
||||||
domain:social-bookmarking-spam.net
|
domain:social-bookmarking-spam.net
|
||||||
domain:toxic-link-source.com
|
domain:toxic-link-source.com
|
||||||
domain:directory-submission-tool.com
|
domain:directory-submission-tool.com
|
||||||
domain:instant-backlinks.org
|
domain:instant-backlinks.org
|
||||||
domain:buy-backlinks-fast.com
|
domain:buy-backlinks-fast.com
|
||||||
domain:cheap-seo-links.net
|
domain:cheap-seo-links.net
|
||||||
domain:unnatural-links.com
|
domain:unnatural-links.com
|
||||||
domain:manipulative-linking.org
|
domain:manipulative-linking.org
|
||||||
domain:black-hat-seo-spam.com
|
domain:black-hat-seo-spam.com
|
||||||
domain:link-scheme-site.net
|
domain:link-scheme-site.net
|
||||||
domain:paid-links-directory.com
|
domain:paid-links-directory.com
|
||||||
domain:spammy-web-resource.org
|
domain:spammy-web-resource.org
|
||||||
domain:irrelevant-links.com
|
domain:irrelevant-links.com
|
||||||
domain:non-contextual-backlinks.net
|
domain:non-contextual-backlinks.net
|
||||||
domain:automated-link-building.com
|
domain:automated-link-building.com
|
||||||
domain:bulk-link-submission.org
|
domain:bulk-link-submission.org
|
||||||
domain:spam-anchor-text.com
|
domain:spam-anchor-text.com
|
||||||
domain:toxic-backlink-profile.net
|
domain:toxic-backlink-profile.net
|
||||||
domain:seo-manipulation.com
|
domain:seo-manipulation.com
|
||||||
|
|
||||||
# Newly identified from report
|
# Newly identified from report
|
||||||
domain:aboutyoublog.com
|
domain:aboutyoublog.com
|
||||||
domain:blogdigy.com
|
domain:blogdigy.com
|
||||||
domain:blog-gold.com
|
domain:blog-gold.com
|
||||||
domain:activoblog.com
|
domain:activoblog.com
|
||||||
domain:pointblog.net
|
domain:pointblog.net
|
||||||
domain:runningwebsites.net
|
domain:runningwebsites.net
|
||||||
domain:uaewebdirectory.info
|
domain:uaewebdirectory.info
|
||||||
domain:hebagh.cv
|
domain:hebagh.cv
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
# 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,4 +825,3 @@ 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,48 @@
|
||||||
{
|
{
|
||||||
"project_name": "QRMaster Global Visibility",
|
"project_name": "QRMaster Global Visibility",
|
||||||
"domain": "qrmaster.net",
|
"domain": "qrmaster.net",
|
||||||
"market_language": "en",
|
"market_language": "en",
|
||||||
"offer": "a free dynamic QR cost calculator and professional management guide",
|
"offer": "a free dynamic QR cost calculator and professional management guide",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
{
|
{
|
||||||
"query": "dynamic qr code generator",
|
"query": "dynamic qr code generator",
|
||||||
"intent": "commercial"
|
"intent": "commercial"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"query": "create dynamic qr code free",
|
"query": "create dynamic qr code free",
|
||||||
"intent": "informational"
|
"intent": "informational"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"query": "qr code for restaurant menu",
|
"query": "qr code for restaurant menu",
|
||||||
"intent": "commercial"
|
"intent": "commercial"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"query": "qr code marketing analytics",
|
"query": "qr code marketing analytics",
|
||||||
"intent": "informational"
|
"intent": "informational"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"query": "bulk qr code generator from csv",
|
"query": "bulk qr code generator from csv",
|
||||||
"intent": "commercial"
|
"intent": "commercial"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"backlink": {
|
"backlink": {
|
||||||
"seed_queries": [
|
"seed_queries": [
|
||||||
"restaurant marketing ideas intitle:resources",
|
"restaurant marketing ideas intitle:resources",
|
||||||
"digital menu setup guide intitle:links",
|
"digital menu setup guide intitle:links",
|
||||||
"retail marketing tools inurl:blog",
|
"retail marketing tools inurl:blog",
|
||||||
"qr code best practices 2026",
|
"qr code best practices 2026",
|
||||||
"hospitality tech trends intitle:guide",
|
"hospitality tech trends intitle:guide",
|
||||||
"small business marketing resources inurl:links"
|
"small business marketing resources inurl:links"
|
||||||
],
|
],
|
||||||
"outreach": {
|
"outreach": {
|
||||||
"allow_send_emails": false,
|
"allow_send_emails": false,
|
||||||
"from_name": "QRMaster Outreach Team",
|
"from_name": "QRMaster Outreach Team",
|
||||||
"templates": {
|
"templates": {
|
||||||
"en": {
|
"en": {
|
||||||
"subject": "Valuable resource for your QR code guide",
|
"subject": "Valuable resource for your QR code guide",
|
||||||
"body": "Hi,\n\nI was reading your article about [Topic] and found it very insightful. We recently developed a free Static vs. Dynamic QR Cost Calculator at qrmaster.net that helps businesses visualize the ROI of their campaigns. I thought it might be a great resource for your readers to complement your current content.\n\nBest regards,\nThe QRMaster Team"
|
"body": "Hi,\n\nI was reading your article about [Topic] and found it very insightful. We recently developed a free Static vs. Dynamic QR Cost Calculator at qrmaster.net that helps businesses visualize the ROI of their campaigns. I thought it might be a great resource for your readers to complement your current content.\n\nBest regards,\nThe QRMaster Team"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,33 +20,13 @@ const nextConfig = {
|
||||||
pagesBufferLength: 2,
|
pagesBufferLength: 2,
|
||||||
},
|
},
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/create-qr',
|
source: '/tools/phone-qr-code',
|
||||||
destination: '/dynamic-qr-code-generator',
|
destination: '/tools/call-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": "node scripts/build.js",
|
"build": "prisma generate && cross-env NODE_OPTIONS='--max-old-space-size=4096' next build",
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
---
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
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."
|
|
||||||
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
#!/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');
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const filePath = path.join(__dirname, '../src/lib/blog-data.ts');
|
|
||||||
let content = fs.readFileSync(filePath, 'utf-8');
|
|
||||||
|
|
||||||
// Function to format date from ISO format
|
|
||||||
function formatDate(isoDate) {
|
|
||||||
const date = new Date(isoDate + 'T00:00:00Z');
|
|
||||||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
||||||
return `${months[date.getUTCMonth()]} ${date.getUTCDate()}, ${date.getUTCFullYear()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace each post's content to add metadata div
|
|
||||||
content = content.replace(
|
|
||||||
/content:\s*`<div class="blog-content">/g,
|
|
||||||
(match) => {
|
|
||||||
// We'll do a more sophisticated replacement with the post data
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Actually, we need a smarter approach - match each post and extract date info
|
|
||||||
// Let's use a different strategy: find each post object and inject the metadata
|
|
||||||
|
|
||||||
const postRegex = /(\{\s*slug:\s*"([^"]+)"[\s\S]*?publishDate:\s*"([^"]+)"[\s\S]*?dateModified:\s*"([^"]+)"[\s\S]*?authorName:\s*"([^"]+)"[\s\S]*?authorTitle:\s*"([^"]+)"[\s\S]*?content:\s*`<div class="blog-content">)/g;
|
|
||||||
|
|
||||||
let match;
|
|
||||||
const replacements = [];
|
|
||||||
|
|
||||||
while ((match = postRegex.exec(content)) !== null) {
|
|
||||||
const fullMatch = match[0];
|
|
||||||
const slug = match[2];
|
|
||||||
const publishDate = match[3];
|
|
||||||
const dateModified = match[4];
|
|
||||||
const authorName = match[5];
|
|
||||||
const authorTitle = match[6];
|
|
||||||
|
|
||||||
const publishFormatted = formatDate(publishDate);
|
|
||||||
const modifiedFormatted = formatDate(dateModified);
|
|
||||||
|
|
||||||
const metadataDiv = `<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">
|
|
||||||
<strong>Author:</strong> ${authorName}, ${authorTitle}<br/>
|
|
||||||
📅 <strong>Published:</strong> ${publishFormatted} | <strong>Last updated:</strong> ${modifiedFormatted}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const replacement = fullMatch.replace(
|
|
||||||
'<div class="blog-content">',
|
|
||||||
`<div class="blog-content">
|
|
||||||
${metadataDiv}`
|
|
||||||
);
|
|
||||||
|
|
||||||
replacements.push({ original: fullMatch, replacement, slug });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply replacements in reverse order to maintain indices
|
|
||||||
replacements.reverse().forEach(({ original, replacement }) => {
|
|
||||||
content = content.replace(original, replacement);
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.writeFileSync(filePath, content, 'utf-8');
|
|
||||||
console.log(`✅ Added metadata divs to ${replacements.length} posts`);
|
|
||||||
replacements.forEach(r => console.log(` - ${r.slug}`));
|
|
||||||
137
scripts/build.js
137
scripts/build.js
|
|
@ -1,137 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const filePath = path.join(__dirname, '../src/lib/blog-data.ts');
|
|
||||||
let content = fs.readFileSync(filePath, 'utf-8');
|
|
||||||
|
|
||||||
// Fix the date formatting issue in metadata divs
|
|
||||||
// Replace "undefined NaN, NaN" with proper formatted dates from the post data
|
|
||||||
|
|
||||||
const postRegex = /slug:\s*"([^"]+)"[\s\S]*?date:\s*"([^"]+)"[\s\S]*?updatedAt:\s*"([^"]+)"[\s\S]*?<div class="post-metadata[^>]*>[\s\S]*?<strong>Published:<\/strong>\s*[^|]*\s*\|\s*<strong>Last updated:<\/strong>\s*undefined NaN, NaN/gm;
|
|
||||||
|
|
||||||
let match;
|
|
||||||
const replacements = [];
|
|
||||||
|
|
||||||
// First pass: collect all post slugs with their correct dates
|
|
||||||
const postDatesRegex = /slug:\s*"([^"]+)"[\s\S]*?date:\s*"([^"]+)"[\s\S]*?updatedAt:\s*"([^"]+)"/gm;
|
|
||||||
|
|
||||||
while ((match = postDatesRegex.exec(content)) !== null) {
|
|
||||||
const slug = match[1];
|
|
||||||
const publishDate = match[2]; // e.g., "February 16, 2026"
|
|
||||||
const updatedDate = match[3]; // e.g., "2026-01-26"
|
|
||||||
|
|
||||||
// Format the updated date
|
|
||||||
const [year, month, day] = updatedDate.split('-');
|
|
||||||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
||||||
const formattedUpdated = `${months[parseInt(month) - 1]} ${parseInt(day)}, ${year}`;
|
|
||||||
|
|
||||||
replacements.push({
|
|
||||||
slug,
|
|
||||||
publishDate,
|
|
||||||
updatedDate: formattedUpdated
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now replace the broken metadata divs
|
|
||||||
replacements.forEach(({ slug, publishDate, updatedDate }) => {
|
|
||||||
const pattern = new RegExp(
|
|
||||||
`(<div class="post-metadata[^>]*>[\s\S]*?<strong>Published:<\/strong>\s*)${publishDate.replace(/[.*+?^${}()|[\]\\]/g, '\$&')}([\s\S]*?<strong>Last updated:<\/strong>\s*)undefined NaN, NaN`,
|
|
||||||
'gm'
|
|
||||||
);
|
|
||||||
|
|
||||||
content = content.replace(pattern, `$1${publishDate}$2${updatedDate}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.writeFileSync(filePath, content, 'utf-8');
|
|
||||||
console.log(`✅ Fixed date formatting in ${replacements.length} posts`);
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const filePath = path.join(__dirname, '../src/lib/blog-data.ts');
|
|
||||||
let content = fs.readFileSync(filePath, 'utf-8');
|
|
||||||
|
|
||||||
// Remove the draft note from qr-code-scan-statistics-2026
|
|
||||||
const draftNotePattern = /<p><em>Note: I'm not browsing live sources[\s\S]*?before publishing.*?replace the placeholder sections below with your numbers \+ citations\.<\/em><\/p>/gm;
|
|
||||||
|
|
||||||
const originalLength = content.length;
|
|
||||||
content = content.replace(draftNotePattern, '');
|
|
||||||
const newLength = content.length;
|
|
||||||
|
|
||||||
fs.writeFileSync(filePath, content, 'utf-8');
|
|
||||||
|
|
||||||
if (originalLength > newLength) {
|
|
||||||
console.log(`✅ Removed draft note from qr-code-scan-statistics-2026 (${originalLength - newLength} bytes deleted)`);
|
|
||||||
} else {
|
|
||||||
console.log('⚠️ Draft note not found or already removed');
|
|
||||||
}
|
|
||||||
|
|
@ -70,13 +70,12 @@ 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="/use-cases">Use Cases</Link></li>
|
<li><Link href="/faq">{t.nav.faq}</Link></li>
|
||||||
<li><Link href="/faq">{t.nav.faq}</Link></li>
|
<li><Link href="/about">{t.nav.about}</Link></li>
|
||||||
<li><Link href="/about">{t.nav.about}</Link></li>
|
<li><Link href="/contact">{t.nav.contact}</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 */}
|
||||||
|
|
@ -176,15 +175,12 @@ 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="/use-cases" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
<Link href="/learn" className="px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors">
|
||||||
Use Cases
|
{t.nav.learn}
|
||||||
</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>
|
||||||
|
|
@ -269,10 +265,9 @@ 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="/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="/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="/dynamic-qr-code-generator">
|
<Link href="/create-qr">
|
||||||
<Button size="lg">Create QR Code</Button>
|
<Button size="lg">Create QR Code</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,14 @@ 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,18 +1,15 @@
|
||||||
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 Bulk QR Codes from Excel',
|
absolute: 'Bulk QR Code Generator - Create 1000s 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',
|
||||||
|
|
@ -24,14 +21,14 @@ export const metadata: Metadata = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: 'Bulk QR Code Generator - Create Bulk QR Codes from Excel',
|
title: 'Bulk QR Code Generator - Create 1000s 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 Bulk QR Codes from Excel',
|
title: 'Bulk QR Code Generator - Create 1000s 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.',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -283,43 +280,15 @@ 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' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const relatedUseCaseLinks = [
|
return (
|
||||||
{
|
<>
|
||||||
href: '/qr-code-for-marketing-campaigns',
|
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||||
title: 'QR Codes for Marketing Campaigns',
|
<div className="min-h-screen bg-white">
|
||||||
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">
|
||||||
|
|
@ -328,7 +297,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>CSV and Excel workflows</span>
|
<span>Generate 1000s in Minutes</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">
|
||||||
|
|
@ -378,7 +347,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">Bulk-ready file</p>
|
<p className="text-sm text-gray-500">1,247 rows ready</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">
|
||||||
|
|
@ -393,11 +362,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">
|
||||||
Continue with a full batch import
|
+ 1,239 more codes
|
||||||
</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">
|
||||||
Bulk import
|
1000s at Once!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -662,23 +631,14 @@ Product C,https://example.com/product-c,Budget Widget,electronics,sale`}
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<GrowthLinksSection
|
{/* CTA Section */}
|
||||||
eyebrow="Bulk-ready workflows"
|
<section className="py-20 bg-gradient-to-r from-green-600 to-blue-600 text-white">
|
||||||
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 bulk QR codes without one-by-one setup
|
Generate 1000s of QR Codes in Minutes
|
||||||
</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,12 +4,9 @@ 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,
|
||||||
|
|
@ -290,43 +287,15 @@ 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' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const relatedUseCaseLinks = [
|
return (
|
||||||
{
|
<>
|
||||||
href: '/qr-code-for-marketing-campaigns',
|
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||||
title: 'QR Codes for Marketing Campaigns',
|
<div className="min-h-screen bg-white">
|
||||||
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">
|
||||||
|
|
@ -652,20 +621,11 @@ export default function CustomQRCodeGeneratorPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<GrowthLinksSection
|
{/* Final CTA */}
|
||||||
eyebrow="Branded workflows"
|
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||||
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,14 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Button } from '@/components/ui/Button';
|
import Link from 'next/link';
|
||||||
|
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: {
|
||||||
|
|
@ -232,43 +230,15 @@ 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' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const relatedUseCaseLinks = [
|
return (
|
||||||
{
|
<>
|
||||||
href: featuredUseCases[0].href,
|
<SeoJsonLd data={[softwareSchema, howToSchema, faqSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||||
title: featuredUseCases[0].title,
|
<div className="min-h-screen bg-white">
|
||||||
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">
|
||||||
|
|
@ -304,21 +274,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">
|
||||||
<TrackedCtaLink href="/signup" ctaLabel="Create Dynamic QR Code" ctaLocation="hero_primary" pageType="commercial" cluster="dynamic-qr">
|
<Link href="/signup">
|
||||||
<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>
|
||||||
</TrackedCtaLink>
|
</Link>
|
||||||
<TrackedCtaLink href="/pricing" ctaLabel="View Pricing" ctaLocation="hero_secondary" pageType="commercial" cluster="dynamic-qr">
|
<Link href="/pricing">
|
||||||
<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>
|
||||||
</TrackedCtaLink>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Visual Demo */}
|
{/* Visual Demo */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|
@ -544,41 +514,32 @@ export default function DynamicQRCodeGeneratorPage() {
|
||||||
</p>
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<GrowthLinksSection
|
{/* CTA Section */}
|
||||||
eyebrow="Best next workflows"
|
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
||||||
title="See where dynamic QR becomes most useful"
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||||
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."
|
<h2 className="text-4xl font-bold mb-6">
|
||||||
links={relatedUseCaseLinks}
|
Start Creating Dynamic QR Codes Today
|
||||||
pageType="commercial"
|
</h2>
|
||||||
cluster="dynamic-qr"
|
<p className="text-xl mb-8 text-purple-100">
|
||||||
/>
|
Join thousands of businesses who never worry about reprinting QR codes again
|
||||||
|
</p>
|
||||||
{/* CTA Section */}
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
<section className="py-20 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
|
<Link href="/signup">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
<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">
|
||||||
<h2 className="text-4xl font-bold mb-6">
|
Get Started Free
|
||||||
Start Creating Dynamic QR Codes Today
|
</Button>
|
||||||
</h2>
|
</Link>
|
||||||
<p className="text-xl mb-8 text-purple-100">
|
<Link href="/signup">
|
||||||
Use one QR code that can keep working even when the destination behind it needs to change.
|
<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">
|
||||||
</p>
|
Create QR Code Now
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
</Button>
|
||||||
<TrackedCtaLink href="/signup" ctaLabel="Get Started Free" ctaLocation="footer_primary" pageType="commercial" cluster="dynamic-qr">
|
</Link>
|
||||||
<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
|
</div>
|
||||||
</Button>
|
</section>
|
||||||
</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,8 +1,11 @@
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import '@/styles/globals.css';
|
import AdBanner from '@/components/ads/AdBanner';
|
||||||
import MarketingLayout from './MarketingLayout';
|
import '@/styles/globals.css';
|
||||||
// Import schema functions from library
|
import { Providers } from '@/components/Providers';
|
||||||
import { organizationSchema } from '@/lib/schema';
|
import MarketingLayout from './MarketingLayout';
|
||||||
|
// 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';
|
||||||
|
|
||||||
|
|
@ -51,20 +54,29 @@ 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,20 +8,14 @@ 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,16 +2,10 @@ 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();
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
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,13 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Button } from '@/components/ui/Button';
|
import Link from 'next/link';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Button } from '@/components/ui/Button';
|
||||||
import SeoJsonLd from '@/components/SeoJsonLd';
|
import { Card } from '@/components/ui/Card';
|
||||||
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
import SeoJsonLd from '@/components/SeoJsonLd';
|
||||||
import { breadcrumbSchema } from '@/lib/schema';
|
import Breadcrumbs, { BreadcrumbItem } from '@/components/Breadcrumbs';
|
||||||
import { GrowthLinksSection } from '@/components/marketing/GrowthLinksSection';
|
import { breadcrumbSchema } from '@/lib/schema';
|
||||||
import { MarketingPageTracker, TrackedCtaLink } from '@/components/marketing/MarketingAnalytics';
|
|
||||||
import { featuredUseCases } from '@/lib/growth-pages';
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
|
|
@ -166,43 +164,15 @@ 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' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const relatedUseCaseLinks = [
|
return (
|
||||||
{
|
<>
|
||||||
href: featuredUseCases[2].href,
|
<SeoJsonLd data={[softwareSchema, howToSchema, breadcrumbSchema(breadcrumbItems)]} />
|
||||||
title: featuredUseCases[2].title,
|
<div className="min-h-screen bg-white">
|
||||||
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">
|
||||||
|
|
@ -220,20 +190,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">
|
||||||
<TrackedCtaLink href="/signup" ctaLabel="Start Tracking Free" ctaLocation="hero_primary" pageType="commercial" cluster="qr-tracking">
|
<Link href="/signup">
|
||||||
<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>
|
||||||
</TrackedCtaLink>
|
</Link>
|
||||||
<TrackedCtaLink href="/signup" ctaLabel="Create Trackable QR Code" ctaLocation="hero_secondary" pageType="commercial" cluster="qr-tracking">
|
<Link href="/signup">
|
||||||
<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>
|
||||||
</TrackedCtaLink>
|
</Link>
|
||||||
</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">
|
||||||
|
|
@ -242,13 +212,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>Placement-ready reports</span>
|
<span>Unlimited scans</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Analytics Preview */}
|
{/* Analytics Preview */}
|
||||||
|
|
@ -257,20 +227,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">Placement view</span>
|
<span className="text-gray-600">Total Scans</span>
|
||||||
<span className="text-base font-semibold text-primary-600">Flyer vs booth vs table card</span>
|
<span className="text-2xl font-bold text-primary-600">12,547</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">Time trend</span>
|
<span className="text-gray-600">Unique Users</span>
|
||||||
<span className="text-base font-semibold text-primary-600">Lunch, event day, or campaign burst</span>
|
<span className="text-2xl font-bold text-primary-600">8,392</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">Location context</span>
|
<span className="text-gray-600">Top Location</span>
|
||||||
<span className="font-semibold">Region and city level view</span>
|
<span className="font-semibold">🇩🇪 Germany</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-gray-600">Device context</span>
|
<span className="text-gray-600">Top Device</span>
|
||||||
<span className="font-semibold">Phone, desktop, and scan mix</span>
|
<span className="font-semibold">📱 iPhone</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -348,9 +318,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>
|
||||||
|
|
@ -394,42 +364,33 @@ export default function QRCodeTrackingPage() {
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<GrowthLinksSection
|
{/* CTA Section */}
|
||||||
eyebrow="Tracking-led workflows"
|
<section className="py-20 bg-gradient-to-r from-primary-600 to-purple-600 text-white">
|
||||||
title="Where scan visibility matters most"
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
||||||
description="These are the workflows where scan context, placement comparison, and destination flexibility make QR tracking materially more useful."
|
<h2 className="text-4xl font-bold mb-6">
|
||||||
links={relatedUseCaseLinks}
|
Start Tracking Your QR Codes Today
|
||||||
pageType="commercial"
|
</h2>
|
||||||
cluster="qr-tracking"
|
<p className="text-xl mb-8 text-primary-100">
|
||||||
/>
|
Join thousands of businesses using QR Master to track and optimize their QR code campaigns
|
||||||
|
</p>
|
||||||
{/* CTA Section */}
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
<section className="py-20 bg-gradient-to-r from-primary-600 to-purple-600 text-white">
|
<Link href="/signup">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-4xl text-center">
|
<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">
|
||||||
<h2 className="text-4xl font-bold mb-6">
|
Create Free Account
|
||||||
Start Tracking Your QR Codes Today
|
</Button>
|
||||||
</h2>
|
</Link>
|
||||||
<p className="text-xl mb-8 text-primary-100">
|
<Link href="/pricing">
|
||||||
Measure scans with enough context to improve the next placement, campaign, or printed workflow.
|
<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">
|
||||||
</p>
|
View Pricing
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
</Button>
|
||||||
<TrackedCtaLink href="/signup" ctaLabel="Create Free Account" ctaLocation="footer_primary" pageType="commercial" cluster="qr-tracking">
|
</Link>
|
||||||
<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
|
</div>
|
||||||
</Button>
|
</section>
|
||||||
</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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,303 +0,0 @@
|
||||||
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,10 +1,11 @@
|
||||||
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 FacebookPixel from '@/components/analytics/FacebookPixel';
|
import AdSenseScript from '@/components/ads/AdSenseScript';
|
||||||
|
import FacebookPixel from '@/components/analytics/FacebookPixel';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
title: {
|
||||||
|
|
@ -54,33 +55,46 @@ export const metadata: Metadata = {
|
||||||
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
|
'facebook-domain-verification': process.env.NEXT_PUBLIC_FACEBOOK_DOMAIN_VERIFICATION || '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MarketingDeGroupLayout({
|
import AdBanner from '@/components/ads/AdBanner'; // Import AdBanner
|
||||||
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>
|
||||||
<FacebookPixel />
|
<AdSenseScript />
|
||||||
<script
|
<FacebookPixel />
|
||||||
type="application/ld+json"
|
<script
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema()) }}
|
type="application/ld+json"
|
||||||
|
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>
|
|
||||||
</Providers>
|
{/* Global Marketing Ad - Exclusions handled in AdBanner */}
|
||||||
</Suspense>
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-7xl pb-8">
|
||||||
</body>
|
<AdBanner
|
||||||
|
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,53 +53,20 @@ 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) => ({
|
||||||
|
|
@ -222,11 +189,10 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
...toolPages,
|
...toolPages,
|
||||||
...blogPages,
|
...blogPages,
|
||||||
...learnPages,
|
...learnPages,
|
||||||
...growthUseCasePages,
|
...authorPages,
|
||||||
...authorPages,
|
];
|
||||||
];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
'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 } from '@/components/PostHogProvider';
|
import { PostHogProvider, PostHogPageView } 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}>
|
||||||
<GoogleAnalytics />
|
<PostHogPageView />
|
||||||
<FacebookPixel />
|
<GoogleAnalytics />
|
||||||
</Suspense>
|
<FacebookPixel />
|
||||||
|
</Suspense>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
{children}
|
{children}
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,64 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { usePathname, useSearchParams } from 'next/navigation';
|
import { usePathname, useSearchParams } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export default function GoogleAnalytics() {
|
export default function GoogleAnalytics() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
|
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!GA_MEASUREMENT_ID) return;
|
if (!GA_MEASUREMENT_ID) return;
|
||||||
|
|
||||||
const cookieConsent = localStorage.getItem('cookieConsent');
|
const cookieConsent = localStorage.getItem('cookieConsent');
|
||||||
if (cookieConsent !== 'accepted') return;
|
if (cookieConsent !== 'accepted') return;
|
||||||
|
|
||||||
const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : '');
|
const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : '');
|
||||||
|
|
||||||
if (typeof window.gtag !== 'undefined') {
|
if (typeof window.gtag !== 'undefined') {
|
||||||
window.gtag('config', GA_MEASUREMENT_ID, {
|
window.gtag('config', GA_MEASUREMENT_ID, {
|
||||||
page_path: url,
|
page_path: url,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [pathname, searchParams, GA_MEASUREMENT_ID]);
|
}, [pathname, searchParams, GA_MEASUREMENT_ID]);
|
||||||
|
|
||||||
const cookieConsent = typeof window !== 'undefined' ? localStorage.getItem('cookieConsent') : null;
|
const cookieConsent = typeof window !== 'undefined' ? localStorage.getItem('cookieConsent') : null;
|
||||||
|
|
||||||
if (!GA_MEASUREMENT_ID || cookieConsent !== 'accepted') {
|
if (!GA_MEASUREMENT_ID || cookieConsent !== 'accepted') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Script
|
<Script
|
||||||
strategy="afterInteractive"
|
strategy="afterInteractive"
|
||||||
src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
|
src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
|
||||||
/>
|
/>
|
||||||
<Script
|
<Script
|
||||||
id="google-analytics"
|
id="google-analytics"
|
||||||
strategy="afterInteractive"
|
strategy="afterInteractive"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `
|
__html: `
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag(){dataLayer.push(arguments);}
|
function gtag(){dataLayer.push(arguments);}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
|
|
||||||
gtag('config', '${GA_MEASUREMENT_ID}', {
|
gtag('config', '${GA_MEASUREMENT_ID}', {
|
||||||
page_path: window.location.pathname,
|
page_path: window.location.pathname,
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add gtag to window object for TypeScript
|
// Add gtag to window object for TypeScript
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
gtag: (command: string, id: string, config?: any) => void;
|
gtag: (command: string, id: string, config?: any) => void;
|
||||||
dataLayer: any[];
|
dataLayer: any[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
"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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,427 +0,0 @@
|
||||||
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,11 +47,10 @@ 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="/use-cases" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>Use Cases</Link></li>
|
<li><Link href="/faq" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.faq}</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>
|
||||||
<li><Link href="/blog" className={isDashboard ? 'hover:text-primary-600' : 'hover:text-white'}>{translations.blog}</Link></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -67,10 +66,9 @@ 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="/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="/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>
|
||||||
|
|
|
||||||
4704
src/lib/blog-data.ts
4704
src/lib/blog-data.ts
File diff suppressed because it is too large
Load Diff
|
|
@ -17,11 +17,7 @@ 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);
|
||||||
if (!p?.published) return undefined;
|
return p?.published ? p : 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[] {
|
||||||
|
|
|
||||||
|
|
@ -1,402 +0,0 @@
|
||||||
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}/about`,
|
`${baseUrl}/qr-code-erstellen`,
|
||||||
`${baseUrl}/contact`,
|
`${baseUrl}/qr-code-tracking`,
|
||||||
`${baseUrl}/press`,
|
`${baseUrl}/reprint-calculator`,
|
||||||
`${baseUrl}/testimonials`,
|
`${baseUrl}/dynamic-qr-code-generator`,
|
||||||
`${baseUrl}/qr-code-erstellen`,
|
`${baseUrl}/bulk-qr-code-generator`,
|
||||||
`${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}/privacy`,
|
`${baseUrl}/signup`,
|
||||||
`${baseUrl}/newsletter`,
|
`${baseUrl}/login`,
|
||||||
];
|
`${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 = [
|
||||||
|
|
|
||||||
|
|
@ -58,28 +58,6 @@ export function organizationSchema() {
|
||||||
|
|
||||||
export function blogPostingSchema(post: BlogPost, author?: AuthorProfile) {
|
export function blogPostingSchema(post: BlogPost, author?: AuthorProfile) {
|
||||||
const url = `${SITE_URL}/blog/${post.slug}`;
|
const url = `${SITE_URL}/blog/${post.slug}`;
|
||||||
|
|
||||||
// Use post.authorName if available (from AEO/GEO optimization), otherwise fall back to author profile
|
|
||||||
const authorSchema = post.authorName
|
|
||||||
? {
|
|
||||||
"@type": "Person",
|
|
||||||
name: post.authorName,
|
|
||||||
jobTitle: post.authorTitle,
|
|
||||||
url: `${SITE_URL}/#organization`,
|
|
||||||
}
|
|
||||||
: author
|
|
||||||
? {
|
|
||||||
"@type": "Person",
|
|
||||||
name: author.name,
|
|
||||||
url: `${SITE_URL}/authors/${author.slug}`,
|
|
||||||
sameAs: author.sameAs ?? undefined,
|
|
||||||
knowsAbout: author.knowsAbout ?? undefined
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
"@type": "Organization",
|
|
||||||
name: "QR Master"
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "BlogPosting",
|
"@type": "BlogPosting",
|
||||||
|
|
@ -89,7 +67,18 @@ export function blogPostingSchema(post: BlogPost, author?: AuthorProfile) {
|
||||||
datePublished: post.datePublished,
|
datePublished: post.datePublished,
|
||||||
dateModified: post.dateModified || post.datePublished,
|
dateModified: post.dateModified || post.datePublished,
|
||||||
image: post.heroImage ? `${SITE_URL}${post.heroImage}` : undefined,
|
image: post.heroImage ? `${SITE_URL}${post.heroImage}` : undefined,
|
||||||
author: authorSchema,
|
author: author
|
||||||
|
? {
|
||||||
|
"@type": "Person",
|
||||||
|
name: author.name,
|
||||||
|
url: `${SITE_URL}/authors/${author.slug}`,
|
||||||
|
sameAs: author.sameAs ?? undefined,
|
||||||
|
knowsAbout: author.knowsAbout ?? undefined
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
"@type": "Organization",
|
||||||
|
name: "QR Master"
|
||||||
|
},
|
||||||
publisher: {
|
publisher: {
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
name: "QR Master",
|
name: "QR Master",
|
||||||
|
|
@ -117,13 +106,6 @@ export function howToSchema(post: BlogPost, author?: AuthorProfile) {
|
||||||
text
|
text
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Use post.authorName if available, otherwise fall back to author profile
|
|
||||||
const authorSchema = post.authorName
|
|
||||||
? { "@type": "Person", name: post.authorName, jobTitle: post.authorTitle }
|
|
||||||
: author
|
|
||||||
? { "@type": "Person", name: author.name, url: `${SITE_URL}/authors/${author.slug}` }
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "HowTo",
|
"@type": "HowTo",
|
||||||
|
|
@ -131,7 +113,9 @@ export function howToSchema(post: BlogPost, author?: AuthorProfile) {
|
||||||
description: post.description,
|
description: post.description,
|
||||||
url: `${url}#howto`,
|
url: `${url}#howto`,
|
||||||
step: steps,
|
step: steps,
|
||||||
author: authorSchema
|
author: author
|
||||||
|
? { "@type": "Person", name: author.name, url: `${SITE_URL}/authors/${author.slug}` }
|
||||||
|
: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,6 @@ export type BlogPost = {
|
||||||
publishDate?: string; // User-provided alternate date field
|
publishDate?: string; // User-provided alternate date field
|
||||||
updatedAt?: string; // User-provided alternate date field
|
updatedAt?: string; // User-provided alternate date field
|
||||||
authorSlug: string;
|
authorSlug: string;
|
||||||
authorName?: string; // Full name for AEO/GEO optimization
|
|
||||||
authorTitle?: string; // Job title/expertise for schema markup
|
|
||||||
|
|
||||||
// SEO
|
// SEO
|
||||||
keywords?: string[];
|
keywords?: string[];
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,12 @@ 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') {
|
|
||||||
return NextResponse.redirect(new URL('/dynamic-qr-code-generator', req.url), 301);
|
// Public routes that don't require authentication
|
||||||
}
|
const publicPaths = [
|
||||||
|
|
||||||
// Public routes that don't require authentication
|
|
||||||
const publicPaths = [
|
|
||||||
'/',
|
'/',
|
||||||
'/pricing',
|
'/pricing',
|
||||||
'/faq',
|
'/faq',
|
||||||
|
|
@ -45,13 +42,11 @@ export function middleware(req: NextRequest) {
|
||||||
'/display',
|
'/display',
|
||||||
'/contact',
|
'/contact',
|
||||||
'/about',
|
'/about',
|
||||||
'/learn',
|
'/learn',
|
||||||
'/use-cases',
|
'/authors',
|
||||||
'/authors',
|
'/press',
|
||||||
'/press',
|
'/testimonials',
|
||||||
'/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 + '/'));
|
||||||
|
|
@ -100,4 +95,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