Compare commits
1 Commits
79e54f8ae2
...
analytics
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af2d8f1e8f |
@@ -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.
|
|
||||||
@@ -25,9 +25,7 @@
|
|||||||
"Bash(echo \"\n\n## CSRF Debug aktiviert!\n\nBitte teste jetzt:\n1. Browser zu http://localhost:3050/create\n2. Dynamic QR Code erstellen versuchen\n3. Server-Logs zeigen jetzt [CSRF Debug] Output\n\nIch sehe dann:\n- Ob headerToken vorhanden ist\n- Ob cookieToken vorhanden ist \n- Ob sie übereinstimmen\n\n---\n\nStripe Portal 500 Error ist separates Problem:\nhttps://dashboard.stripe.com/test/settings/billing/portal\n→ Customer Portal Configuration muss erstellt werden\n\")",
|
"Bash(echo \"\n\n## CSRF Debug aktiviert!\n\nBitte teste jetzt:\n1. Browser zu http://localhost:3050/create\n2. Dynamic QR Code erstellen versuchen\n3. Server-Logs zeigen jetzt [CSRF Debug] Output\n\nIch sehe dann:\n- Ob headerToken vorhanden ist\n- Ob cookieToken vorhanden ist \n- Ob sie übereinstimmen\n\n---\n\nStripe Portal 500 Error ist separates Problem:\nhttps://dashboard.stripe.com/test/settings/billing/portal\n→ Customer Portal Configuration muss erstellt werden\n\")",
|
||||||
"Bash(pkill:*)",
|
"Bash(pkill:*)",
|
||||||
"Skill(shadcn-ui)",
|
"Skill(shadcn-ui)",
|
||||||
"Bash(find:*)",
|
"Bash(find:*)"
|
||||||
"Bash(ls -la \"/c/Users/User/Documents/QR-master/src/app/\\(main\\)/\\(marketing\\)/\")",
|
|
||||||
"Bash(npx tsc:*)"
|
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
110
.dockerignore
@@ -1,55 +1,55 @@
|
|||||||
# Dependencies
|
# Dependencies
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
pnpm-debug.log
|
pnpm-debug.log
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
coverage
|
coverage
|
||||||
.nyc_output
|
.nyc_output
|
||||||
|
|
||||||
# Next.js
|
# Next.js
|
||||||
.next
|
.next
|
||||||
out
|
out
|
||||||
dist
|
dist
|
||||||
build
|
build
|
||||||
|
|
||||||
# Environment files
|
# Environment files
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
|
||||||
# IDEs
|
# IDEs
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*~
|
*~
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Git
|
# Git
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
Dockerfile
|
Dockerfile
|
||||||
docker-compose*.yml
|
docker-compose*.yml
|
||||||
.dockerignore
|
.dockerignore
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
README.md
|
README.md
|
||||||
.prettierrc
|
.prettierrc
|
||||||
.eslintrc.json
|
.eslintrc.json
|
||||||
*.md
|
*.md
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Prisma
|
# Prisma
|
||||||
# prisma/migrations # Now included in Docker image for deployment
|
prisma/migrations
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.{js,jsx,ts,tsx,json,css,scss,md}]
|
[*.{js,jsx,ts,tsx,json,css,scss,md}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
38
.env.example
@@ -1,26 +1,12 @@
|
|||||||
# Database credentials (used by both db and web services in docker-compose.yml)
|
NODE_ENV=production
|
||||||
POSTGRES_USER=postgres
|
PORT=3000
|
||||||
POSTGRES_PASSWORD=postgres
|
DATABASE_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||||
POSTGRES_DB=qrmaster
|
DIRECT_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
# Note: DATABASE_URL and DIRECT_URL are auto-generated from POSTGRES_* vars in docker-compose.yml
|
NEXTAUTH_SECRET=CHANGE_ME
|
||||||
# You don't need to set them here when using Docker Compose
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||||
NODE_ENV=production
|
GOOGLE_CLIENT_ID=
|
||||||
PORT=3000
|
GOOGLE_CLIENT_SECRET=
|
||||||
NEXTAUTH_URL=http://localhost:3000
|
REDIS_URL=redis://redis:6379
|
||||||
NEXTAUTH_SECRET=CHANGE_ME
|
IP_SALT=CHANGE_ME_SALT
|
||||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
ENABLE_DEMO=true
|
||||||
GOOGLE_CLIENT_ID=
|
|
||||||
GOOGLE_CLIENT_SECRET=
|
|
||||||
REDIS_URL=redis://redis:6379
|
|
||||||
IP_SALT=CHANGE_ME_SALT
|
|
||||||
ENABLE_DEMO=true
|
|
||||||
|
|
||||||
# SMTP (for welcome + retention emails via nodemailer)
|
|
||||||
SMTP_HOST=smtp.qrmaster.net
|
|
||||||
SMTP_PORT=465
|
|
||||||
SMTP_USER=timo@qrmaster.net
|
|
||||||
SMTP_PASS=
|
|
||||||
|
|
||||||
# Cron job protection — generate with: openssl rand -base64 32
|
|
||||||
CRON_SECRET=
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"next/core-web-vitals",
|
|
||||||
"next/typescript"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
50
.github/workflows/ci.yml
vendored
@@ -1,26 +1,26 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on: [push]
|
on: [push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Generate Prisma Client
|
- name: Generate Prisma Client
|
||||||
run: npx prisma generate
|
run: npx prisma generate
|
||||||
|
|
||||||
- name: Build application
|
- name: Build application
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
run: npm run lint
|
run: npm run lint
|
||||||
83
.gitignore
vendored
@@ -1,52 +1,51 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
/.next/
|
/.next/
|
||||||
/out/
|
/out/
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env*.local
|
.env*.local
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
# prisma
|
# prisma
|
||||||
# /prisma/migrations/ # Now tracked in Git for deployment
|
/prisma/migrations/
|
||||||
|
|
||||||
# docker
|
# docker
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
*.sql
|
*.sql
|
||||||
!prisma/migrations/**/*.sql
|
|
||||||
/backups/
|
/backups/
|
||||||
|
|
||||||
# logs
|
# logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# local dev script
|
# local dev script
|
||||||
dev-server.js
|
dev-server.js
|
||||||
11
.mcp.json
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"apify": {
|
|
||||||
"type": "sse",
|
|
||||||
"url": "https://mcp.apify.com/sse",
|
|
||||||
"headers": {
|
|
||||||
"Authorization": "Bearer apify_api_0D7RWI6eW1H9LETBuLY7PHNpAErxL72ua6lo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
.npmrc
@@ -1,2 +1,2 @@
|
|||||||
registry=https://registry.npmjs.org/
|
registry=https://registry.npmjs.org/
|
||||||
legacy-peer-deps=true
|
legacy-peer-deps=true
|
||||||
14
.prettierrc
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"printWidth": 80,
|
"printWidth": 80,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"useTabs": false
|
"useTabs": false
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.next
|
.next
|
||||||
.git
|
.git
|
||||||
*.log
|
*.log
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.vercel
|
.vercel
|
||||||
*.sql
|
*.sql
|
||||||
/backups/
|
/backups/
|
||||||
.npmrc
|
.npmrc
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"codium.codeCompletion.enable": false
|
"codium.codeCompletion.enable": false
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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
|
|
||||||
187
CHANGELOG.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
# Changelog - PostgreSQL Migration
|
||||||
|
|
||||||
|
## [2.0.0] - 2024-10-13
|
||||||
|
|
||||||
|
### 🎉 Major Changes - Supabase to PostgreSQL Migration
|
||||||
|
|
||||||
|
#### Removed
|
||||||
|
- ❌ **Supabase dependency** - Removed all Supabase-specific configurations
|
||||||
|
- ❌ **DIRECT_URL** - Removed connection pooling URL (Supabase-specific)
|
||||||
|
- ❌ **External database dependency** - Now fully self-hosted
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- ✅ **PostgreSQL 16 in Docker** - Local PostgreSQL database with Docker support
|
||||||
|
- ✅ **Redis 7** - Caching and rate limiting with Redis
|
||||||
|
- ✅ **Adminer** - Database management UI (http://localhost:8080)
|
||||||
|
- ✅ **Docker Compose setups** - Both development and production configurations
|
||||||
|
- ✅ **Database initialization** - Automated database setup with extensions
|
||||||
|
- ✅ **Complete documentation** - Multiple guides for setup and migration
|
||||||
|
- ✅ **Setup scripts** - Automated setup for both Linux/Mac and Windows
|
||||||
|
- ✅ **npm scripts** - Convenient Docker commands via npm
|
||||||
|
|
||||||
|
#### Modified Files
|
||||||
|
- 📝 `prisma/schema.prisma` - Removed directUrl field
|
||||||
|
- 📝 `src/lib/env.ts` - Removed DIRECT_URL, updated DATABASE_URL default
|
||||||
|
- 📝 `docker-compose.yml` - Complete rewrite with PostgreSQL, Redis, and networking
|
||||||
|
- 📝 `Dockerfile` - Enhanced with proper PostgreSQL support
|
||||||
|
- 📝 `package.json` - Added Docker scripts and tsx dependency
|
||||||
|
- 📝 `README.md` - Updated with new setup instructions
|
||||||
|
|
||||||
|
#### New Files
|
||||||
|
- 📄 `docker-compose.dev.yml` - Development environment (database only)
|
||||||
|
- 📄 `docker/init-db.sh` - PostgreSQL initialization script
|
||||||
|
- 📄 `docker/README.md` - Docker-specific documentation
|
||||||
|
- 📄 `DOCKER_SETUP.md` - Comprehensive Docker setup guide
|
||||||
|
- 📄 `MIGRATION_FROM_SUPABASE.md` - Step-by-step migration guide
|
||||||
|
- 📄 `env.example` - Environment variable template
|
||||||
|
- 📄 `.dockerignore` - Docker build optimization
|
||||||
|
- 📄 `scripts/setup.sh` - Quick setup script (Linux/Mac)
|
||||||
|
- 📄 `scripts/setup.ps1` - Quick setup script (Windows)
|
||||||
|
- 📄 `CHANGELOG.md` - This file
|
||||||
|
|
||||||
|
### 📦 Docker Services
|
||||||
|
|
||||||
|
#### PostgreSQL Database
|
||||||
|
- **Image**: postgres:16-alpine
|
||||||
|
- **Port**: 5432
|
||||||
|
- **Features**:
|
||||||
|
- Health checks
|
||||||
|
- Volume persistence
|
||||||
|
- UTF-8 encoding
|
||||||
|
- Extensions: uuid-ossp, pg_trgm
|
||||||
|
|
||||||
|
#### Redis Cache
|
||||||
|
- **Image**: redis:7-alpine
|
||||||
|
- **Port**: 6379
|
||||||
|
- **Features**:
|
||||||
|
- AOF persistence
|
||||||
|
- 256MB max memory with LRU eviction
|
||||||
|
- Health checks
|
||||||
|
|
||||||
|
#### Next.js Application
|
||||||
|
- **Port**: 3000
|
||||||
|
- **Features**:
|
||||||
|
- Multi-stage build
|
||||||
|
- Production optimization
|
||||||
|
- Health checks
|
||||||
|
- Automatic Prisma generation
|
||||||
|
|
||||||
|
#### Adminer (Development)
|
||||||
|
- **Port**: 8080
|
||||||
|
- **Features**:
|
||||||
|
- Database management UI
|
||||||
|
- Optional (dev profile)
|
||||||
|
- Pre-configured for PostgreSQL
|
||||||
|
|
||||||
|
### 🚀 Quick Start
|
||||||
|
|
||||||
|
#### Development Mode
|
||||||
|
```bash
|
||||||
|
npm run docker:dev # Start database
|
||||||
|
npm run db:migrate # Run migrations
|
||||||
|
npm run dev # Start app
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Production Mode
|
||||||
|
```bash
|
||||||
|
npm run docker:prod # Start all services
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
- **README.md** - Main documentation with quick start
|
||||||
|
- **DOCKER_SETUP.md** - Complete Docker guide with troubleshooting
|
||||||
|
- **MIGRATION_FROM_SUPABASE.md** - Migration guide from Supabase
|
||||||
|
- **docker/README.md** - Docker commands and operations
|
||||||
|
- **env.example** - Environment variable reference
|
||||||
|
|
||||||
|
### 🔧 New npm Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker commands
|
||||||
|
npm run docker:dev # Start development services
|
||||||
|
npm run docker:dev:stop # Stop development services
|
||||||
|
npm run docker:prod # Start production stack
|
||||||
|
npm run docker:stop # Stop all services
|
||||||
|
npm run docker:logs # View all logs
|
||||||
|
npm run docker:db # PostgreSQL CLI
|
||||||
|
npm run docker:redis # Redis CLI
|
||||||
|
npm run docker:backup # Backup database
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔐 Environment Variables
|
||||||
|
|
||||||
|
#### Required
|
||||||
|
- `DATABASE_URL` - PostgreSQL connection string
|
||||||
|
- `NEXTAUTH_SECRET` - NextAuth.js secret (generate with openssl)
|
||||||
|
- `NEXTAUTH_URL` - Application URL
|
||||||
|
- `IP_SALT` - Salt for IP hashing (generate with openssl)
|
||||||
|
|
||||||
|
#### Optional
|
||||||
|
- `GOOGLE_CLIENT_ID` - Google OAuth client ID
|
||||||
|
- `GOOGLE_CLIENT_SECRET` - Google OAuth secret
|
||||||
|
- `REDIS_URL` - Redis connection string
|
||||||
|
- `ENABLE_DEMO` - Enable demo mode
|
||||||
|
|
||||||
|
### 🎯 Benefits
|
||||||
|
|
||||||
|
1. **Full Control** - Own your data and infrastructure
|
||||||
|
2. **No Vendor Lock-in** - Standard PostgreSQL
|
||||||
|
3. **Lower Latency** - Local network speed
|
||||||
|
4. **Cost Effective** - No monthly database fees
|
||||||
|
5. **Privacy** - Data stays on your infrastructure
|
||||||
|
6. **Development** - Easy local testing
|
||||||
|
7. **Offline Capable** - Works without internet
|
||||||
|
|
||||||
|
### 🔄 Migration Path
|
||||||
|
|
||||||
|
1. Backup Supabase data
|
||||||
|
2. Update codebase
|
||||||
|
3. Start local PostgreSQL
|
||||||
|
4. Restore data or run migrations
|
||||||
|
5. Update environment variables
|
||||||
|
6. Deploy
|
||||||
|
|
||||||
|
See [MIGRATION_FROM_SUPABASE.md](MIGRATION_FROM_SUPABASE.md) for detailed steps.
|
||||||
|
|
||||||
|
### ⚠️ Breaking Changes
|
||||||
|
|
||||||
|
- `DIRECT_URL` environment variable removed
|
||||||
|
- Database now requires Docker or local PostgreSQL
|
||||||
|
- Supabase-specific features removed
|
||||||
|
|
||||||
|
### 📊 Performance Improvements
|
||||||
|
|
||||||
|
- Local database reduces latency
|
||||||
|
- Redis caching improves response times
|
||||||
|
- Connection pooling via Prisma
|
||||||
|
- Optimized Docker images
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- Fixed database connection handling
|
||||||
|
- Improved error messages
|
||||||
|
- Better health checks
|
||||||
|
|
||||||
|
### 🔜 Future Enhancements
|
||||||
|
|
||||||
|
- [ ] PostgreSQL replication for HA
|
||||||
|
- [ ] Redis Sentinel for failover
|
||||||
|
- [ ] Automated backup scripts
|
||||||
|
- [ ] Monitoring and alerting
|
||||||
|
- [ ] Database performance tuning
|
||||||
|
- [ ] Multi-region deployment
|
||||||
|
|
||||||
|
### 📝 Notes
|
||||||
|
|
||||||
|
- Default PostgreSQL password should be changed in production
|
||||||
|
- Always backup data before migration
|
||||||
|
- Review security settings before deployment
|
||||||
|
- Set up automated backups in production
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Migration completed successfully!** 🎉
|
||||||
|
|
||||||
|
For support, see documentation or open an issue on GitHub.
|
||||||
|
|
||||||
281
CLAUDE.md
@@ -1,281 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
**QR Master** is a production-ready SaaS application for creating and managing QR codes with advanced analytics, Stripe payment integration, and multi-tier subscription plans (FREE, PRO, BUSINESS).
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
- **Frontend**: Next.js 14 (App Router), React 18, TypeScript, Tailwind CSS, Framer Motion
|
|
||||||
- **Backend**: Next.js API Routes, Prisma ORM, PostgreSQL
|
|
||||||
- **Authentication**: NextAuth.js v4 (Credentials + Google OAuth)
|
|
||||||
- **Payments**: Stripe (subscriptions, webhooks)
|
|
||||||
- **Cache**: Redis (optional)
|
|
||||||
- **Analytics**: PostHog (optional), QR scan tracking with IP hashing
|
|
||||||
- **QR Generation**: qrcode, qr-code-styling libraries
|
|
||||||
- **Bulk Operations**: Papa Parse (CSV), ExcelJS, JSZip
|
|
||||||
- **Storage**: AWS S3 (via @aws-sdk)
|
|
||||||
|
|
||||||
## Quick Development Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Setup
|
|
||||||
npm install
|
|
||||||
npm run docker:dev # Start PostgreSQL & Redis in Docker
|
|
||||||
npx prisma migrate dev # Run migrations
|
|
||||||
npm run db:seed # Seed demo data
|
|
||||||
|
|
||||||
# Development
|
|
||||||
npm run dev # Start dev server (port 3050)
|
|
||||||
npm run lint # Run ESLint
|
|
||||||
|
|
||||||
# Database
|
|
||||||
npm run db:migrate # Run pending migrations (dev mode)
|
|
||||||
npm run db:deploy # Apply migrations (production)
|
|
||||||
npm run db:studio # Open Prisma Studio UI
|
|
||||||
npx prisma migrate reset # Reset database (drops, recreates, seeds)
|
|
||||||
|
|
||||||
# Docker
|
|
||||||
npm run docker:prod # Start full production stack
|
|
||||||
npm run docker:dev:stop # Stop dev services
|
|
||||||
npm run docker:logs # View logs
|
|
||||||
npm run docker:db # PostgreSQL CLI
|
|
||||||
npm run docker:redis # Redis CLI
|
|
||||||
npm run docker:backup # Backup database to SQL file
|
|
||||||
|
|
||||||
# Build & Deploy
|
|
||||||
npm run build # Production build
|
|
||||||
npm run start # Start production server
|
|
||||||
```
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── app/
|
|
||||||
│ └── (main)/
|
|
||||||
│ ├── (app)/ # Authenticated app pages (dashboard, bulk-creation, settings)
|
|
||||||
│ ├── (auth)/ # Auth pages (login, signup, forgot-password)
|
|
||||||
│ ├── (marketing)/ # Public pages & marketing tools
|
|
||||||
│ │ └── tools/ # QR code type-specific generators (20+ tools)
|
|
||||||
│ └── api/ # API routes organized by domain
|
|
||||||
│ ├── auth/ # Authentication (signin, signup, OAuth, password reset)
|
|
||||||
│ ├── qrs/ # QR code CRUD (GET, POST, PATCH, DELETE)
|
|
||||||
│ ├── analytics/ # Analytics summary endpoint
|
|
||||||
│ ├── stripe/ # Payment webhooks & session management
|
|
||||||
│ ├── user/ # User profile, plan, stats, password
|
|
||||||
│ ├── newsletter/ # Subscription management
|
|
||||||
│ └── [other]/ # admin, feedback, leads, bulk, etc.
|
|
||||||
├── components/
|
|
||||||
│ ├── ui/ # Reusable UI primitives (Card, Dialog, Input, etc.)
|
|
||||||
│ ├── generator/ # QR code generator components
|
|
||||||
│ ├── analytics/ # Charts, maps, data visualization
|
|
||||||
│ ├── dashboard/ # Dashboard-specific components
|
|
||||||
│ ├── settings/ # Settings & account components
|
|
||||||
│ └── SessionProvider.tsx # NextAuth session provider
|
|
||||||
├── lib/
|
|
||||||
│ ├── auth.ts # NextAuth configuration
|
|
||||||
│ ├── db.ts # Prisma client
|
|
||||||
│ ├── stripe.ts # Stripe utilities
|
|
||||||
│ ├── email.ts # Email sending (Resend)
|
|
||||||
│ ├── qr.ts # QR code generation utilities
|
|
||||||
│ ├── geo.ts # Geolocation utilities
|
|
||||||
│ ├── hash.ts # IP hashing (privacy)
|
|
||||||
│ ├── csrf.ts # CSRF token generation/validation
|
|
||||||
│ ├── rateLimit.ts # Rate limiting utilities
|
|
||||||
│ ├── schema.ts # Zod validation schemas
|
|
||||||
│ ├── validationSchemas.ts # Additional validation
|
|
||||||
│ └── cookieConfig.ts # Cookie configuration
|
|
||||||
├── hooks/
|
|
||||||
│ ├── useCsrf.ts # CSRF token hook
|
|
||||||
│ └── useTranslation.ts # i18n hook
|
|
||||||
└── types/
|
|
||||||
└── analytics.ts # Analytics type definitions
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database Architecture
|
|
||||||
|
|
||||||
**Key Models** (see `prisma/schema.prisma`):
|
|
||||||
|
|
||||||
- **User**: User accounts with Stripe subscription fields
|
|
||||||
- **QRCode**: QR code records (static/dynamic, multiple content types)
|
|
||||||
- **QRScan**: Analytics data (ts, ipHash, device, os, country, UTM params)
|
|
||||||
- **Account/Session**: NextAuth authentication tables
|
|
||||||
- **Integration**: Third-party integrations
|
|
||||||
- **NewsletterSubscription**: Email subscribers
|
|
||||||
- **Lead**: Lead generation data
|
|
||||||
|
|
||||||
**QR Code Types**: URL, VCARD, GEO, PHONE, SMS, TEXT, WHATSAPP, PDF, APP, COUPON, FEEDBACK
|
|
||||||
|
|
||||||
## API Architecture
|
|
||||||
|
|
||||||
### Authentication Flow
|
|
||||||
- Credentials-based login/signup via `/api/auth/signup` and `/api/auth/simple-login`
|
|
||||||
- Google OAuth via `/api/auth/google`
|
|
||||||
- NextAuth.js session management at `/api/auth/[...nextauth]`
|
|
||||||
- Password reset: `/api/auth/forgot-password` + `/api/auth/reset-password`
|
|
||||||
|
|
||||||
### QR Code Operations
|
|
||||||
- **CRUD**: `GET/POST /api/qrs`, `GET/PATCH/DELETE /api/qrs/[id]`
|
|
||||||
- **Static Generation**: `POST /api/qrs/static`
|
|
||||||
- **Bulk Operations**: `POST /api/bulk/*` for CSV/Excel import
|
|
||||||
- **Public Redirect**: `GET /r/[slug]` (redirect + analytics tracking)
|
|
||||||
|
|
||||||
### Payments
|
|
||||||
- Stripe webhooks: `POST /api/stripe/webhook`
|
|
||||||
- Checkout session: `POST /api/stripe/checkout` or `/api/stripe/create-checkout-session`
|
|
||||||
- Customer portal: `POST /api/stripe/portal`
|
|
||||||
- Subscription sync: `POST /api/stripe/sync-subscription`
|
|
||||||
- Cancellation: `POST /api/stripe/cancel-subscription`
|
|
||||||
|
|
||||||
### Analytics
|
|
||||||
- Summary endpoint: `GET /api/analytics/summary?qrId=<id>`
|
|
||||||
- Scan tracking with hashed IP (GDPR-compliant)
|
|
||||||
|
|
||||||
## Key Implementation Patterns
|
|
||||||
|
|
||||||
### Authentication & Authorization
|
|
||||||
- NextAuth.js v4 with Prisma adapter
|
|
||||||
- Sessions stored in database
|
|
||||||
- CSRF protection on all mutations (check `useCsrf` hook)
|
|
||||||
- Password hashing with bcryptjs
|
|
||||||
|
|
||||||
### API Security
|
|
||||||
- Rate limiting on sensitive endpoints (auth, payments)
|
|
||||||
- CSRF tokens validated on POST/PATCH/DELETE
|
|
||||||
- IP hashing for privacy (IP_SALT environment variable)
|
|
||||||
- DNT header respected for analytics
|
|
||||||
|
|
||||||
### Database Operations
|
|
||||||
- Prisma ORM for all database access
|
|
||||||
- Migrations stored in `prisma/migrations/`
|
|
||||||
- Seed script for demo data in `prisma/seed.ts`
|
|
||||||
- Database indexes on frequently queried fields (userId, createdAt, etc.)
|
|
||||||
|
|
||||||
### QR Code Generation
|
|
||||||
- `qrcode` library for basic generation
|
|
||||||
- `qr-code-styling` for advanced customization
|
|
||||||
- `qrcode.react` for inline React components
|
|
||||||
- Canvas/SVG export via `html-to-image`, `jspdf`, `jszip`
|
|
||||||
|
|
||||||
### State & Validation
|
|
||||||
- Zod schemas in `/lib/schema.ts` for runtime validation
|
|
||||||
- TypeScript strict mode enabled
|
|
||||||
- Prisma provides type safety at database layer
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
**Required**:
|
|
||||||
- `DATABASE_URL` - PostgreSQL connection string
|
|
||||||
- `NEXTAUTH_SECRET` - JWT encryption secret
|
|
||||||
- `NEXTAUTH_URL` - Application URL (default: `http://localhost:3050`)
|
|
||||||
- `IP_SALT` - Salt for IP hashing
|
|
||||||
|
|
||||||
**Optional but Important**:
|
|
||||||
- `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`
|
|
||||||
- `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` - OAuth
|
|
||||||
- `REDIS_URL` - Redis connection
|
|
||||||
- `NEXT_PUBLIC_POSTHOG_KEY`, `NEXT_PUBLIC_POSTHOG_HOST` - Analytics
|
|
||||||
- `NEXT_PUBLIC_INDEXABLE` - Set to `true` for production (enables search engine indexing)
|
|
||||||
|
|
||||||
**Generate Secrets**:
|
|
||||||
```bash
|
|
||||||
openssl rand -base64 32 # NEXTAUTH_SECRET and IP_SALT
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Tasks
|
|
||||||
|
|
||||||
### Adding a New QR Code Type
|
|
||||||
1. Add type to `ContentType` enum in `prisma/schema.prisma`
|
|
||||||
2. Create generator component in `src/components/generator/` or `src/app/(main)/(marketing)/tools/`
|
|
||||||
3. Add validation schema in `src/lib/schema.ts`
|
|
||||||
4. Create API endpoint if needed in `src/app/(main)/api/qrs/`
|
|
||||||
|
|
||||||
### Creating a New Marketing Tool Page
|
|
||||||
1. Create page at `src/app/(main)/(marketing)/tools/[tool-name]/page.tsx`
|
|
||||||
2. Create generator component in same directory
|
|
||||||
3. Add SEO metadata in page component
|
|
||||||
4. Tool should be static (no database) or use public API endpoints
|
|
||||||
|
|
||||||
### Adding a New API Endpoint
|
|
||||||
1. Create route file in appropriate directory under `src/app/(main)/api/`
|
|
||||||
2. Add Zod validation schema in `src/lib/schema.ts`
|
|
||||||
3. Check authentication with `getServerSession()` if needed
|
|
||||||
4. Implement rate limiting for sensitive operations
|
|
||||||
5. Return typed responses with proper status codes
|
|
||||||
|
|
||||||
### Database Schema Changes
|
|
||||||
1. Update `prisma/schema.prisma`
|
|
||||||
2. Run `npx prisma migrate dev --name <migration-name>`
|
|
||||||
3. This creates migration file and updates Prisma client
|
|
||||||
4. Test with `npm run db:seed` if demo data affected
|
|
||||||
|
|
||||||
## Testing & Debugging
|
|
||||||
|
|
||||||
- Demo account (after seed): email: `demo@qrmaster.com`, password: `demo123`
|
|
||||||
- Prisma Studio: `npm run db:studio` - visual database browser
|
|
||||||
- API testing: Check `/src/app/(main)/api/` for examples
|
|
||||||
- Frontend: Pages hot-reload on changes during `npm run dev`
|
|
||||||
|
|
||||||
## Performance Considerations
|
|
||||||
|
|
||||||
- PostgreSQL indexes on `QRCode(userId, createdAt)` and `QRScan(qrId, ts)`
|
|
||||||
- Redis optional but recommended for caching analytics
|
|
||||||
- Static export for marketing pages when possible
|
|
||||||
- Image optimization enabled in `next.config.mjs`
|
|
||||||
- Prisma connection pooling recommended for production
|
|
||||||
|
|
||||||
## Database Change Policy
|
|
||||||
|
|
||||||
**IMPORTANT: No Prisma migrations.** All database schema changes (new columns, new enum values, new tables) must be applied via raw SQL commands directly against the running PostgreSQL instance.
|
|
||||||
|
|
||||||
Workflow for schema changes:
|
|
||||||
1. Write the raw SQL (e.g. `ALTER TABLE`, `ALTER TYPE ... ADD VALUE`)
|
|
||||||
2. Run via Docker: `npm run docker:db` then execute SQL, or use `docker-compose exec db psql -U postgres -d qrmaster -c "..."`
|
|
||||||
3. Update `prisma/schema.prisma` to match (so Prisma client types stay in sync)
|
|
||||||
4. Run `npx prisma generate` to regenerate the client (no `migrate`)
|
|
||||||
|
|
||||||
Example — adding an enum value:
|
|
||||||
```sql
|
|
||||||
ALTER TYPE "ContentType" ADD VALUE 'BARCODE';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Pitfalls
|
|
||||||
|
|
||||||
1. **Database Connection**: If "Can't reach database server", ensure Docker is running (`npm run docker:dev`)
|
|
||||||
2. **Prisma Out of Sync**: Run `npx prisma generate` if TypeScript errors appear
|
|
||||||
3. **Migration Conflicts**: Use `npx prisma migrate reset` to start fresh
|
|
||||||
4. **Port 3050 in Use**: Change port in `package.json` dev script or kill process
|
|
||||||
5. **Build Failures**: Check `NODE_OPTIONS='--max-old-space-size=4096'` in build script - set higher if needed
|
|
||||||
|
|
||||||
## SEO & Content
|
|
||||||
|
|
||||||
- Schema.org structured data implemented for products, organizations, FAQs
|
|
||||||
- Breadcrumb navigation for UX/SEO
|
|
||||||
- Meta tags configured per page
|
|
||||||
- Open Graph images at `/api/og`
|
|
||||||
- Sitemap generation via next-sitemap
|
|
||||||
- Google Indexing API + IndexNow submission scripts available
|
|
||||||
|
|
||||||
## Deployment Notes
|
|
||||||
|
|
||||||
### Docker (Self-Hosted)
|
|
||||||
```bash
|
|
||||||
npm run docker:prod # Builds and starts full stack
|
|
||||||
docker-compose exec web npx prisma migrate deploy # Run migrations in container
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vercel
|
|
||||||
- Push to GitHub and import in Vercel dashboard
|
|
||||||
- Set environment variables in Vercel settings
|
|
||||||
- Requires external PostgreSQL database (Vercel Postgres, Supabase, etc.)
|
|
||||||
- Redis is optional
|
|
||||||
|
|
||||||
## Additional Resources
|
|
||||||
|
|
||||||
- README.md - Detailed setup and feature overview
|
|
||||||
- DOCKER_SETUP.md - Complete Docker deployment guide
|
|
||||||
- prisma/schema.prisma - Database schema and relationships
|
|
||||||
- env.example - Environment variable template
|
|
||||||
269
DEPLOYMENT_CHECKLIST.md
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
# 🚀 Deployment Checklist für QR Master
|
||||||
|
|
||||||
|
Diese Checkliste enthält alle notwendigen Änderungen vor dem Push nach Gitea und dem Production Deployment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 1. Environment Variables (.env)
|
||||||
|
|
||||||
|
### Basis URLs ändern
|
||||||
|
```bash
|
||||||
|
# Von:
|
||||||
|
NEXT_PUBLIC_APP_URL=http://localhost:3050
|
||||||
|
NEXTAUTH_URL=http://localhost:3050
|
||||||
|
|
||||||
|
# Zu:
|
||||||
|
NEXT_PUBLIC_APP_URL=https://www.qrmaster.net
|
||||||
|
NEXTAUTH_URL=https://www.qrmaster.net
|
||||||
|
```
|
||||||
|
|
||||||
|
### Secrets generieren (falls noch nicht geschehen)
|
||||||
|
```bash
|
||||||
|
# NEXTAUTH_SECRET (für JWT/Session Encryption)
|
||||||
|
openssl rand -base64 32
|
||||||
|
|
||||||
|
# IP_SALT (für DSGVO-konforme IP-Hashing)
|
||||||
|
openssl rand -base64 32
|
||||||
|
```
|
||||||
|
|
||||||
|
Bereits generiert:
|
||||||
|
- ✅ NEXTAUTH_SECRET: `PT8XVydC4v7QluCz/mV1yb7Y3docSFZeFDioJz4ZE98=`
|
||||||
|
- ✅ IP_SALT: `j/aluIpzsgn5Z6cbF4conM6ApK5cj4jDagkswzfgQPc=`
|
||||||
|
|
||||||
|
### Database URLs
|
||||||
|
```bash
|
||||||
|
# Development (localhost):
|
||||||
|
DATABASE_URL="postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public"
|
||||||
|
DIRECT_URL="postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public"
|
||||||
|
|
||||||
|
# Production (anpassen an deinen Server):
|
||||||
|
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/qrmaster?schema=public"
|
||||||
|
DIRECT_URL="postgresql://USER:PASSWORD@HOST:5432/qrmaster?schema=public"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 2. Google OAuth Configuration
|
||||||
|
|
||||||
|
### Redirect URIs in Google Cloud Console hinzufügen
|
||||||
|
|
||||||
|
1. Gehe zu: https://console.cloud.google.com/apis/credentials
|
||||||
|
2. Wähle deine OAuth 2.0 Client ID: `683784117141-ci1d928jo8f9g6i1isrveflmrinp92l4.apps.googleusercontent.com`
|
||||||
|
3. Füge folgende **Authorized redirect URIs** hinzu:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://www.qrmaster.net/api/auth/callback/google
|
||||||
|
```
|
||||||
|
|
||||||
|
**Optional** (für Staging/Testing):
|
||||||
|
```
|
||||||
|
http://localhost:3050/api/auth/callback/google
|
||||||
|
https://staging.qrmaster.net/api/auth/callback/google
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💳 3. Stripe Configuration
|
||||||
|
|
||||||
|
### ⚠️ WICHTIG: Von Test Mode zu Live Mode wechseln
|
||||||
|
|
||||||
|
#### Current (Test Mode):
|
||||||
|
```bash
|
||||||
|
STRIPE_SECRET_KEY=sk_test_51QYL7gP9xM...
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_51QYL7gP9xM...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Production (Live Mode):
|
||||||
|
1. Gehe zu: https://dashboard.stripe.com/
|
||||||
|
2. Wechsle von **Test Mode** zu **Live Mode** (Toggle oben rechts)
|
||||||
|
3. Hole dir die **Live Keys**:
|
||||||
|
- `API Keys` → `Secret key` (beginnt mit `sk_live_`)
|
||||||
|
- `API Keys` → `Publishable key` (beginnt mit `pk_live_`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Production Keys:
|
||||||
|
STRIPE_SECRET_KEY=sk_live_XXXXX
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_XXXXX
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Webhook Secret (Production)
|
||||||
|
1. Erstelle einen neuen Webhook Endpoint: https://dashboard.stripe.com/webhooks
|
||||||
|
2. Endpoint URL: `https://www.qrmaster.net/api/webhooks/stripe`
|
||||||
|
3. Events to listen:
|
||||||
|
- `checkout.session.completed`
|
||||||
|
- `customer.subscription.updated`
|
||||||
|
- `customer.subscription.deleted`
|
||||||
|
- `invoice.payment_succeeded`
|
||||||
|
- `invoice.payment_failed`
|
||||||
|
4. Kopiere den **Signing Secret** (beginnt mit `whsec_`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
STRIPE_WEBHOOK_SECRET=whsec_XXXXX
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Price IDs aktualisieren
|
||||||
|
Erstelle Produkte und Preise in **Live Mode**:
|
||||||
|
1. https://dashboard.stripe.com/products
|
||||||
|
2. Erstelle "Pro" und "Business" Pläne
|
||||||
|
3. Kopiere die Price IDs (beginnen mit `price_`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
STRIPE_PRICE_ID_PRO_MONTHLY=price_XXXXX
|
||||||
|
STRIPE_PRICE_ID_PRO_YEARLY=price_XXXXX
|
||||||
|
STRIPE_PRICE_ID_BUSINESS_MONTHLY=price_XXXXX
|
||||||
|
STRIPE_PRICE_ID_BUSINESS_YEARLY=price_XXXXX
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📧 4. Resend Email Configuration
|
||||||
|
|
||||||
|
### Domain Verification
|
||||||
|
1. Gehe zu: https://resend.com/domains
|
||||||
|
2. Füge Domain hinzu: `qrmaster.net`
|
||||||
|
3. Konfiguriere DNS Records (SPF, DKIM, DMARC)
|
||||||
|
4. Warte auf Verification
|
||||||
|
|
||||||
|
### From Email anpassen
|
||||||
|
Aktuell verwendet alle Emails: `onboarding@resend.dev` (Resend's Test Domain)
|
||||||
|
|
||||||
|
Nach Domain Verification in `src/lib/email.ts` ändern:
|
||||||
|
```typescript
|
||||||
|
// Von:
|
||||||
|
from: 'Timo from QR Master <onboarding@resend.dev>',
|
||||||
|
|
||||||
|
// Zu:
|
||||||
|
from: 'Timo from QR Master <hello@qrmaster.net>',
|
||||||
|
// oder
|
||||||
|
from: 'Timo from QR Master <noreply@qrmaster.net>',
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 5. SEO Configuration
|
||||||
|
|
||||||
|
### Bereits korrekt konfiguriert ✅
|
||||||
|
```bash
|
||||||
|
NEXT_PUBLIC_INDEXABLE=true # ✅ Bereits gesetzt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sitemap & robots.txt prüfen
|
||||||
|
- Sitemap: `https://www.qrmaster.net/sitemap.xml`
|
||||||
|
- Robots: `https://www.qrmaster.net/robots.txt`
|
||||||
|
|
||||||
|
Nach Deployment testen!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 6. PostHog Analytics (Optional)
|
||||||
|
|
||||||
|
Falls du PostHog nutzt:
|
||||||
|
```bash
|
||||||
|
NEXT_PUBLIC_POSTHOG_KEY=phc_XXXXX
|
||||||
|
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐳 7. Docker Deployment
|
||||||
|
|
||||||
|
### docker-compose.yml prüfen
|
||||||
|
Stelle sicher, dass alle ENV Variables korrekt gemappt sind:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
NEXTAUTH_URL: https://www.qrmaster.net
|
||||||
|
NEXT_PUBLIC_APP_URL: https://www.qrmaster.net
|
||||||
|
# ... weitere vars
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deployment Commands
|
||||||
|
```bash
|
||||||
|
# Build & Deploy
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Database Migration (nach erstem Deploy)
|
||||||
|
docker-compose exec web npm run db:migrate
|
||||||
|
|
||||||
|
# Logs checken
|
||||||
|
docker-compose logs -f web
|
||||||
|
|
||||||
|
# Health Check
|
||||||
|
curl https://www.qrmaster.net
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 8. Security Checklist
|
||||||
|
|
||||||
|
- [ ] ✅ NEXTAUTH_SECRET ist gesetzt und sicher (32+ Zeichen)
|
||||||
|
- [ ] ✅ IP_SALT ist gesetzt und sicher
|
||||||
|
- [ ] ⚠️ Stripe ist auf **Live Mode** umgestellt
|
||||||
|
- [ ] ⚠️ Google OAuth Redirect URIs enthalten Production URL
|
||||||
|
- [ ] ⚠️ Resend Domain ist verifiziert
|
||||||
|
- [ ] ⚠️ Webhook Secrets sind für Production gesetzt
|
||||||
|
- [ ] ⚠️ Database URLs zeigen auf Production DB
|
||||||
|
- [ ] ⚠️ Keine Test/Dev Secrets in Production
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 9. Vor dem Git Push
|
||||||
|
|
||||||
|
### Files prüfen
|
||||||
|
```bash
|
||||||
|
# .env sollte NICHT committet werden!
|
||||||
|
git status
|
||||||
|
|
||||||
|
# Falls .env in Git ist:
|
||||||
|
git rm --cached .env
|
||||||
|
echo ".env" >> .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sensible Daten entfernen
|
||||||
|
- [ ] Keine API Keys im Code
|
||||||
|
- [ ] Keine Secrets in Config Files
|
||||||
|
- [ ] `.env` ist in `.gitignore`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 10. Nach dem Deployment testen
|
||||||
|
|
||||||
|
### Funktionen testen
|
||||||
|
1. **Google OAuth Login**: https://www.qrmaster.net/login
|
||||||
|
2. **QR Code erstellen**: https://www.qrmaster.net/create
|
||||||
|
3. **Stripe Checkout**: Testprodukt kaufen mit echten Stripe Test Cards
|
||||||
|
4. **Email Delivery**: Password Reset testen
|
||||||
|
5. **Analytics**: PostHog Events tracken
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
```bash
|
||||||
|
# Server Logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Database Status
|
||||||
|
docker-compose exec db psql -U postgres -d qrmaster -c "SELECT COUNT(*) FROM \"User\";"
|
||||||
|
|
||||||
|
# Redis Status
|
||||||
|
docker-compose exec redis redis-cli PING
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Support Kontakte
|
||||||
|
|
||||||
|
- **Stripe Support**: https://support.stripe.com
|
||||||
|
- **Google Cloud Support**: https://support.google.com/cloud
|
||||||
|
- **Resend Support**: https://resend.com/docs
|
||||||
|
- **Next.js Docs**: https://nextjs.org/docs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Deployment erfolgreich!
|
||||||
|
|
||||||
|
Nach erfolgreichem Deployment:
|
||||||
|
1. ✅ Teste alle wichtigen Features
|
||||||
|
2. ✅ Monitor Logs für Fehler
|
||||||
|
3. ✅ Prüfe Analytics Dashboard
|
||||||
|
4. ✅ Backup der Production Database erstellen
|
||||||
|
|
||||||
|
**Good luck! 🚀**
|
||||||
922
DOCKER_SETUP.md
@@ -1,461 +1,461 @@
|
|||||||
# 🐳 Docker Setup Guide for QR Master
|
# 🐳 Docker Setup Guide for QR Master
|
||||||
|
|
||||||
Complete guide for setting up and running QR Master with Docker and PostgreSQL.
|
Complete guide for setting up and running QR Master with Docker and PostgreSQL.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Docker Desktop (Windows/Mac) or Docker Engine (Linux)
|
- Docker Desktop (Windows/Mac) or Docker Engine (Linux)
|
||||||
- Docker Compose V2
|
- Docker Compose V2
|
||||||
- Git
|
- Git
|
||||||
- Node.js 18+ (for local development)
|
- Node.js 18+ (for local development)
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
### Option 1: Development Mode (Recommended for Development)
|
### Option 1: Development Mode (Recommended for Development)
|
||||||
|
|
||||||
Run only the database services in Docker and the Next.js app on your host machine:
|
Run only the database services in Docker and the Next.js app on your host machine:
|
||||||
|
|
||||||
1. **Clone the repository**
|
1. **Clone the repository**
|
||||||
```bash
|
```bash
|
||||||
git clone <your-repo-url>
|
git clone <your-repo-url>
|
||||||
cd QRMASTER
|
cd QRMASTER
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Install dependencies**
|
2. **Install dependencies**
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Set up environment variables**
|
3. **Set up environment variables**
|
||||||
```bash
|
```bash
|
||||||
cp env.example .env
|
cp env.example .env
|
||||||
```
|
```
|
||||||
Edit `.env` and update the values, especially:
|
Edit `.env` and update the values, especially:
|
||||||
- `NEXTAUTH_SECRET` (generate with: `openssl rand -base64 32`)
|
- `NEXTAUTH_SECRET` (generate with: `openssl rand -base64 32`)
|
||||||
- `IP_SALT` (generate with: `openssl rand -base64 32`)
|
- `IP_SALT` (generate with: `openssl rand -base64 32`)
|
||||||
|
|
||||||
4. **Start database services**
|
4. **Start database services**
|
||||||
```bash
|
```bash
|
||||||
npm run docker:dev
|
npm run docker:dev
|
||||||
```
|
```
|
||||||
This starts PostgreSQL, Redis, and Adminer.
|
This starts PostgreSQL, Redis, and Adminer.
|
||||||
|
|
||||||
5. **Run database migrations**
|
5. **Run database migrations**
|
||||||
```bash
|
```bash
|
||||||
npm run db:migrate
|
npm run db:migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **Seed the database (optional)**
|
6. **Seed the database (optional)**
|
||||||
```bash
|
```bash
|
||||||
npm run db:seed
|
npm run db:seed
|
||||||
```
|
```
|
||||||
|
|
||||||
7. **Start the development server**
|
7. **Start the development server**
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
8. **Access the application**
|
8. **Access the application**
|
||||||
- **App**: http://localhost:3050
|
- **App**: http://localhost:3050
|
||||||
- **Database UI (Adminer)**: http://localhost:8080
|
- **Database UI (Adminer)**: http://localhost:8080
|
||||||
- System: PostgreSQL
|
- System: PostgreSQL
|
||||||
- Server: db
|
- Server: db
|
||||||
- Username: postgres
|
- Username: postgres
|
||||||
- Password: postgres
|
- Password: postgres
|
||||||
- Database: qrmaster
|
- Database: qrmaster
|
||||||
|
|
||||||
### Option 2: Full Production Mode
|
### Option 2: Full Production Mode
|
||||||
|
|
||||||
Run everything in Docker containers:
|
Run everything in Docker containers:
|
||||||
|
|
||||||
1. **Clone and configure**
|
1. **Clone and configure**
|
||||||
```bash
|
```bash
|
||||||
git clone <your-repo-url>
|
git clone <your-repo-url>
|
||||||
cd QRMASTER
|
cd QRMASTER
|
||||||
cp env.example .env
|
cp env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Update environment variables in `.env`**
|
2. **Update environment variables in `.env`**
|
||||||
Make sure to set strong secrets in production!
|
Make sure to set strong secrets in production!
|
||||||
|
|
||||||
3. **Build and start all services**
|
3. **Build and start all services**
|
||||||
```bash
|
```bash
|
||||||
npm run docker:prod
|
npm run docker:prod
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Run migrations inside the container**
|
4. **Run migrations inside the container**
|
||||||
```bash
|
```bash
|
||||||
docker-compose exec web npx prisma migrate deploy
|
docker-compose exec web npx prisma migrate deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Access the application**
|
5. **Access the application**
|
||||||
- **App**: http://localhost:3050
|
- **App**: http://localhost:3050
|
||||||
|
|
||||||
## 📦 What Gets Installed
|
## 📦 What Gets Installed
|
||||||
|
|
||||||
### Services
|
### Services
|
||||||
|
|
||||||
1. **PostgreSQL 16** - Main database
|
1. **PostgreSQL 16** - Main database
|
||||||
- Port: 5432
|
- Port: 5432
|
||||||
- Database: qrmaster
|
- Database: qrmaster
|
||||||
- User: postgres
|
- User: postgres
|
||||||
- Password: postgres (change in production!)
|
- Password: postgres (change in production!)
|
||||||
|
|
||||||
2. **Redis 7** - Caching and rate limiting
|
2. **Redis 7** - Caching and rate limiting
|
||||||
- Port: 6379
|
- Port: 6379
|
||||||
- Max memory: 256MB with LRU eviction
|
- Max memory: 256MB with LRU eviction
|
||||||
- Persistence: AOF enabled
|
- Persistence: AOF enabled
|
||||||
|
|
||||||
3. **Next.js App** - The QR Master application
|
3. **Next.js App** - The QR Master application
|
||||||
- Port: 3000
|
- Port: 3000
|
||||||
- Built with production optimizations
|
- Built with production optimizations
|
||||||
|
|
||||||
4. **Adminer** - Database management UI (dev only)
|
4. **Adminer** - Database management UI (dev only)
|
||||||
- Port: 8080
|
- Port: 8080
|
||||||
- Lightweight alternative to pgAdmin
|
- Lightweight alternative to pgAdmin
|
||||||
|
|
||||||
## 🗄️ Database Management
|
## 🗄️ Database Management
|
||||||
|
|
||||||
### Prisma Commands
|
### Prisma Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate Prisma Client
|
# Generate Prisma Client
|
||||||
npm run db:generate
|
npm run db:generate
|
||||||
|
|
||||||
# Create a new migration
|
# Create a new migration
|
||||||
npm run db:migrate
|
npm run db:migrate
|
||||||
|
|
||||||
# Deploy migrations (production)
|
# Deploy migrations (production)
|
||||||
npm run db:deploy
|
npm run db:deploy
|
||||||
|
|
||||||
# Seed the database
|
# Seed the database
|
||||||
npm run db:seed
|
npm run db:seed
|
||||||
|
|
||||||
# Open Prisma Studio
|
# Open Prisma Studio
|
||||||
npm run db:studio
|
npm run db:studio
|
||||||
```
|
```
|
||||||
|
|
||||||
### Direct PostgreSQL Access
|
### Direct PostgreSQL Access
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Connect to PostgreSQL
|
# Connect to PostgreSQL
|
||||||
docker-compose exec db psql -U postgres -d qrmaster
|
docker-compose exec db psql -U postgres -d qrmaster
|
||||||
|
|
||||||
# Backup database
|
# Backup database
|
||||||
docker-compose exec db pg_dump -U postgres qrmaster > backup_$(date +%Y%m%d).sql
|
docker-compose exec db pg_dump -U postgres qrmaster > backup_$(date +%Y%m%d).sql
|
||||||
|
|
||||||
# Restore database
|
# Restore database
|
||||||
docker-compose exec -T db psql -U postgres qrmaster < backup.sql
|
docker-compose exec -T db psql -U postgres qrmaster < backup.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔧 Docker Commands
|
## 🔧 Docker Commands
|
||||||
|
|
||||||
### Starting Services
|
### Starting Services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Development mode (database only)
|
# Development mode (database only)
|
||||||
npm run docker:dev
|
npm run docker:dev
|
||||||
# or
|
# or
|
||||||
docker-compose -f docker-compose.dev.yml up -d
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
# Production mode (full stack)
|
# Production mode (full stack)
|
||||||
npm run docker:prod
|
npm run docker:prod
|
||||||
# or
|
# or
|
||||||
docker-compose up -d --build
|
docker-compose up -d --build
|
||||||
|
|
||||||
# Production with database UI
|
# Production with database UI
|
||||||
docker-compose --profile dev up -d
|
docker-compose --profile dev up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### Stopping Services
|
### Stopping Services
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop all services
|
# Stop all services
|
||||||
npm run docker:stop
|
npm run docker:stop
|
||||||
# or
|
# or
|
||||||
docker-compose down
|
docker-compose down
|
||||||
|
|
||||||
# Stop and remove volumes (⚠️ deletes data!)
|
# Stop and remove volumes (⚠️ deletes data!)
|
||||||
docker-compose down -v
|
docker-compose down -v
|
||||||
```
|
```
|
||||||
|
|
||||||
### Viewing Logs
|
### Viewing Logs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# All services
|
# All services
|
||||||
docker-compose logs -f
|
docker-compose logs -f
|
||||||
|
|
||||||
# Specific service
|
# Specific service
|
||||||
docker-compose logs -f web
|
docker-compose logs -f web
|
||||||
docker-compose logs -f db
|
docker-compose logs -f db
|
||||||
docker-compose logs -f redis
|
docker-compose logs -f redis
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rebuilding
|
### Rebuilding
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Rebuild the web application
|
# Rebuild the web application
|
||||||
docker-compose build web
|
docker-compose build web
|
||||||
|
|
||||||
# Rebuild without cache
|
# Rebuild without cache
|
||||||
docker-compose build --no-cache web
|
docker-compose build --no-cache web
|
||||||
|
|
||||||
# Rebuild and restart
|
# Rebuild and restart
|
||||||
docker-compose up -d --build web
|
docker-compose up -d --build web
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🌍 Environment Variables
|
## 🌍 Environment Variables
|
||||||
|
|
||||||
### Required Variables
|
### Required Variables
|
||||||
|
|
||||||
```env
|
```env
|
||||||
# Database (automatically set for Docker)
|
# Database (automatically set for Docker)
|
||||||
DATABASE_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
DATABASE_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||||
|
|
||||||
# NextAuth
|
# NextAuth
|
||||||
NEXTAUTH_URL=http://localhost:3050
|
NEXTAUTH_URL=http://localhost:3050
|
||||||
NEXTAUTH_SECRET=<generate-with-openssl-rand-base64-32>
|
NEXTAUTH_SECRET=<generate-with-openssl-rand-base64-32>
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
IP_SALT=<generate-with-openssl-rand-base64-32>
|
IP_SALT=<generate-with-openssl-rand-base64-32>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Optional Variables
|
### Optional Variables
|
||||||
|
|
||||||
```env
|
```env
|
||||||
# OAuth (Google)
|
# OAuth (Google)
|
||||||
GOOGLE_CLIENT_ID=
|
GOOGLE_CLIENT_ID=
|
||||||
GOOGLE_CLIENT_SECRET=
|
GOOGLE_CLIENT_SECRET=
|
||||||
|
|
||||||
# Redis (automatically set for Docker)
|
# Redis (automatically set for Docker)
|
||||||
REDIS_URL=redis://redis:6379
|
REDIS_URL=redis://redis:6379
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
ENABLE_DEMO=false
|
ENABLE_DEMO=false
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generating Secrets
|
### Generating Secrets
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# On Linux/Mac
|
# On Linux/Mac
|
||||||
openssl rand -base64 32
|
openssl rand -base64 32
|
||||||
|
|
||||||
# On Windows (PowerShell)
|
# On Windows (PowerShell)
|
||||||
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))
|
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔍 Health Checks
|
## 🔍 Health Checks
|
||||||
|
|
||||||
All services include health checks:
|
All services include health checks:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check status of all services
|
# Check status of all services
|
||||||
docker-compose ps
|
docker-compose ps
|
||||||
|
|
||||||
# Check database health
|
# Check database health
|
||||||
docker-compose exec db pg_isready -U postgres
|
docker-compose exec db pg_isready -U postgres
|
||||||
|
|
||||||
# Check Redis health
|
# Check Redis health
|
||||||
docker-compose exec redis redis-cli ping
|
docker-compose exec redis redis-cli ping
|
||||||
|
|
||||||
# Check web app health
|
# Check web app health
|
||||||
curl http://localhost:3050
|
curl http://localhost:3050
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
### Database Connection Failed
|
### Database Connection Failed
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check database is running
|
# Check database is running
|
||||||
docker-compose ps db
|
docker-compose ps db
|
||||||
|
|
||||||
# Check database logs
|
# Check database logs
|
||||||
docker-compose logs db
|
docker-compose logs db
|
||||||
|
|
||||||
# Restart database
|
# Restart database
|
||||||
docker-compose restart db
|
docker-compose restart db
|
||||||
|
|
||||||
# Test connection
|
# Test connection
|
||||||
docker-compose exec db psql -U postgres -d qrmaster -c "SELECT version();"
|
docker-compose exec db psql -U postgres -d qrmaster -c "SELECT version();"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Port Already in Use
|
### Port Already in Use
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Windows - find process using port
|
# Windows - find process using port
|
||||||
netstat -ano | findstr :3050
|
netstat -ano | findstr :3050
|
||||||
|
|
||||||
# Linux/Mac - find process using port
|
# Linux/Mac - find process using port
|
||||||
lsof -i :3050
|
lsof -i :3050
|
||||||
|
|
||||||
# Kill the process or change the port in docker-compose.yml
|
# Kill the process or change the port in docker-compose.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Migration Errors
|
### Migration Errors
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Reset the database (⚠️ deletes all data!)
|
# Reset the database (⚠️ deletes all data!)
|
||||||
docker-compose exec web npx prisma migrate reset
|
docker-compose exec web npx prisma migrate reset
|
||||||
|
|
||||||
# Or manually
|
# Or manually
|
||||||
docker-compose down -v
|
docker-compose down -v
|
||||||
docker-compose up -d db redis
|
docker-compose up -d db redis
|
||||||
npm run db:migrate
|
npm run db:migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
### Container Won't Start
|
### Container Won't Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Remove all containers and volumes
|
# Remove all containers and volumes
|
||||||
docker-compose down -v
|
docker-compose down -v
|
||||||
|
|
||||||
# Remove dangling images
|
# Remove dangling images
|
||||||
docker image prune
|
docker image prune
|
||||||
|
|
||||||
# Rebuild from scratch
|
# Rebuild from scratch
|
||||||
docker-compose build --no-cache
|
docker-compose build --no-cache
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### Prisma Client Not Generated
|
### Prisma Client Not Generated
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate Prisma Client
|
# Generate Prisma Client
|
||||||
npm run db:generate
|
npm run db:generate
|
||||||
|
|
||||||
# Or in Docker
|
# Or in Docker
|
||||||
docker-compose exec web npx prisma generate
|
docker-compose exec web npx prisma generate
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔐 Production Checklist
|
## 🔐 Production Checklist
|
||||||
|
|
||||||
Before deploying to production:
|
Before deploying to production:
|
||||||
|
|
||||||
- [ ] Change PostgreSQL password
|
- [ ] Change PostgreSQL password
|
||||||
- [ ] Generate strong `NEXTAUTH_SECRET`
|
- [ ] Generate strong `NEXTAUTH_SECRET`
|
||||||
- [ ] Generate strong `IP_SALT`
|
- [ ] Generate strong `IP_SALT`
|
||||||
- [ ] Set proper `NEXTAUTH_URL` (your domain)
|
- [ ] Set proper `NEXTAUTH_URL` (your domain)
|
||||||
- [ ] Configure OAuth credentials (if using)
|
- [ ] Configure OAuth credentials (if using)
|
||||||
- [ ] Set up database backups
|
- [ ] Set up database backups
|
||||||
- [ ] Configure Redis persistence
|
- [ ] Configure Redis persistence
|
||||||
- [ ] Set up monitoring and logging
|
- [ ] Set up monitoring and logging
|
||||||
- [ ] Enable HTTPS/SSL
|
- [ ] Enable HTTPS/SSL
|
||||||
- [ ] Review and adjust rate limits
|
- [ ] Review and adjust rate limits
|
||||||
- [ ] Set up a reverse proxy (nginx/Traefik)
|
- [ ] Set up a reverse proxy (nginx/Traefik)
|
||||||
- [ ] Configure firewall rules
|
- [ ] Configure firewall rules
|
||||||
- [ ] Set up automated database backups
|
- [ ] Set up automated database backups
|
||||||
|
|
||||||
## 📊 Monitoring
|
## 📊 Monitoring
|
||||||
|
|
||||||
### Resource Usage
|
### Resource Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View resource usage
|
# View resource usage
|
||||||
docker stats
|
docker stats
|
||||||
|
|
||||||
# View specific container
|
# View specific container
|
||||||
docker stats qrmaster-web qrmaster-db qrmaster-redis
|
docker stats qrmaster-web qrmaster-db qrmaster-redis
|
||||||
```
|
```
|
||||||
|
|
||||||
### Database Size
|
### Database Size
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check database size
|
# Check database size
|
||||||
docker-compose exec db psql -U postgres -d qrmaster -c "
|
docker-compose exec db psql -U postgres -d qrmaster -c "
|
||||||
SELECT
|
SELECT
|
||||||
pg_size_pretty(pg_database_size('qrmaster')) as db_size,
|
pg_size_pretty(pg_database_size('qrmaster')) as db_size,
|
||||||
pg_size_pretty(pg_total_relation_size('\"QRCode\"')) as qrcode_table_size;
|
pg_size_pretty(pg_total_relation_size('\"QRCode\"')) as qrcode_table_size;
|
||||||
"
|
"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Redis Info
|
### Redis Info
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Get Redis info
|
# Get Redis info
|
||||||
docker-compose exec redis redis-cli info
|
docker-compose exec redis redis-cli info
|
||||||
|
|
||||||
# Get memory usage
|
# Get memory usage
|
||||||
docker-compose exec redis redis-cli info memory
|
docker-compose exec redis redis-cli info memory
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔄 Backup and Recovery
|
## 🔄 Backup and Recovery
|
||||||
|
|
||||||
### Automated Backups
|
### Automated Backups
|
||||||
|
|
||||||
Create a backup script `backup.sh`:
|
Create a backup script `backup.sh`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
BACKUP_DIR="./backups"
|
BACKUP_DIR="./backups"
|
||||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
mkdir -p $BACKUP_DIR
|
mkdir -p $BACKUP_DIR
|
||||||
|
|
||||||
# Backup database
|
# Backup database
|
||||||
docker-compose exec -T db pg_dump -U postgres qrmaster > "$BACKUP_DIR/qrmaster_$TIMESTAMP.sql"
|
docker-compose exec -T db pg_dump -U postgres qrmaster > "$BACKUP_DIR/qrmaster_$TIMESTAMP.sql"
|
||||||
|
|
||||||
# Backup Redis
|
# Backup Redis
|
||||||
docker-compose exec redis redis-cli BGSAVE
|
docker-compose exec redis redis-cli BGSAVE
|
||||||
|
|
||||||
echo "Backup completed: $BACKUP_DIR/qrmaster_$TIMESTAMP.sql"
|
echo "Backup completed: $BACKUP_DIR/qrmaster_$TIMESTAMP.sql"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Restore from Backup
|
### Restore from Backup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop the web service
|
# Stop the web service
|
||||||
docker-compose stop web
|
docker-compose stop web
|
||||||
|
|
||||||
# Restore database
|
# Restore database
|
||||||
cat backup_20241013.sql | docker-compose exec -T db psql -U postgres qrmaster
|
cat backup_20241013.sql | docker-compose exec -T db psql -U postgres qrmaster
|
||||||
|
|
||||||
# Restart
|
# Restart
|
||||||
docker-compose start web
|
docker-compose start web
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 Performance Tips
|
## 🚀 Performance Tips
|
||||||
|
|
||||||
1. **Increase PostgreSQL shared buffers** (in production):
|
1. **Increase PostgreSQL shared buffers** (in production):
|
||||||
Edit `docker-compose.yml`:
|
Edit `docker-compose.yml`:
|
||||||
```yaml
|
```yaml
|
||||||
db:
|
db:
|
||||||
command: postgres -c shared_buffers=256MB -c max_connections=100
|
command: postgres -c shared_buffers=256MB -c max_connections=100
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Enable Redis persistence**:
|
2. **Enable Redis persistence**:
|
||||||
Already configured with AOF in docker-compose.yml
|
Already configured with AOF in docker-compose.yml
|
||||||
|
|
||||||
3. **Use connection pooling**:
|
3. **Use connection pooling**:
|
||||||
Prisma already includes connection pooling
|
Prisma already includes connection pooling
|
||||||
|
|
||||||
4. **Monitor slow queries**:
|
4. **Monitor slow queries**:
|
||||||
```bash
|
```bash
|
||||||
docker-compose exec db psql -U postgres -d qrmaster -c "
|
docker-compose exec db psql -U postgres -d qrmaster -c "
|
||||||
SELECT query, mean_exec_time, calls
|
SELECT query, mean_exec_time, calls
|
||||||
FROM pg_stat_statements
|
FROM pg_stat_statements
|
||||||
ORDER BY mean_exec_time DESC
|
ORDER BY mean_exec_time DESC
|
||||||
LIMIT 10;"
|
LIMIT 10;"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📚 Additional Resources
|
## 📚 Additional Resources
|
||||||
|
|
||||||
- [Docker Documentation](https://docs.docker.com/)
|
- [Docker Documentation](https://docs.docker.com/)
|
||||||
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
||||||
- [Redis Documentation](https://redis.io/documentation)
|
- [Redis Documentation](https://redis.io/documentation)
|
||||||
- [Prisma Documentation](https://www.prisma.io/docs/)
|
- [Prisma Documentation](https://www.prisma.io/docs/)
|
||||||
- [Next.js Documentation](https://nextjs.org/docs)
|
- [Next.js Documentation](https://nextjs.org/docs)
|
||||||
|
|
||||||
## 🆘 Getting Help
|
## 🆘 Getting Help
|
||||||
|
|
||||||
If you encounter issues:
|
If you encounter issues:
|
||||||
|
|
||||||
1. Check the logs: `docker-compose logs -f`
|
1. Check the logs: `docker-compose logs -f`
|
||||||
2. Check service health: `docker-compose ps`
|
2. Check service health: `docker-compose ps`
|
||||||
3. Review this guide
|
3. Review this guide
|
||||||
4. Check the `docker/README.md` for more details
|
4. Check the `docker/README.md` for more details
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Happy coding! 🎉**
|
**Happy coding! 🎉**
|
||||||
|
|
||||||
|
|||||||
134
Dockerfile
@@ -1,70 +1,64 @@
|
|||||||
# ---- deps ----
|
# ---- deps ----
|
||||||
FROM node:20-alpine AS deps
|
FROM node:20-alpine AS deps
|
||||||
# Install OpenSSL for Prisma
|
# Install OpenSSL for Prisma
|
||||||
RUN apk add --no-cache openssl
|
RUN apk add --no-cache openssl
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* .npmrc* ./
|
COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* .npmrc* ./
|
||||||
# Copy prisma schema for postinstall script
|
# Copy prisma schema for postinstall script
|
||||||
COPY prisma ./prisma
|
COPY prisma ./prisma
|
||||||
RUN \
|
RUN \
|
||||||
if [ -f pnpm-lock.yaml ]; then \
|
if [ -f pnpm-lock.yaml ]; then \
|
||||||
npm i -g pnpm && pnpm i --frozen-lockfile; \
|
npm i -g pnpm && pnpm i --frozen-lockfile; \
|
||||||
elif [ -f yarn.lock ]; then \
|
elif [ -f yarn.lock ]; then \
|
||||||
yarn --frozen-lockfile; \
|
yarn --frozen-lockfile; \
|
||||||
elif [ -f package-lock.json ]; then \
|
elif [ -f package-lock.json ]; then \
|
||||||
npm ci; \
|
npm ci; \
|
||||||
else \
|
else \
|
||||||
npm install --legacy-peer-deps; \
|
npm install --legacy-peer-deps; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---- builder ----
|
# ---- builder ----
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
# Install OpenSSL for Prisma
|
# Install OpenSSL for Prisma
|
||||||
RUN apk add --no-cache openssl
|
RUN apk add --no-cache openssl
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
# Add build-time environment variables with defaults
|
# Add build-time environment variables with defaults
|
||||||
ENV NEXTAUTH_URL="https://www.qrmaster.net"
|
ENV DATABASE_URL="postgresql://postgres:postgres@db:5432/qrmaster?schema=public"
|
||||||
ENV NEXTAUTH_SECRET="build-time-secret"
|
ENV NEXTAUTH_URL="https://www.qrmaster.net"
|
||||||
ENV IP_SALT="build-time-salt"
|
ENV NEXTAUTH_SECRET="build-time-secret"
|
||||||
ENV STRIPE_SECRET_KEY="sk_test_placeholder_for_build"
|
ENV IP_SALT="build-time-salt"
|
||||||
ENV RESEND_API_KEY="re_placeholder_for_build"
|
ENV STRIPE_SECRET_KEY="sk_test_placeholder_for_build"
|
||||||
ENV NEXT_PUBLIC_APP_URL="https://www.qrmaster.net"
|
ENV RESEND_API_KEY="re_placeholder_for_build"
|
||||||
# PostHog Analytics - REQUIRED at build time for client-side bundle
|
ENV NEXT_PUBLIC_APP_URL="https://www.qrmaster.net"
|
||||||
ENV NEXT_PUBLIC_POSTHOG_KEY="phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ"
|
# PostHog Analytics - REQUIRED at build time for client-side bundle
|
||||||
ENV NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com"
|
ENV NEXT_PUBLIC_POSTHOG_KEY="phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ"
|
||||||
ENV NEXT_PUBLIC_INDEXABLE="true"
|
ENV NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com"
|
||||||
ENV NEXT_PUBLIC_FACEBOOK_PIXEL_ID="1601718491252690"
|
ENV NEXT_PUBLIC_INDEXABLE="true"
|
||||||
RUN npx prisma generate
|
RUN npx prisma generate
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ---- runner ----
|
# ---- runner ----
|
||||||
FROM node:20-alpine AS runner
|
FROM node:20-alpine AS runner
|
||||||
# Install OpenSSL for Prisma runtime
|
# Install OpenSSL for Prisma runtime
|
||||||
RUN apk add --no-cache openssl
|
RUN apk add --no-cache openssl
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
RUN adduser --system --uid 1001 nextjs
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
COPY --from=builder /app/prisma ./prisma
|
COPY --from=builder /app/prisma ./prisma
|
||||||
COPY --from=builder /app/.next/standalone ./
|
COPY --from=builder /app/.next/standalone ./
|
||||||
COPY --from=builder /app/.next/static ./.next/static
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
COPY --from=builder /app/docker/entrypoint.sh ./docker/entrypoint.sh
|
|
||||||
|
USER nextjs
|
||||||
RUN chmod +x ./docker/entrypoint.sh
|
|
||||||
|
EXPOSE 3000
|
||||||
# --- NEU: Ordner erstellen und Rechte an den nextjs User geben ---
|
|
||||||
RUN mkdir -p /app/.next/cache && chown nextjs:nodejs /app/.next/cache
|
CMD ["node", "server.js"]
|
||||||
|
|
||||||
USER nextjs
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD ["./docker/entrypoint.sh"]
|
|
||||||
40
LICENSE
@@ -1,21 +1,21 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 QR Master
|
Copyright (c) 2025 QR Master
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all
|
||||||
copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
321
MIGRATION_FROM_SUPABASE.md
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
# Migration Guide: From Supabase to Local PostgreSQL
|
||||||
|
|
||||||
|
This guide helps you migrate your QR Master application from Supabase to a local PostgreSQL database with Docker.
|
||||||
|
|
||||||
|
## What Changed
|
||||||
|
|
||||||
|
### ✅ Removed
|
||||||
|
- Supabase connection pooling (`DIRECT_URL` environment variable)
|
||||||
|
- Supabase-specific configurations
|
||||||
|
- External database dependency
|
||||||
|
|
||||||
|
### ✨ Added
|
||||||
|
- Local PostgreSQL 16 database in Docker
|
||||||
|
- Redis cache for better performance
|
||||||
|
- Adminer database management UI
|
||||||
|
- Complete Docker setup with docker-compose
|
||||||
|
- Database initialization scripts
|
||||||
|
- Development and production Docker configurations
|
||||||
|
|
||||||
|
## Migration Steps
|
||||||
|
|
||||||
|
### 1. Backup Your Supabase Database (IMPORTANT!)
|
||||||
|
|
||||||
|
Before making any changes, backup your existing data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If you have access to Supabase CLI
|
||||||
|
supabase db dump > backup_$(date +%Y%m%d).sql
|
||||||
|
|
||||||
|
# Or use pg_dump directly with your Supabase credentials
|
||||||
|
pg_dump "postgresql://postgres:[PASSWORD]@[PROJECT_REF].supabase.co:5432/postgres" > backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Update Your Codebase
|
||||||
|
|
||||||
|
Pull the latest changes or update these files:
|
||||||
|
|
||||||
|
#### Updated Files:
|
||||||
|
- ✏️ `prisma/schema.prisma` - Removed `directUrl` field
|
||||||
|
- ✏️ `src/lib/env.ts` - Removed `DIRECT_URL` variable
|
||||||
|
- ✏️ `docker-compose.yml` - Updated with PostgreSQL setup
|
||||||
|
- ✏️ `Dockerfile` - Enhanced with PostgreSQL support
|
||||||
|
- ✏️ `package.json` - Added Docker scripts and tsx
|
||||||
|
|
||||||
|
#### New Files:
|
||||||
|
- 📄 `docker-compose.dev.yml` - Development setup
|
||||||
|
- 📄 `docker/init-db.sh` - Database initialization
|
||||||
|
- 📄 `docker/README.md` - Docker documentation
|
||||||
|
- 📄 `DOCKER_SETUP.md` - Complete Docker guide
|
||||||
|
- 📄 `env.example` - Environment template
|
||||||
|
- 📄 `.dockerignore` - Docker build optimization
|
||||||
|
|
||||||
|
### 3. Set Up Environment Variables
|
||||||
|
|
||||||
|
1. Remove Supabase-specific variables:
|
||||||
|
```bash
|
||||||
|
# Remove these from .env:
|
||||||
|
# DIRECT_URL=...
|
||||||
|
# SUPABASE_URL=...
|
||||||
|
# SUPABASE_ANON_KEY=...
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update database connection:
|
||||||
|
```bash
|
||||||
|
# For Docker (default):
|
||||||
|
DATABASE_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||||
|
|
||||||
|
# For local development (without Docker):
|
||||||
|
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/qrmaster?schema=public
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Copy from template:
|
||||||
|
```bash
|
||||||
|
cp env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Generate secure secrets:
|
||||||
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
openssl rand -base64 32 # Use for NEXTAUTH_SECRET
|
||||||
|
openssl rand -base64 32 # Use for IP_SALT
|
||||||
|
|
||||||
|
# Windows PowerShell
|
||||||
|
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Start Local PostgreSQL
|
||||||
|
|
||||||
|
#### Option A: Development Mode (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start database only (run app on host)
|
||||||
|
npm run docker:dev
|
||||||
|
|
||||||
|
# Wait for database to be ready
|
||||||
|
docker-compose -f docker-compose.dev.yml logs -f db
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Full Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start all services
|
||||||
|
npm run docker:prod
|
||||||
|
|
||||||
|
# Wait for all services to be ready
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Restore Your Data
|
||||||
|
|
||||||
|
#### Option 1: Using Prisma Migrations (Clean Start)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate Prisma client
|
||||||
|
npm run db:generate
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
npm run db:migrate
|
||||||
|
|
||||||
|
# Seed with demo data
|
||||||
|
npm run db:seed
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option 2: Restore from Backup (Preserve Data)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore your Supabase backup
|
||||||
|
cat backup.sql | docker-compose exec -T db psql -U postgres qrmaster
|
||||||
|
|
||||||
|
# Or if running locally
|
||||||
|
psql -U postgres -d qrmaster < backup.sql
|
||||||
|
|
||||||
|
# Then run migrations to update schema
|
||||||
|
npm run db:deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Verify Migration
|
||||||
|
|
||||||
|
1. **Check Database Connection:**
|
||||||
|
```bash
|
||||||
|
# Connect to database
|
||||||
|
npm run docker:db
|
||||||
|
|
||||||
|
# Or manually
|
||||||
|
docker-compose exec db psql -U postgres -d qrmaster
|
||||||
|
|
||||||
|
# Run test query
|
||||||
|
SELECT COUNT(*) FROM "User";
|
||||||
|
SELECT COUNT(*) FROM "QRCode";
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Access Adminer (Database UI):**
|
||||||
|
- URL: http://localhost:8080
|
||||||
|
- System: PostgreSQL
|
||||||
|
- Server: db
|
||||||
|
- Username: postgres
|
||||||
|
- Password: postgres
|
||||||
|
- Database: qrmaster
|
||||||
|
|
||||||
|
3. **Test Your Application:**
|
||||||
|
```bash
|
||||||
|
# Start the app (if using dev mode)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Access: http://localhost:3050
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Update Your Deployment
|
||||||
|
|
||||||
|
#### For Docker Production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and deploy
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
docker-compose exec web npx prisma migrate deploy
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
docker-compose logs -f web
|
||||||
|
```
|
||||||
|
|
||||||
|
#### For Other Platforms (Vercel, Railway, etc.):
|
||||||
|
|
||||||
|
Update your environment variables in the platform's dashboard:
|
||||||
|
- Remove: `DIRECT_URL`
|
||||||
|
- Update: `DATABASE_URL` to your new PostgreSQL connection string
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Connection Refused
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if database is running
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Check database logs
|
||||||
|
docker-compose logs db
|
||||||
|
|
||||||
|
# Restart database
|
||||||
|
docker-compose restart db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Migration Errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reset migrations (⚠️ deletes data!)
|
||||||
|
npm run db:migrate reset
|
||||||
|
|
||||||
|
# Or manually reset
|
||||||
|
docker-compose down -v
|
||||||
|
docker-compose up -d db
|
||||||
|
npm run db:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Prisma Client Not Generated
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Regenerate Prisma client
|
||||||
|
npm run db:generate
|
||||||
|
|
||||||
|
# Or
|
||||||
|
npx prisma generate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Data Not Migrated
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if backup was restored correctly
|
||||||
|
docker-compose exec db psql -U postgres -d qrmaster -c "
|
||||||
|
SELECT
|
||||||
|
schemaname,
|
||||||
|
tablename,
|
||||||
|
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
|
||||||
|
FROM pg_tables
|
||||||
|
WHERE schemaname = 'public'
|
||||||
|
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Differences: Supabase vs Local PostgreSQL
|
||||||
|
|
||||||
|
| Feature | Supabase | Local PostgreSQL |
|
||||||
|
|---------|----------|------------------|
|
||||||
|
| Hosting | Cloud (managed) | Self-hosted (Docker) |
|
||||||
|
| Connection Pooling | Built-in (Supavisor) | Prisma built-in |
|
||||||
|
| Database UI | Supabase Studio | Adminer (included) |
|
||||||
|
| Backups | Automatic | Manual (or scripted) |
|
||||||
|
| Cost | Free tier + paid | Free (infrastructure cost only) |
|
||||||
|
| Latency | Internet dependent | Local network |
|
||||||
|
| Setup | Account required | Docker only |
|
||||||
|
| Scaling | Automatic | Manual |
|
||||||
|
|
||||||
|
## Benefits of Local PostgreSQL
|
||||||
|
|
||||||
|
✅ **Full Control**: Own your data and infrastructure
|
||||||
|
✅ **No Vendor Lock-in**: Standard PostgreSQL
|
||||||
|
✅ **Lower Latency**: Local network speed
|
||||||
|
✅ **Cost**: No monthly fees
|
||||||
|
✅ **Privacy**: Data stays on your infrastructure
|
||||||
|
✅ **Development**: Easy local testing
|
||||||
|
✅ **Offline**: Works without internet
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. ✅ Verify all data migrated correctly
|
||||||
|
2. ✅ Test all application features
|
||||||
|
3. ✅ Update your CI/CD pipelines
|
||||||
|
4. ✅ Set up automated backups:
|
||||||
|
```bash
|
||||||
|
# Create backup script
|
||||||
|
cat > backup.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
BACKUP_DIR="./backups"
|
||||||
|
mkdir -p $BACKUP_DIR
|
||||||
|
docker-compose exec -T db pg_dump -U postgres qrmaster > "$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).sql"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x backup.sh
|
||||||
|
|
||||||
|
# Run daily backups (cron example)
|
||||||
|
# 0 2 * * * /path/to/backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
5. ✅ Monitor your application
|
||||||
|
6. ✅ Update documentation
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If you need to rollback to Supabase:
|
||||||
|
|
||||||
|
1. Keep your Supabase project active during testing
|
||||||
|
2. Keep your backup files safe
|
||||||
|
3. To rollback, simply change `DATABASE_URL` back to Supabase
|
||||||
|
4. Add back `DIRECT_URL` to `prisma/schema.prisma`:
|
||||||
|
```prisma
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
directUrl = env("DIRECT_URL")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues:
|
||||||
|
1. Check [DOCKER_SETUP.md](DOCKER_SETUP.md) for detailed Docker help
|
||||||
|
2. Check [docker/README.md](docker/README.md) for Docker commands
|
||||||
|
3. Review logs: `docker-compose logs -f`
|
||||||
|
4. Open an issue on GitHub
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🎉 **Congratulations!** You've successfully migrated from Supabase to local PostgreSQL!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
# Feature Plan: Bulk Dynamic QR + Dynamic Barcode Generator
|
|
||||||
|
|
||||||
## Feature 1: Bulk Generator → Dynamic QR freischalten
|
|
||||||
|
|
||||||
### Ziel
|
|
||||||
Nutzer können beim Bulk-Import wählen ob sie statische oder dynamische QR-Codes erstellen. Dynamisch = DB-Einträge mit Slugs + Tracking. Nur für PRO/BUSINESS.
|
|
||||||
|
|
||||||
### Aktueller Stand (Ist)
|
|
||||||
- `bulk-creation/page.tsx` generiert QR-Codes rein client-seitig (kein DB-Eintrag)
|
|
||||||
- `generateStaticQRCodes()` rendert SVGs lokal via `qrcode`-Library
|
|
||||||
- `saveQRCodesToDatabase()` sendet an `POST /api/qrs` mit `isStatic: true` — also immer statisch
|
|
||||||
- Kein Toggle Static/Dynamic vorhanden
|
|
||||||
- Kein Plan-Check für Dynamic im Bulk-Flow
|
|
||||||
|
|
||||||
### Änderungen
|
|
||||||
|
|
||||||
#### A) Frontend: `bulk-creation/page.tsx`
|
|
||||||
|
|
||||||
1. **Toggle "Static / Dynamic" hinzufügen** (nach Plan-Check)
|
|
||||||
- Nur sichtbar/aktivierbar wenn `userPlan === 'PRO' || 'BUSINESS'`
|
|
||||||
- FREE-Nutzer sehen den Toggle gesperrt mit Upgrade-Hinweis
|
|
||||||
- State: `const [isDynamic, setIsDynamic] = useState(false)`
|
|
||||||
|
|
||||||
2. **Interface erweitern**
|
|
||||||
```ts
|
|
||||||
interface GeneratedQR {
|
|
||||||
title: string;
|
|
||||||
content: string;
|
|
||||||
svg: string;
|
|
||||||
slug?: string; // nur bei dynamic, nach API-Antwort gesetzt
|
|
||||||
redirectUrl?: string; // z.B. https://qrmaster.net/r/abc123
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Generierungslogik aufteilen**
|
|
||||||
- Static (wie bisher): client-seitig SVG generieren, kein DB-Eintrag nötig
|
|
||||||
- Dynamic: direkt `POST /api/qrs` pro Eintrag mit `isDynamic: true` → API gibt Slug zurück → QR encodiert `/r/[slug]` statt Original-URL
|
|
||||||
|
|
||||||
4. **Preview-Spalte erweitern**
|
|
||||||
- Bei dynamic: Slug-Link anzeigen + "Ziel änderbar" Badge
|
|
||||||
- Download-ZIP enthält QR-SVGs die `/r/[slug]` encodieren
|
|
||||||
|
|
||||||
5. **Limit-Anzeige**
|
|
||||||
- PRO: max 50 dynamic / Bulk-Run (entspricht QR-Limit)
|
|
||||||
- BUSINESS: max 500
|
|
||||||
|
|
||||||
#### B) Backend: `POST /api/qrs`
|
|
||||||
|
|
||||||
Keine strukturelle Änderung nötig — der bestehende Endpunkt unterstützt bereits `isDynamic: false/true` und gibt `slug` zurück. Nur sicherstellen:
|
|
||||||
|
|
||||||
- Plan-Limit-Check zählt korrekt bei Bulk-Erstellung (momentan prüft `POST /api/qrs` nur ob Gesamtanzahl < Limit — das bleibt so, aber Bulk erstellt X Requests nacheinander → ggf. Rate-Limit beachten)
|
|
||||||
- Evtl. neuen Endpunkt `POST /api/qrs/bulk` der ein Array entgegennimmt und in einer DB-Transaktion schreibt (besser als 500 Einzelrequests)
|
|
||||||
|
|
||||||
#### C) Optionaler neuer Endpunkt: `POST /api/qrs/bulk` (empfohlen)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Body: { qrCodes: Array<{ title, content, contentType, isDynamic }>, plan }
|
|
||||||
// Response: { created: Array<{ id, slug, redirectUrl }>, failed: number }
|
|
||||||
// Vorteile: eine DB-Transaktion, ein CSRF-Check, schneller
|
|
||||||
```
|
|
||||||
|
|
||||||
Plan-Check: `if (isDynamic && plan === 'FREE') return 403`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Reihenfolge der Umsetzung
|
|
||||||
|
|
||||||
1. Toggle + Plan-Check im Frontend
|
|
||||||
2. Dynamic-Generierungslogik (nutzt bestehenden `POST /api/qrs`)
|
|
||||||
3. (Optional) `POST /api/qrs/bulk` für Performance
|
|
||||||
4. ZIP-Download mit Redirect-URLs als Metadaten-CSV
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Feature 2: Dynamischer Barcode Generator
|
|
||||||
|
|
||||||
### Aufteilung: Landingpage (Marketing) + Dashboard (Funktion)
|
|
||||||
|
|
||||||
**Wichtig:** "Dynamic" existiert nur im Dashboard `/create`. Die Landingpage erklärt das Konzept und treibt Nutzer zum Signup/Login — sie hat keinen eigenen Dynamic-Modus.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2a) Landingpage: `/tools/dynamic-barcode-generator`
|
|
||||||
|
|
||||||
**Ziel:** SEO-Traffic auf Keyword "barcode generator" (100k–1M, 0% Competition) konvertieren zu Signups.
|
|
||||||
|
|
||||||
**Was die Landingpage NICHT hat:**
|
|
||||||
- Keinen Dynamic-Toggle
|
|
||||||
- Keine echte Dynamic-Funktionalität
|
|
||||||
|
|
||||||
**Was die Landingpage HAT:**
|
|
||||||
- Bestehenden `BarcodeGeneratorClient` eingebettet (statischer Generator, unverändert)
|
|
||||||
- Erklärung was ein dynamischer Barcode ist + Vorteile
|
|
||||||
- Klarer CTA: "Create Dynamic Barcode → Sign up / Dashboard"
|
|
||||||
|
|
||||||
**Aufbau** (`src/app/(main)/(marketing)/tools/dynamic-barcode-generator/page.tsx`):
|
|
||||||
|
|
||||||
```
|
|
||||||
Hero-Section
|
|
||||||
H1: "Dynamic Barcode Generator — Update Any Barcode Without Reprinting"
|
|
||||||
Subtext: Erklärt Tracking + Redirect-Konzept
|
|
||||||
CTA-Button: "Create Dynamic Barcode" → /login oder /signup
|
|
||||||
|
|
||||||
Tool-Section
|
|
||||||
BarcodeGeneratorClient (statisch, wie bisher, keine Änderungen)
|
|
||||||
Banner darunter: "Want dynamic barcodes? Sign up free →"
|
|
||||||
|
|
||||||
How It Works (3 Schritte)
|
|
||||||
1. Sign up & create barcode in dashboard
|
|
||||||
2. Print it once
|
|
||||||
3. Update the destination anytime — no reprint needed
|
|
||||||
|
|
||||||
Use Cases
|
|
||||||
Retail-Verpackungen, Logistik-Labels, Produktkataloge, Event-Badges
|
|
||||||
|
|
||||||
FAQ-Section (schema.org FAQ markup)
|
|
||||||
- "Was ist ein dynamischer Barcode?"
|
|
||||||
- "Wie unterscheidet sich dynamisch von statisch?"
|
|
||||||
- "Welche Formate werden unterstützt?"
|
|
||||||
|
|
||||||
RelatedTools-Komponente (bereits vorhanden)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Metadata:**
|
|
||||||
```ts
|
|
||||||
title: 'Dynamic Barcode Generator — Trackable & Editable Barcodes'
|
|
||||||
description: 'Create dynamic barcodes that you can update without reprinting. Track scans, change destinations, and manage all barcodes from one dashboard.'
|
|
||||||
canonical: 'https://www.qrmaster.net/tools/dynamic-barcode-generator'
|
|
||||||
keywords: ['dynamic barcode generator', 'barcode generator', 'trackable barcode', 'editable barcode']
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sitemap:** `/tools/dynamic-barcode-generator` hinzufügen.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2b) Dashboard `/create` — BARCODE als ContentType
|
|
||||||
|
|
||||||
**Ziel:** Eingeloggte Nutzer können Barcodes (statisch oder dynamisch) im Dashboard erstellen, speichern und tracken.
|
|
||||||
|
|
||||||
**DB-Änderung (kein migrate!):**
|
|
||||||
```sql
|
|
||||||
-- Direkt gegen PostgreSQL ausführen (npm run docker:db)
|
|
||||||
ALTER TYPE "ContentType" ADD VALUE 'BARCODE';
|
|
||||||
```
|
|
||||||
Danach: `npx prisma generate`
|
|
||||||
|
|
||||||
**Änderungen `create/page.tsx`:**
|
|
||||||
- BARCODE zu `contentTypes` Array hinzufügen
|
|
||||||
- `renderContentFields()` Case: Barcode-Wert + Format-Picker (CODE128, EAN13, UPC, etc.)
|
|
||||||
- Preview: `react-barcode` statt `QRCodeSVG` wenn ContentType === BARCODE
|
|
||||||
- QR-spezifische Optionen ausblenden bei BARCODE (Frames, Logo, Corner Style)
|
|
||||||
- Dynamic Barcode = encodiert `/r/[slug]` → nutzt bestehendes Redirect- + Tracking-System
|
|
||||||
- Static Barcode = encodiert Rohwert direkt
|
|
||||||
|
|
||||||
**Kein neues Backend nötig** — `POST /api/qrs` + `/r/[slug]`-Redirect funktionieren bereits.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Reihenfolge der Umsetzung
|
|
||||||
|
|
||||||
**Phase 1 — Landingpage (kein DB-Change):**
|
|
||||||
1. `page.tsx` unter `/tools/dynamic-barcode-generator` erstellen
|
|
||||||
2. Sitemap-Eintrag
|
|
||||||
|
|
||||||
**Phase 2 — Dashboard BARCODE:**
|
|
||||||
1. SQL ausführen: `ALTER TYPE "ContentType" ADD VALUE 'BARCODE'`
|
|
||||||
2. `npx prisma generate`
|
|
||||||
3. `create/page.tsx` erweitern
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Abhängigkeiten zwischen den Features
|
|
||||||
|
|
||||||
| Feature | Hängt ab von |
|
|
||||||
|---------|-------------|
|
|
||||||
| Bulk Dynamic | Bestehendem `POST /api/qrs` (bereits fertig) |
|
|
||||||
| Bulk Dynamic (optional) | Neuem `POST /api/qrs/bulk` Endpunkt |
|
|
||||||
| Landingpage Dynamic Barcode | Bestehendem BarcodeGeneratorClient (keine Änderungen) |
|
|
||||||
| Dashboard BARCODE | SQL-Enum-Erweiterung + `prisma generate` |
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
# QR Master — Deep Analysis & Growth Potential
|
|
||||||
**Date:** April 1, 2026 | **Project Age:** 3 months (launched Jan 1, 2026)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
QR Master is a young but well-built QR code SaaS entering a **$1–7B market growing at 16%+ CAGR**. After just 3 months, the numbers tell an interesting story: explosive impression growth in Google Search (0 → 4,100/month), strong AI citation momentum (1,849 citations across 11 pages), and 169 unique visitors in the last 90 days with promising engagement metrics (8m 36s avg session, 16% bounce rate). However, organic search clicks remain very low, and there are zero paying customers visible yet. The opportunity is real, but execution over the next 6 months will determine everything.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Your Numbers — What They Tell Me
|
|
||||||
|
|
||||||
### Google Search Console (Jan–Mar 2026)
|
|
||||||
|
|
||||||
| Metric | January | February | March | Growth |
|
|
||||||
|--------|---------|----------|-------|--------|
|
|
||||||
| Impressions | 158 | 44 | 4,115 | **+2,504%** |
|
|
||||||
| Clicks | 0 | 0 | 23 | From zero |
|
|
||||||
| Avg Position | ~1.3 | ~3.3 | ~68.4 | Widened (more keywords) |
|
|
||||||
|
|
||||||
**What this means:** Google discovered your site in late January, but the real indexing explosion happened in March. The jump from 44 impressions in February to 4,115 in March is dramatic — this is the "Google sandbox" lifting. The average position being ~68 means most of your keywords are on pages 6–7 of Google. That's normal for a 3-month-old domain, but it also means you're not getting meaningful organic traffic yet from competitive terms.
|
|
||||||
|
|
||||||
**Top performing pages by impressions:**
|
|
||||||
|
|
||||||
| Page | Impressions | Clicks | Avg Position |
|
|
||||||
|------|-------------|--------|-------------|
|
|
||||||
| Homepage | 356 | 15 | 4.0 |
|
|
||||||
| Barcode Generator | 1,160 | 4 | 71.4 |
|
|
||||||
| Dynamic QR Code Generator | 536 | 0 | 81.1 |
|
|
||||||
| Restaurant Menu QR Blog | 425 | 0 | 80.9 |
|
|
||||||
| Geolocation QR Code | 281 | 0 | 73.2 |
|
|
||||||
| URL QR Code | 205 | 0 | 83.6 |
|
|
||||||
| Twitter QR Code | 169 | 0 | 71.3 |
|
|
||||||
|
|
||||||
**Key insight:** Your barcode generator page has 1,160 impressions but only 4 clicks because it's ranking at position 71. If you can move that to page 1, even position 8–10, you'd capture 20–50 clicks/day from that keyword cluster alone. Same story for "dynamic QR code generator" (536 impressions, position 81).
|
|
||||||
|
|
||||||
### AI Citations (Google AI Overviews)
|
|
||||||
|
|
||||||
| Week | Citations | Avg/Day | Trend |
|
|
||||||
|------|-----------|---------|-------|
|
|
||||||
| Week 9 (late Feb) | 123 | 30.8 | Baseline |
|
|
||||||
| Week 10 | 328 | 46.9 | +167% |
|
|
||||||
| Week 11 | 343 | 49.0 | +5% |
|
|
||||||
| Week 12 | 514 | 73.4 | +50% |
|
|
||||||
| Week 13 | 541 | 77.3 | +5% |
|
|
||||||
|
|
||||||
**Total: 1,849 citations across up to 11 unique pages.**
|
|
||||||
|
|
||||||
This is genuinely impressive for a 3-month-old site. AI citations at 77/day means Google's AI is actively referencing your content when answering QR-related queries. This is a leading indicator — AI citations often precede organic ranking improvements. You're building topical authority faster than traditional SEO alone would deliver.
|
|
||||||
|
|
||||||
### PostHog Analytics (Last 90 Days)
|
|
||||||
|
|
||||||
| Metric | Value | Assessment |
|
|
||||||
|--------|-------|-----------|
|
|
||||||
| Unique Visitors | 169 | Low, but expected for 3 months |
|
|
||||||
| Page Views | 2,120 | ~12.5 pages/visitor — very high engagement |
|
|
||||||
| Sessions | 417 | 2.5 sessions/visitor average |
|
|
||||||
| Session Duration | 8m 36s | Excellent — users are exploring deeply |
|
|
||||||
| Bounce Rate | 16% | Outstanding (industry avg is 40-60%) |
|
|
||||||
|
|
||||||
**Traffic sources:**
|
|
||||||
|
|
||||||
| Channel | Visitors | Views |
|
|
||||||
|---------|----------|-------|
|
|
||||||
| Referral | 100 | 803 |
|
|
||||||
| Organic Social | 64 | 89 |
|
|
||||||
| Direct | 17 | 1,123 |
|
|
||||||
| Organic Search | 6 | 70 |
|
|
||||||
|
|
||||||
**Critical insight:** Your 16% bounce rate and 8.5-minute session duration are exceptional. People who find your site actually use it. The problem isn't product quality — it's distribution. Only 6 visitors from organic search in 90 days confirms you're still in the early SEO growth phase.
|
|
||||||
|
|
||||||
**Retention concern:** The cohort data shows near-zero retention after week 1 for most cohorts. This is the biggest red flag — users come, try the tool, but don't come back. This is typical for free QR generator users, but it means conversion to paid will depend heavily on capturing value during that first session.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Competitive Landscape
|
|
||||||
|
|
||||||
### The Market You're Entering
|
|
||||||
|
|
||||||
The QR code generator market is valued at **$1.1–6.8B in 2025** (estimates vary by definition) and growing at **16–17% CAGR**. Dynamic QR codes specifically are the revenue driver, accounting for 55-64% of market value.
|
|
||||||
|
|
||||||
### Key Competitors
|
|
||||||
|
|
||||||
| Company | Est. Revenue (2025) | Pricing | Key Strength |
|
|
||||||
|---------|-------------------|---------|-------------|
|
|
||||||
| **Flowcode** | $15.7M | Free / $5-25/mo | VC-backed, design-focused, landing pages |
|
|
||||||
| **Bitly** | $200M+ (link shortening + QR) | $8–199/mo | Brand recognition, link ecosystem |
|
|
||||||
| **Uniqode** (fka Beaconstac) | ~$10M est. | $5–99/mo | Enterprise (SOC2, HIPAA, ISO) |
|
|
||||||
| **QR Tiger** | ~$5M est. | $7–37/mo | SEO-aggressive, content marketing |
|
|
||||||
| **Trycon/Scanova** | $7.2M | $5–49/mo | 2,000+ businesses, enterprise |
|
|
||||||
| **Mobilo** | $5.4M | $4.99–14.99/mo | Digital business cards focus |
|
|
||||||
| **Unitag** | $2.3M | Free / $9.90+/mo | European market, design tools |
|
|
||||||
| **QR Code Creator** | $1.5M | $4.99–14.99/mo | Long-established (since 2009) |
|
|
||||||
| **QRCode Monkey** | Unknown | Free (ad-supported) | SEO dominant, free tools |
|
|
||||||
|
|
||||||
### What This Means for QR Master
|
|
||||||
|
|
||||||
The market has room. Even QR Code Creator, which has been around since 2009, only does $1.5M. The winner-take-all dynamics of pure SaaS don't apply as strongly here because QR codes are a utility — businesses often try 2-3 tools before settling. Key entry angles that still work: niche verticals (restaurants, real estate), better free tier to build SEO traffic, and superior analytics.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. SEO Assessment
|
|
||||||
|
|
||||||
### Current State: Early but Promising
|
|
||||||
|
|
||||||
Your site is showing 700+ unique keywords with impressions — that's a lot of keyword surface area for a 3-month-old site. But almost all rankings are on pages 5-10 of Google (positions 50-100).
|
|
||||||
|
|
||||||
### Biggest Keyword Opportunities (High Impression, Improvable Position)
|
|
||||||
|
|
||||||
| Keyword | Monthly Impressions | Current Position | Difficulty |
|
|
||||||
|---------|-------------------|-----------------|-----------|
|
|
||||||
| barcode generator | 148+ | 74.8 | Very High |
|
|
||||||
| dynamic qr code | 115+ | 80.3 | High |
|
|
||||||
| dynamic qr code generator | 81+ | 85.8 | High |
|
|
||||||
| restaurant qr code | 51+ | 75.0 | Medium |
|
|
||||||
| qr code restaurant menu | 48+ | 82.8 | Medium |
|
|
||||||
| twitter qr code | 46+ | 76.7 | Low-Medium |
|
|
||||||
| instagram qr code generator | 35+ | 54.9 | Medium |
|
|
||||||
| bulk qr code generator | 28+ | 94.4 | Medium |
|
|
||||||
| vcard qr code generator | 9+ | 74.9 | Low-Medium |
|
|
||||||
| teams qr code | 3+ | 34.7 | Low |
|
|
||||||
|
|
||||||
### Quick Wins (Already Close to Page 1)
|
|
||||||
|
|
||||||
- **"teams qr code"** — Position 34.7 with dedicated tool page already built. You already have traffic to this page (20 visitors in PostHog). Push this to page 1.
|
|
||||||
- **"zoom qr code"** — Position 40.3. Similar opportunity.
|
|
||||||
- **"qrmaster"** branded terms — Position 4.3 (already strong, but should be #1)
|
|
||||||
- **"qr master"** — Position 8.1. Should be #1 for your own brand.
|
|
||||||
|
|
||||||
### What's Holding You Back
|
|
||||||
|
|
||||||
1. **Domain age (3 months):** Google inherently trusts older domains more. This improves automatically with time.
|
|
||||||
2. **Backlink profile:** Likely thin. Your referral traffic (100 visitors) suggests some links, but you need more authoritative ones.
|
|
||||||
3. **Competition on head terms:** "QR code generator" is dominated by Bitly, QRCode Monkey, QR Tiger with DA 50+ domains. You won't rank for this soon.
|
|
||||||
4. **Content depth vs. competitors:** QR Tiger and Uniqode have 100+ blog posts each. You're early in content production.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Revenue Potential — Realistic Projections
|
|
||||||
|
|
||||||
### Assumptions
|
|
||||||
|
|
||||||
Based on your current pricing model (FREE / PRO / BUSINESS tiers) and market benchmarks:
|
|
||||||
|
|
||||||
- **Free-to-paid conversion rate for QR tools:** 2-4% (industry benchmark)
|
|
||||||
- **Average revenue per user (ARPU):** $10-20/month for SMB QR tools
|
|
||||||
- **Churn rate:** 5-8% monthly for SMB SaaS
|
|
||||||
|
|
||||||
### Scenario Modeling
|
|
||||||
|
|
||||||
#### Conservative (SEO-only growth, no paid acquisition)
|
|
||||||
|
|
||||||
| Month | Monthly Visitors | Signups (15% CVR) | Paying Users (cum.) | MRR |
|
|
||||||
|-------|-----------------|-------------------|--------------------|----|
|
|
||||||
| Month 3 (now) | 170 | 39 | 0 | $0 |
|
|
||||||
| Month 6 | 1,000 | 150 | 8 | $120 |
|
|
||||||
| Month 12 | 5,000 | 750 | 60 | $900 |
|
|
||||||
| Month 18 | 15,000 | 2,250 | 200 | $3,000 |
|
|
||||||
| Month 24 | 40,000 | 6,000 | 500 | $7,500 |
|
|
||||||
|
|
||||||
**Year 1 ARR: ~$10,800 | Year 2 ARR: ~$90,000**
|
|
||||||
|
|
||||||
#### Moderate (SEO + content marketing + some link building)
|
|
||||||
|
|
||||||
| Month | Monthly Visitors | Signups | Paying Users (cum.) | MRR |
|
|
||||||
|-------|-----------------|---------|--------------------|----|
|
|
||||||
| Month 6 | 3,000 | 450 | 25 | $375 |
|
|
||||||
| Month 12 | 15,000 | 2,250 | 180 | $2,700 |
|
|
||||||
| Month 18 | 40,000 | 6,000 | 500 | $7,500 |
|
|
||||||
| Month 24 | 80,000 | 12,000 | 1,200 | $18,000 |
|
|
||||||
|
|
||||||
**Year 1 ARR: ~$32,400 | Year 2 ARR: ~$216,000**
|
|
||||||
|
|
||||||
#### Aggressive (SEO + paid + partnerships + viral loops)
|
|
||||||
|
|
||||||
| Month | Monthly Visitors | Signups | Paying Users (cum.) | MRR |
|
|
||||||
|-------|-----------------|---------|--------------------|----|
|
|
||||||
| Month 12 | 50,000 | 7,500 | 600 | $9,000 |
|
|
||||||
| Month 24 | 200,000 | 30,000 | 3,000 | $45,000 |
|
|
||||||
|
|
||||||
**Year 2 ARR: ~$540,000**
|
|
||||||
|
|
||||||
### Revenue Benchmarks from Comparable Companies
|
|
||||||
|
|
||||||
- QR Code Creator (17 years old): **$1.5M/year**
|
|
||||||
- Unitag (10+ years old): **$2.3M/year**
|
|
||||||
- Flowcode (7 years, VC-backed): **$15.7M/year**
|
|
||||||
- Indie SaaS benchmarks: $5K–25K MRR is very achievable within 18-24 months with focused execution
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Strengths & Risks
|
|
||||||
|
|
||||||
### What's Working
|
|
||||||
|
|
||||||
1. **Product quality is high.** 8.5-minute sessions, 16% bounce rate, 12.5 pages/visitor — these are remarkable engagement metrics. The product holds attention.
|
|
||||||
2. **AI citations growing fast.** 77 citations/day after 3 months is a strong signal Google's AI trusts your content.
|
|
||||||
3. **Broad keyword footprint.** 700+ keywords showing impressions means Google is indexing and considering your pages for a wide range of queries.
|
|
||||||
4. **Niche tool pages (Teams, Zoom, WiFi, Instagram).** These long-tail pages are your fastest path to page 1 rankings.
|
|
||||||
5. **Technical foundation is solid.** Next.js 14, Stripe, analytics, dynamic QR codes — the infrastructure is already at a paid-product level.
|
|
||||||
|
|
||||||
### Risks & Concerns
|
|
||||||
|
|
||||||
1. **Near-zero retention.** Cohort data shows almost no users returning after week 1. Without solving retention, paid conversion will be extremely difficult.
|
|
||||||
2. **No revenue yet.** 3 months with 0 MRR is normal, but the clock is ticking. Need to see first paying user by month 4-5.
|
|
||||||
3. **Competitive head terms are years away.** You won't rank for "QR code generator" anytime soon. The long-tail strategy is correct but requires patience.
|
|
||||||
4. **Single-channel dependency.** Currently SEO-reliant. If Google changes its algorithm or sandbox behavior, growth stalls.
|
|
||||||
5. **service_account.json in root.** If this is a live credential, it's a security risk. Remove from version control immediately.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Recommendations — Next 90 Days
|
|
||||||
|
|
||||||
### Immediate (This Month)
|
|
||||||
|
|
||||||
1. **Fix retention first.** Add email capture on first QR creation, build a "My QR Codes" dashboard value prop, and implement a follow-up email sequence showing scan analytics. Users need a reason to come back.
|
|
||||||
2. **Push "quick win" keywords to page 1.** Teams QR code (pos 34), Zoom QR code (pos 40), and your branded terms. These need targeted backlinks and content optimization.
|
|
||||||
3. **Add pricing friction at the right moment.** If you're not gating anything, users have no reason to pay. Consider limiting dynamic QR codes to 1-3 on free tier, then upsell.
|
|
||||||
|
|
||||||
### Short-term (Months 4-6)
|
|
||||||
|
|
||||||
4. **Build 10-15 high-quality backlinks.** Guest posts on marketing blogs, HARO/Connectively responses, tool directories. Domain authority is your bottleneck.
|
|
||||||
5. **Double down on restaurant/menu niche.** You have 425 impressions on the restaurant menu QR blog. This is a vertical where local businesses actually pay for QR tools. Build a dedicated landing page.
|
|
||||||
6. **Launch on Product Hunt, Indie Hackers, and relevant subreddits.** Free exposure that also builds backlinks.
|
|
||||||
|
|
||||||
### Medium-term (Months 6-12)
|
|
||||||
|
|
||||||
7. **Expand content to 50+ blog posts.** Target every long-tail QR keyword cluster. QR Tiger's content machine is the model to emulate.
|
|
||||||
8. **Add a freemium viral loop.** "Powered by QR Master" branding on free QR codes (with option to remove on paid plan).
|
|
||||||
9. **Consider German market positioning.** Your GSC data shows German-language queries (dynamische QR codes, etc.) and you appear to be German-speaking. The German QR code market is underserved compared to English.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Bottom Line
|
|
||||||
|
|
||||||
**Is there real potential here?** Yes, absolutely. The QR code market is large, growing, and the barrier to entry is manageable with good technical execution — which you clearly have. Companies doing $1.5M–$15M in this space prove the model works.
|
|
||||||
|
|
||||||
**How much can you realistically make?** With sustained effort, **$3K–$10K MRR by month 18** is realistic under a moderate growth scenario. That's $36K–$120K ARR — meaningful side-project revenue. The top end of $200K+ ARR is achievable within 24 months if you invest in content, backlinks, and conversion optimization.
|
|
||||||
|
|
||||||
**What's the biggest risk?** Not retention, not competition — it's giving up too early. SEO-driven SaaS is a compounding game. Months 1-6 feel slow because organic traffic takes time to build. The hockey stick starts around months 9-12 when domain authority compounds with content volume. QR Code Creator took 17 years to hit $1.5M, but the market was 10x smaller then.
|
|
||||||
|
|
||||||
**Your 3-month scorecard: B+.** Excellent product, strong engagement, growing search visibility. The missing pieces are retention mechanics, first revenue, and backlink authority. All fixable.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Analysis based on Google Search Console data (Jan–Mar 2026), PostHog analytics (90 days), Google AI Performance data, and market research as of April 2026.*
|
|
||||||
|
|
||||||
*Sources: [Mordor Intelligence QR Market Report](https://www.mordorintelligence.com/industry-reports/qr-codes-market), [GetLatka QR SaaS Companies](https://getlatka.com/companies/industries/i-qr-code-generator-software), [Bitly QR Code Statistics](https://bitly.com/blog/qr-code-statistics/), [360iResearch QR Market](https://www.360iresearch.com/library/intelligence/qr-code-generator), [QR Code Chimp Statistics](https://www.qrcodechimp.com/qr-code-statistics/)*
|
|
||||||
957
README.md
@@ -1,482 +1,475 @@
|
|||||||
# QR Master - Create Custom QR Codes in Seconds
|
# QR Master - Create Custom QR Codes in Seconds
|
||||||
|
|
||||||
A production-ready SaaS application for creating and managing QR codes with advanced tracking, analytics, and Stripe payment integration.
|
A production-ready SaaS application for creating and managing QR codes with advanced tracking, analytics, and Stripe payment integration.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 🎨 **Custom QR Codes** - Create static and dynamic QR codes with full customization
|
- 🎨 **Custom QR Codes** - Create static and dynamic QR codes with full customization
|
||||||
- 📊 **Advanced Analytics** - Track scans, locations, devices, and user behavior
|
- 📊 **Advanced Analytics** - Track scans, locations, devices, and user behavior
|
||||||
- 🔄 **Dynamic Content** - Edit QR code destinations anytime without reprinting
|
- 🔄 **Dynamic Content** - Edit QR code destinations anytime without reprinting
|
||||||
- 📦 **Bulk Operations** - Import CSV/Excel files to create up to 1,000 QR codes at once
|
- 📦 **Bulk Operations** - Import CSV/Excel files to create up to 1,000 QR codes at once
|
||||||
- 💳 **Stripe Integration** - FREE, PRO, and BUSINESS subscription plans with secure billing
|
- 💳 **Stripe Integration** - FREE, PRO, and BUSINESS subscription plans with secure billing
|
||||||
- 🎨 **Custom Branding** - Logo upload, custom colors (PRO+ plans)
|
- 🎨 **Custom Branding** - Logo upload, custom colors (PRO+ plans)
|
||||||
- 🌍 **SEO Optimized** - Schema.org structured data, meta tags, breadcrumbs
|
- 🌍 **SEO Optimized** - Schema.org structured data, meta tags, breadcrumbs
|
||||||
- 🔒 **Privacy-First** - GDPR-compliant, hashed IPs, DNT headers respected
|
- 🔒 **Privacy-First** - GDPR-compliant, hashed IPs, DNT headers respected
|
||||||
- 📱 **Responsive Design** - Works perfectly on all devices
|
- 📱 **Responsive Design** - Works perfectly on all devices
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **Frontend**: Next.js 14 (App Router), TypeScript, Tailwind CSS
|
- **Frontend**: Next.js 14 (App Router), TypeScript, Tailwind CSS
|
||||||
- **Backend**: Next.js API Routes, Prisma ORM
|
- **Backend**: Next.js API Routes, Prisma ORM
|
||||||
- **Database**: PostgreSQL (with Prisma migrations)
|
- **Database**: PostgreSQL (with Prisma migrations)
|
||||||
- **Cache**: Redis (optional)
|
- **Cache**: Redis (optional)
|
||||||
- **Auth**: NextAuth.js (Credentials + Google OAuth)
|
- **Auth**: NextAuth.js (Credentials + Google OAuth)
|
||||||
- **Payments**: Stripe (Subscriptions & Webhooks)
|
- **Payments**: Stripe (Subscriptions & Webhooks)
|
||||||
- **QR Generation**: qrcode library
|
- **QR Generation**: qrcode library
|
||||||
- **Bulk Processing**: Papa Parse (CSV), XLSX, JSZip
|
- **Bulk Processing**: Papa Parse (CSV), XLSX, JSZip
|
||||||
- **Analytics**: PostHog (optional)
|
- **Analytics**: PostHog (optional)
|
||||||
- **SEO**: next-sitemap, Schema.org structured data
|
- **SEO**: next-sitemap, Schema.org structured data
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js 18+
|
- Node.js 18+
|
||||||
- Docker and Docker Compose V2
|
- Docker and Docker Compose V2
|
||||||
- Git
|
- Git
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
#### Option 1: Development Mode (Recommended)
|
#### Option 1: Development Mode (Recommended)
|
||||||
|
|
||||||
Run database in Docker, app on host machine:
|
Run database in Docker, app on host machine:
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/yourusername/qr-master.git
|
git clone https://github.com/yourusername/qr-master.git
|
||||||
cd qr-master
|
cd qr-master
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install dependencies:
|
2. Install dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Copy and configure environment:
|
3. Copy and configure environment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp env.example .env
|
cp env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit `.env` and set:
|
Edit `.env` and set:
|
||||||
|
|
||||||
- `NEXTAUTH_SECRET` (generate: `openssl rand -base64 32`)
|
- `NEXTAUTH_SECRET` (generate: `openssl rand -base64 32`)
|
||||||
- `IP_SALT` (generate: `openssl rand -base64 32`)
|
- `IP_SALT` (generate: `openssl rand -base64 32`)
|
||||||
- (Optional) Google OAuth credentials
|
- (Optional) Google OAuth credentials
|
||||||
|
|
||||||
4. Start database services:
|
4. Start database services:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run docker:dev
|
npm run docker:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Run database migrations and seed:
|
5. Run database migrations and seed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx prisma migrate dev
|
npx prisma migrate dev
|
||||||
npm run db:seed
|
npm run db:seed
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: If you get migration errors, you can reset the database:
|
> **Note**: If you get migration errors, you can reset the database:
|
||||||
>
|
>
|
||||||
> ```bash
|
> ```bash
|
||||||
> npx prisma migrate reset
|
> npx prisma migrate reset
|
||||||
> ```
|
> ```
|
||||||
>
|
>
|
||||||
> This will drop the database, recreate it, run all migrations, and seed data.
|
> This will drop the database, recreate it, run all migrations, and seed data.
|
||||||
|
|
||||||
6. Start development server:
|
6. Start development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
7. Access the application:
|
7. Access the application:
|
||||||
- **App**: http://localhost:3050
|
- **App**: http://localhost:3050
|
||||||
- **Database UI**: http://localhost:8080 (Adminer - username: `root`, password: `root`)
|
- **Database UI**: http://localhost:8080 (Adminer - username: `root`, password: `root`)
|
||||||
- **Database**: localhost:5435 (username: `postgres`, password: `postgres`)
|
- **Database**: localhost:5435 (username: `postgres`, password: `postgres`)
|
||||||
- **Redis**: localhost:6379
|
- **Redis**: localhost:6379
|
||||||
|
|
||||||
#### Option 2: Full Docker (Production)
|
#### Option 2: Full Docker (Production)
|
||||||
|
|
||||||
Run everything in Docker:
|
Run everything in Docker:
|
||||||
|
|
||||||
1. Clone and setup:
|
1. Clone and setup:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/yourusername/qr-master.git
|
git clone https://github.com/yourusername/qr-master.git
|
||||||
cd qr-master
|
cd qr-master
|
||||||
cp env.example .env
|
cp env.example .env
|
||||||
# Edit .env with your configuration
|
# Edit .env with your configuration
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Build and start:
|
2. Build and start:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run docker:prod
|
npm run docker:prod
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run migrations:
|
3. Run migrations:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose exec web npx prisma migrate deploy
|
docker-compose exec web npx prisma migrate deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Access at http://localhost:3050
|
4. Access at http://localhost:3050
|
||||||
|
|
||||||
📚 **For detailed Docker setup, see [DOCKER_SETUP.md](DOCKER_SETUP.md)**
|
📚 **For detailed Docker setup, see [DOCKER_SETUP.md](DOCKER_SETUP.md)**
|
||||||
|
|
||||||
## Demo Account
|
## Demo Account
|
||||||
|
|
||||||
After running `npm run db:seed`, use these credentials to test the application:
|
After running `npm run db:seed`, use these credentials to test the application:
|
||||||
|
|
||||||
- **Email**: demo@qrmaster.com
|
- **Email**: demo@qrmaster.com
|
||||||
- **Password**: demo123
|
- **Password**: demo123
|
||||||
- **Plan**: FREE (3 QR codes limit)
|
- **Plan**: FREE (3 QR codes limit)
|
||||||
|
|
||||||
The seed script also creates sample QR codes for testing.
|
The seed script also creates sample QR codes for testing.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
### Available Scripts
|
### Available Scripts
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Development
|
# Development
|
||||||
npm run dev # Start Next.js dev server (port 3050)
|
npm run dev # Start Next.js dev server (port 3050)
|
||||||
npm run build # Build for production
|
npm run build # Build for production
|
||||||
npm run start # Start production server
|
npm run start # Start production server
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
npm run db:generate # Generate Prisma Client
|
npm run db:generate # Generate Prisma Client
|
||||||
npm run db:migrate # Run migrations (dev mode)
|
npm run db:migrate # Run migrations (dev mode)
|
||||||
npm run db:deploy # Deploy migrations (production)
|
npm run db:deploy # Deploy migrations (production)
|
||||||
npm run db:seed # Seed database with demo data
|
npm run db:seed # Seed database with demo data
|
||||||
npm run db:studio # Open Prisma Studio UI
|
npm run db:studio # Open Prisma Studio UI
|
||||||
npx prisma migrate reset # Reset database (drop, recreate, migrate, seed)
|
npx prisma migrate reset # Reset database (drop, recreate, migrate, seed)
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
npm run docker:dev # Start DB & Redis only
|
npm run docker:dev # Start DB & Redis only
|
||||||
npm run docker:dev:stop # Stop dev services
|
npm run docker:dev:stop # Stop dev services
|
||||||
npm run docker:dev:clean # Stop and clean containers
|
npm run docker:dev:clean # Stop and clean containers
|
||||||
npm run docker:prod # Start full stack (production)
|
npm run docker:prod # Start full stack (production)
|
||||||
npm run docker:stop # Stop all services
|
npm run docker:stop # Stop all services
|
||||||
npm run docker:logs # View container logs
|
npm run docker:logs # View container logs
|
||||||
npm run docker:db # PostgreSQL CLI
|
npm run docker:db # PostgreSQL CLI
|
||||||
npm run docker:redis # Redis CLI
|
npm run docker:redis # Redis CLI
|
||||||
npm run docker:backup # Backup database to SQL file
|
npm run docker:backup # Backup database to SQL file
|
||||||
```
|
```
|
||||||
|
|
||||||
### Local Development (without Docker)
|
### Local Development (without Docker)
|
||||||
|
|
||||||
1. Install dependencies:
|
1. Install dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Set up PostgreSQL and Redis locally
|
2. Set up PostgreSQL and Redis locally
|
||||||
|
|
||||||
3. Configure `.env` with local database URL:
|
3. Configure `.env` with local database URL:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public
|
DATABASE_URL=postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Run migrations and seed:
|
4. Run migrations and seed:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx prisma migrate dev
|
npx prisma migrate dev
|
||||||
npm run db:seed
|
npm run db:seed
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Start dev server:
|
5. Start dev server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Resetting the Database
|
### Resetting the Database
|
||||||
|
|
||||||
If you need to reset your database (drop all tables, recreate, and reseed):
|
If you need to reset your database (drop all tables, recreate, and reseed):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Full reset (drops database, reruns migrations, seeds data)
|
# Full reset (drops database, reruns migrations, seeds data)
|
||||||
npx prisma migrate reset
|
npx prisma migrate reset
|
||||||
|
|
||||||
# Or manually:
|
# Or manually:
|
||||||
npx prisma migrate reset --skip-seed # Reset without seeding
|
npx prisma migrate reset --skip-seed # Reset without seeding
|
||||||
npm run db:seed # Then seed manually
|
npm run db:seed # Then seed manually
|
||||||
```
|
```
|
||||||
|
|
||||||
This is useful when:
|
This is useful when:
|
||||||
|
|
||||||
- Schema has changed significantly
|
- Schema has changed significantly
|
||||||
- You have migration conflicts
|
- You have migration conflicts
|
||||||
- You want to start fresh with clean data
|
- You want to start fresh with clean data
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
qr-master/
|
qr-master/
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── app/ # Next.js app router pages
|
│ ├── app/ # Next.js app router pages
|
||||||
│ ├── components/ # React components
|
│ ├── components/ # React components
|
||||||
│ ├── lib/ # Utility functions and configurations
|
│ ├── lib/ # Utility functions and configurations
|
||||||
│ ├── hooks/ # Custom React hooks
|
│ ├── hooks/ # Custom React hooks
|
||||||
│ ├── styles/ # Global styles
|
│ ├── styles/ # Global styles
|
||||||
│ └── i18n/ # Translation files
|
│ └── i18n/ # Translation files
|
||||||
├── prisma/ # Database schema and migrations
|
├── prisma/ # Database schema and migrations
|
||||||
├── docker/ # Docker initialization scripts
|
├── docker/ # Docker initialization scripts
|
||||||
│ ├── init-db.sh # PostgreSQL initialization
|
│ ├── init-db.sh # PostgreSQL initialization
|
||||||
│ └── README.md # Docker documentation
|
│ └── README.md # Docker documentation
|
||||||
├── public/ # Static assets
|
├── public/ # Static assets
|
||||||
├── docker-compose.yml # Production Docker setup
|
├── docker-compose.yml # Production Docker setup
|
||||||
├── docker-compose.dev.yml # Development Docker setup
|
├── docker-compose.dev.yml # Development Docker setup
|
||||||
├── Dockerfile # Container definition
|
├── Dockerfile # Container definition
|
||||||
├── DOCKER_SETUP.md # Complete Docker guide
|
├── DOCKER_SETUP.md # Complete Docker guide
|
||||||
└── env.example # Environment template
|
└── env.example # Environment template
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
- `POST /api/auth/signin` - Sign in with credentials
|
- `POST /api/auth/signin` - Sign in with credentials
|
||||||
- `POST /api/auth/signout` - Sign out
|
- `POST /api/auth/signout` - Sign out
|
||||||
- `GET /api/auth/session` - Get current session
|
- `GET /api/auth/session` - Get current session
|
||||||
|
|
||||||
### QR Codes
|
### QR Codes
|
||||||
|
|
||||||
- `GET /api/qrs` - List all QR codes
|
- `GET /api/qrs` - List all QR codes
|
||||||
- `POST /api/qrs` - Create a new QR code (dynamic or static)
|
- `POST /api/qrs` - Create a new QR code (dynamic or static)
|
||||||
- `POST /api/qrs/static` - Create a static QR code
|
- `POST /api/qrs/static` - Create a static QR code
|
||||||
- `GET /api/qrs/[id]` - Get QR code details
|
- `GET /api/qrs/[id]` - Get QR code details
|
||||||
- `PATCH /api/qrs/[id]` - Update QR code
|
- `PATCH /api/qrs/[id]` - Update QR code
|
||||||
- `DELETE /api/qrs/[id]` - Delete QR code
|
- `DELETE /api/qrs/[id]` - Delete QR code
|
||||||
- `DELETE /api/qrs/delete-all` - Delete all user's QR codes
|
- `DELETE /api/qrs/delete-all` - Delete all user's QR codes
|
||||||
|
|
||||||
### Analytics
|
### Analytics
|
||||||
|
|
||||||
- `GET /api/analytics/summary` - Get analytics summary for a QR code
|
- `GET /api/analytics/summary` - Get analytics summary for a QR code
|
||||||
|
|
||||||
### User & Settings
|
### User & Settings
|
||||||
|
|
||||||
- `GET /api/user/plan` - Get current user plan
|
- `GET /api/user/plan` - Get current user plan
|
||||||
- `GET /api/user/stats` - Get user statistics
|
- `GET /api/user/stats` - Get user statistics
|
||||||
- `POST /api/user/password` - Update password
|
- `POST /api/user/password` - Update password
|
||||||
- `POST /api/user/profile` - Update profile
|
- `POST /api/user/profile` - Update profile
|
||||||
- `DELETE /api/user/delete` - Delete account
|
- `DELETE /api/user/delete` - Delete account
|
||||||
|
|
||||||
### Stripe Payments
|
### Stripe Payments
|
||||||
|
|
||||||
- `POST /api/stripe/checkout` - Create checkout session
|
- `POST /api/stripe/checkout` - Create checkout session
|
||||||
- `POST /api/stripe/portal` - Create customer portal session
|
- `POST /api/stripe/portal` - Create customer portal session
|
||||||
- `POST /api/stripe/webhook` - Handle Stripe webhooks
|
- `POST /api/stripe/webhook` - Handle Stripe webhooks
|
||||||
- `POST /api/stripe/cancel-subscription` - Cancel subscription
|
- `POST /api/stripe/cancel-subscription` - Cancel subscription
|
||||||
|
|
||||||
### Public Redirect
|
### Public Redirect
|
||||||
|
|
||||||
- `GET /r/[slug]` - Redirect and track QR code scan
|
- `GET /r/[slug]` - Redirect and track QR code scan
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
| Variable | Description | Required | Default |
|
| Variable | Description | Required | Default |
|
||||||
| ------------------------------------ | ----------------------------- | -------- | ---------------------------------------------------------------------- |
|
| ------------------------------------ | ----------------------------- | -------- | ---------------------------------------------------------------------- |
|
||||||
| `DATABASE_URL` | PostgreSQL connection string | Yes | - |
|
| `DATABASE_URL` | PostgreSQL connection string | Yes | `postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public` |
|
||||||
| `NEXTAUTH_URL` | Application URL | Yes | `http://localhost:3050` |
|
| `NEXTAUTH_URL` | Application URL | Yes | `http://localhost:3050` |
|
||||||
| `NEXTAUTH_SECRET` | Secret for JWT encryption | Yes | - (Generate with `openssl rand -base64 32`) |
|
| `NEXTAUTH_SECRET` | Secret for JWT encryption | Yes | Generate with `openssl rand -base64 32` |
|
||||||
| `IP_SALT` | Salt for IP hashing (privacy) | Yes | Generate with `openssl rand -base64 32` |
|
| `IP_SALT` | Salt for IP hashing (privacy) | Yes | Generate with `openssl rand -base64 32` |
|
||||||
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | No | - |
|
| `GOOGLE_CLIENT_ID` | Google OAuth client ID | No | - |
|
||||||
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | No | - |
|
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret | No | - |
|
||||||
| `STRIPE_SECRET_KEY` | Stripe secret key | No | - |
|
| `STRIPE_SECRET_KEY` | Stripe secret key | No | - |
|
||||||
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | No | - |
|
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | No | - |
|
||||||
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe public key | No | - |
|
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe public key | No | - |
|
||||||
| `NEXT_PUBLIC_INDEXABLE` | Allow search engine indexing | No | `false` (set to `true` in production) |
|
| `NEXT_PUBLIC_INDEXABLE` | Allow search engine indexing | No | `false` (set to `true` in production) |
|
||||||
| `REDIS_URL` | Redis connection string | No | `redis://redis:6379` |
|
| `REDIS_URL` | Redis connection string | No | `redis://redis:6379` |
|
||||||
| `NEXT_PUBLIC_POSTHOG_KEY` | PostHog analytics key | No | - |
|
| `NEXT_PUBLIC_POSTHOG_KEY` | PostHog analytics key | No | - |
|
||||||
| `NEXT_PUBLIC_POSTHOG_HOST` | PostHog host URL | No | - |
|
| `NEXT_PUBLIC_POSTHOG_HOST` | PostHog host URL | No | - |
|
||||||
|
|
||||||
**Note**: Copy `env.example` to `.env` and update the values before starting.
|
**Note**: Copy `env.example` to `.env` and update the values before starting.
|
||||||
|
|
||||||
### Generating Secrets
|
### Generating Secrets
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate NEXTAUTH_SECRET
|
# Generate NEXTAUTH_SECRET
|
||||||
openssl rand -base64 32
|
openssl rand -base64 32
|
||||||
|
|
||||||
# Generate IP_SALT
|
# Generate IP_SALT
|
||||||
openssl rand -base64 32
|
openssl rand -base64 32
|
||||||
```
|
```
|
||||||
|
|
||||||
## Security & Privacy
|
## Security & Privacy
|
||||||
|
|
||||||
- **IP Hashing**: IP addresses are hashed with salt before storage (GDPR-compliant)
|
- **IP Hashing**: IP addresses are hashed with salt before storage (GDPR-compliant)
|
||||||
- **DNT Respect**: Honors Do Not Track browser headers
|
- **DNT Respect**: Honors Do Not Track browser headers
|
||||||
- **Rate Limiting**: API endpoints protected against abuse
|
- **Rate Limiting**: API endpoints protected against abuse
|
||||||
- **CSRF Protection**: Token-based CSRF validation on mutations
|
- **CSRF Protection**: Token-based CSRF validation on mutations
|
||||||
- **Secure Sessions**: NextAuth.js with encrypted JWT tokens
|
- **Secure Sessions**: NextAuth.js with encrypted JWT tokens
|
||||||
- **Stripe Security**: PCI-compliant payment processing
|
- **Stripe Security**: PCI-compliant payment processing
|
||||||
- **SQL Injection Prevention**: Prisma ORM parameterized queries
|
- **SQL Injection Prevention**: Prisma ORM parameterized queries
|
||||||
|
|
||||||
## Database Schema
|
## Database Schema
|
||||||
|
|
||||||
The application uses PostgreSQL with Prisma ORM. Key models:
|
The application uses PostgreSQL with Prisma ORM. Key models:
|
||||||
|
|
||||||
- **User**: User accounts with Stripe subscription data
|
- **User**: User accounts with Stripe subscription data
|
||||||
- **QRCode**: QR code records (static/dynamic, multiple content types)
|
- **QRCode**: QR code records (static/dynamic, multiple content types)
|
||||||
- **QRScan**: Scan analytics data (hashed IP, device, location, UTM params)
|
- **QRScan**: Scan analytics data (hashed IP, device, location, UTM params)
|
||||||
- **Integration**: Third-party integrations (Zapier, etc.)
|
- **Integration**: Third-party integrations (Zapier, etc.)
|
||||||
- **Account/Session**: NextAuth authentication data
|
- **Account/Session**: NextAuth authentication data
|
||||||
|
|
||||||
### Supported QR Code Types
|
### Supported QR Code Types
|
||||||
|
|
||||||
- **URL**: Website links
|
- **URL**: Website links
|
||||||
- **VCARD**: Contact cards (name, email, phone, company)
|
- **VCARD**: Contact cards (name, email, phone, company)
|
||||||
- **GEO**: GPS locations
|
- **GEO**: GPS locations
|
||||||
- **PHONE**: Phone numbers (tel: links)
|
- **PHONE**: Phone numbers (tel: links)
|
||||||
- **TEXT**: Plain text
|
- **TEXT**: Plain text
|
||||||
- **SMS**: SMS messages
|
- **SMS**: SMS messages
|
||||||
- **WHATSAPP**: WhatsApp messages
|
- **WHATSAPP**: WhatsApp messages
|
||||||
|
|
||||||
### Plans
|
### Plans
|
||||||
|
|
||||||
- **FREE**: 3 dynamic QR codes, unlimited static
|
- **FREE**: 3 dynamic QR codes, unlimited static
|
||||||
- **PRO**: 50 codes, custom branding, advanced analytics
|
- **PRO**: 50 codes, custom branding, advanced analytics
|
||||||
- **BUSINESS**: 500 codes, bulk upload, API access, priority support
|
- **BUSINESS**: 500 codes, bulk upload, API access, priority support
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Docker (Recommended for Self-Hosting)
|
### Docker (Recommended for Self-Hosting)
|
||||||
|
|
||||||
The application includes production-ready Docker configuration with PostgreSQL and Redis:
|
The application includes production-ready Docker configuration with PostgreSQL and Redis:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build and start all services
|
# Build and start all services
|
||||||
docker-compose up -d --build
|
docker-compose up -d --build
|
||||||
|
|
||||||
# Run migrations
|
# Run migrations
|
||||||
docker-compose exec web npx prisma migrate deploy
|
docker-compose exec web npx prisma migrate deploy
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
docker-compose logs -f
|
docker-compose logs -f
|
||||||
```
|
```
|
||||||
|
|
||||||
For detailed deployment instructions, see [DOCKER_SETUP.md](DOCKER_SETUP.md).
|
For detailed deployment instructions, see [DOCKER_SETUP.md](DOCKER_SETUP.md).
|
||||||
|
|
||||||
### Vercel
|
### Vercel
|
||||||
|
|
||||||
1. Push your code to GitHub
|
1. Push your code to GitHub
|
||||||
2. Import the project in Vercel
|
2. Import the project in Vercel
|
||||||
3. Add a PostgreSQL database (Vercel Postgres, Supabase, or other)
|
3. Add a PostgreSQL database (Vercel Postgres, Supabase, or other)
|
||||||
4. Add environment variables in Vercel dashboard
|
4. Add environment variables in Vercel dashboard
|
||||||
5. Deploy
|
5. Deploy
|
||||||
|
|
||||||
**Note**: For Vercel deployment, you'll need to set up a PostgreSQL database separately.
|
**Note**: For Vercel deployment, you'll need to set up a PostgreSQL database separately.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Database Issues
|
### Database Issues
|
||||||
|
|
||||||
**Problem**: Migration errors or schema conflicts
|
**Problem**: Migration errors or schema conflicts
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Solution: Reset the database
|
# Solution: Reset the database
|
||||||
npx prisma migrate reset
|
npx prisma migrate reset
|
||||||
```
|
```
|
||||||
|
|
||||||
**Problem**: "Error: P1001: Can't reach database server"
|
**Problem**: "Error: P1001: Can't reach database server"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check if Docker containers are running
|
# Check if Docker containers are running
|
||||||
docker ps
|
docker ps
|
||||||
|
|
||||||
# Restart database
|
# Restart database
|
||||||
npm run docker:dev:stop
|
npm run docker:dev:stop
|
||||||
npm run docker:dev
|
npm run docker:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
**Problem**: Prisma Client out of sync
|
**Problem**: Prisma Client out of sync
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Regenerate Prisma Client
|
# Regenerate Prisma Client
|
||||||
npx prisma generate
|
npx prisma generate
|
||||||
```
|
```
|
||||||
|
|
||||||
**Problem**: Need to start completely fresh
|
**Problem**: Need to start completely fresh
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop all Docker containers
|
# Stop all Docker containers
|
||||||
npm run docker:dev:stop
|
npm run docker:dev:stop
|
||||||
|
|
||||||
# Remove volumes (⚠️ deletes all data)
|
# Remove volumes (⚠️ deletes all data)
|
||||||
docker volume prune
|
docker volume prune
|
||||||
|
|
||||||
# Restart everything
|
# Restart everything
|
||||||
npm run docker:dev
|
npm run docker:dev
|
||||||
npx prisma migrate dev
|
npx prisma migrate dev
|
||||||
npm run db:seed
|
npm run db:seed
|
||||||
```
|
```
|
||||||
|
|
||||||
### Port Already in Use
|
### Port Already in Use
|
||||||
|
|
||||||
If port 3050 is already in use:
|
If port 3050 is already in use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Find and kill the process (Windows)
|
# Find and kill the process (Windows)
|
||||||
netstat -ano | findstr :3050
|
netstat -ano | findstr :3050
|
||||||
taskkill /PID <PID> /F
|
taskkill /PID <PID> /F
|
||||||
|
|
||||||
# Or change the port in package.json
|
# Or change the port in package.json
|
||||||
"dev": "next dev -p 3051"
|
"dev": "next dev -p 3051"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Issues
|
### Docker Issues
|
||||||
|
|
||||||
**Problem**: Permission denied errors
|
**Problem**: Permission denied errors
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Windows: Run PowerShell as Administrator
|
# Windows: Run PowerShell as Administrator
|
||||||
# Linux/Mac: Use sudo for docker commands
|
# Linux/Mac: Use sudo for docker commands
|
||||||
```
|
```
|
||||||
|
|
||||||
**Problem**: Out of disk space
|
**Problem**: Out of disk space
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clean up Docker
|
# Clean up Docker
|
||||||
docker system prune -a
|
docker system prune -a
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
1. Fork the repository
|
1. Fork the repository
|
||||||
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
||||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
5. Open a Pull Request
|
5. Open a Pull Request
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For support, email support@qrmaster.net or open an issue on GitHub.
|
For support, email support@qrmaster.net or open an issue on GitHub.
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
- Next.js team for the amazing framework
|
- Next.js team for the amazing framework
|
||||||
- Vercel for hosting and deployment
|
- Vercel for hosting and deployment
|
||||||
- All open-source contributors
|
- All open-source contributors
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Built with ❤️ by QR Master Team
|
Built with ❤️ by QR Master Team
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Führe diese im Terminal aus:
|
|
||||||
|
|
||||||
IndexNow (Bing/Yandex + Partner): npm run submit:indexnow
|
|
||||||
Google Indexing API: npm run trigger:indexing
|
|
||||||
|
|||||||
301
SETUP_COMPLETE.md
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
# ✅ Setup Complete - PostgreSQL Migration
|
||||||
|
|
||||||
|
## 🎉 What Was Done
|
||||||
|
|
||||||
|
Your QR Master application has been successfully migrated from Supabase to a local PostgreSQL database with Docker!
|
||||||
|
|
||||||
|
### ✅ Completed Tasks
|
||||||
|
|
||||||
|
1. **Removed Supabase Dependencies**
|
||||||
|
- ❌ Removed `DIRECT_URL` from Prisma schema
|
||||||
|
- ❌ Removed `DIRECT_URL` from environment validation
|
||||||
|
- ❌ Cleaned up all Supabase-specific configurations
|
||||||
|
|
||||||
|
2. **Created Docker Infrastructure**
|
||||||
|
- ✅ Production Docker Compose (`docker-compose.yml`)
|
||||||
|
- ✅ Development Docker Compose (`docker-compose.dev.yml`)
|
||||||
|
- ✅ Optimized Dockerfile for Next.js
|
||||||
|
- ✅ PostgreSQL 16 Alpine with persistence
|
||||||
|
- ✅ Redis 7 Alpine with AOF persistence
|
||||||
|
- ✅ Adminer database UI (optional)
|
||||||
|
- ✅ Custom bridge network for services
|
||||||
|
|
||||||
|
3. **Database Setup**
|
||||||
|
- ✅ PostgreSQL initialization script
|
||||||
|
- ✅ UUID and pg_trgm extensions
|
||||||
|
- ✅ Health checks for all services
|
||||||
|
- ✅ Volume persistence
|
||||||
|
|
||||||
|
4. **Documentation**
|
||||||
|
- ✅ Updated README.md
|
||||||
|
- ✅ Created DOCKER_SETUP.md (comprehensive guide)
|
||||||
|
- ✅ Created MIGRATION_FROM_SUPABASE.md
|
||||||
|
- ✅ Created docker/README.md
|
||||||
|
- ✅ Created CHANGELOG.md
|
||||||
|
- ✅ Created env.example template
|
||||||
|
|
||||||
|
5. **Developer Tools**
|
||||||
|
- ✅ Setup script for Linux/Mac (`scripts/setup.sh`)
|
||||||
|
- ✅ Setup script for Windows (`scripts/setup.ps1`)
|
||||||
|
- ✅ npm Docker scripts
|
||||||
|
- ✅ .dockerignore for optimization
|
||||||
|
|
||||||
|
6. **Environment Configuration**
|
||||||
|
- ✅ Created env.example template
|
||||||
|
- ✅ Updated environment validation
|
||||||
|
- ✅ Simplified configuration
|
||||||
|
|
||||||
|
## 🚀 How to Get Started
|
||||||
|
|
||||||
|
### Option 1: Quick Setup (Recommended)
|
||||||
|
|
||||||
|
#### Windows:
|
||||||
|
```powershell
|
||||||
|
cd scripts
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux/Mac:
|
||||||
|
```bash
|
||||||
|
chmod +x scripts/setup.sh
|
||||||
|
./scripts/setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Manual Setup
|
||||||
|
|
||||||
|
#### Development Mode (Database in Docker, App on Host)
|
||||||
|
```bash
|
||||||
|
# 1. Copy environment file
|
||||||
|
cp env.example .env
|
||||||
|
|
||||||
|
# 2. Edit .env and set NEXTAUTH_SECRET and IP_SALT
|
||||||
|
# Generate with: openssl rand -base64 32
|
||||||
|
|
||||||
|
# 3. Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 4. Start database services
|
||||||
|
npm run docker:dev
|
||||||
|
|
||||||
|
# 5. Run migrations
|
||||||
|
npm run db:migrate
|
||||||
|
|
||||||
|
# 6. Seed database
|
||||||
|
npm run db:seed
|
||||||
|
|
||||||
|
# 7. Start development server
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Production Mode (Full Stack in Docker)
|
||||||
|
```bash
|
||||||
|
# 1. Copy and configure environment
|
||||||
|
cp env.example .env
|
||||||
|
# Edit .env with your settings
|
||||||
|
|
||||||
|
# 2. Build and start
|
||||||
|
npm run docker:prod
|
||||||
|
|
||||||
|
# 3. Run migrations
|
||||||
|
docker-compose exec web npx prisma migrate deploy
|
||||||
|
|
||||||
|
# 4. Access at http://localhost:3050
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📍 Access Points
|
||||||
|
|
||||||
|
After setup, you can access:
|
||||||
|
|
||||||
|
- **🌐 Application**: http://localhost:3050
|
||||||
|
- **🗄️ Database UI (Adminer)**: http://localhost:8080
|
||||||
|
- System: PostgreSQL
|
||||||
|
- Server: db
|
||||||
|
- Username: postgres
|
||||||
|
- Password: postgres
|
||||||
|
- Database: qrmaster
|
||||||
|
- **💾 PostgreSQL**: localhost:5432
|
||||||
|
- **🔴 Redis**: localhost:6379
|
||||||
|
|
||||||
|
## 📦 What's Included
|
||||||
|
|
||||||
|
### Docker Services
|
||||||
|
|
||||||
|
| Service | Image | Port | Purpose |
|
||||||
|
|---------|-------|------|---------|
|
||||||
|
| web | Next.js (custom) | 3050 | Application |
|
||||||
|
| db | postgres:16-alpine | 5432 | Database |
|
||||||
|
| redis | redis:7-alpine | 6379 | Cache |
|
||||||
|
| adminer | adminer:latest | 8080 | DB UI |
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
QRMASTER/
|
||||||
|
├── docker/
|
||||||
|
│ ├── init-db.sh # PostgreSQL initialization
|
||||||
|
│ └── README.md # Docker commands
|
||||||
|
├── scripts/
|
||||||
|
│ ├── setup.sh # Quick setup (Linux/Mac)
|
||||||
|
│ └── setup.ps1 # Quick setup (Windows)
|
||||||
|
├── src/ # Application code
|
||||||
|
├── prisma/
|
||||||
|
│ └── schema.prisma # Updated schema (no directUrl)
|
||||||
|
├── docker-compose.yml # Production setup
|
||||||
|
├── docker-compose.dev.yml # Development setup
|
||||||
|
├── Dockerfile # Application container
|
||||||
|
├── env.example # Environment template
|
||||||
|
├── .dockerignore # Docker build optimization
|
||||||
|
├── DOCKER_SETUP.md # Complete Docker guide
|
||||||
|
├── MIGRATION_FROM_SUPABASE.md # Migration guide
|
||||||
|
├── CHANGELOG.md # What changed
|
||||||
|
└── README.md # Updated main docs
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Useful Commands
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
npm run dev # Start dev server
|
||||||
|
npm run docker:dev # Start database only
|
||||||
|
npm run docker:dev:stop # Stop database
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database
|
||||||
|
```bash
|
||||||
|
npm run db:migrate # Run migrations
|
||||||
|
npm run db:seed # Seed database
|
||||||
|
npm run db:studio # Open Prisma Studio
|
||||||
|
npm run docker:db # PostgreSQL CLI
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
```bash
|
||||||
|
npm run docker:prod # Start all services
|
||||||
|
npm run docker:stop # Stop all services
|
||||||
|
npm run docker:logs # View logs
|
||||||
|
npm run docker:backup # Backup database
|
||||||
|
```
|
||||||
|
|
||||||
|
### Management
|
||||||
|
```bash
|
||||||
|
docker-compose ps # Check status
|
||||||
|
docker-compose logs -f # Follow logs
|
||||||
|
docker-compose restart web # Restart app
|
||||||
|
docker-compose exec db psql -U postgres -d qrmaster # DB CLI
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
- **[README.md](README.md)** - Main documentation with quick start
|
||||||
|
- **[DOCKER_SETUP.md](DOCKER_SETUP.md)** - Complete Docker guide with troubleshooting
|
||||||
|
- **[MIGRATION_FROM_SUPABASE.md](MIGRATION_FROM_SUPABASE.md)** - Migration guide from Supabase
|
||||||
|
- **[docker/README.md](docker/README.md)** - Docker commands and operations
|
||||||
|
- **[CHANGELOG.md](CHANGELOG.md)** - What changed in this version
|
||||||
|
|
||||||
|
## 🔐 Security Checklist
|
||||||
|
|
||||||
|
Before deploying to production:
|
||||||
|
|
||||||
|
- [ ] Change PostgreSQL password in docker-compose.yml
|
||||||
|
- [ ] Set strong NEXTAUTH_SECRET (generate with `openssl rand -base64 32`)
|
||||||
|
- [ ] Set strong IP_SALT (generate with `openssl rand -base64 32`)
|
||||||
|
- [ ] Update NEXTAUTH_URL to your domain
|
||||||
|
- [ ] Enable HTTPS/SSL
|
||||||
|
- [ ] Set up firewall rules
|
||||||
|
- [ ] Configure automated backups
|
||||||
|
- [ ] Review and test all environment variables
|
||||||
|
|
||||||
|
## 🎯 Next Steps
|
||||||
|
|
||||||
|
1. **Test the Application**
|
||||||
|
```bash
|
||||||
|
npm run docker:dev
|
||||||
|
npm run dev
|
||||||
|
# Visit http://localhost:3050
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Review Configuration**
|
||||||
|
- Check `.env` file
|
||||||
|
- Verify database connection
|
||||||
|
- Test authentication
|
||||||
|
|
||||||
|
3. **Set Up Backups**
|
||||||
|
```bash
|
||||||
|
# Manual backup
|
||||||
|
npm run docker:backup
|
||||||
|
|
||||||
|
# Or create automated backup script
|
||||||
|
# See DOCKER_SETUP.md for examples
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Customize**
|
||||||
|
- Update database passwords
|
||||||
|
- Configure OAuth providers
|
||||||
|
- Adjust resource limits
|
||||||
|
- Set up monitoring
|
||||||
|
|
||||||
|
## 🆘 Need Help?
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Database won't start:**
|
||||||
|
```bash
|
||||||
|
docker-compose logs db
|
||||||
|
docker-compose restart db
|
||||||
|
```
|
||||||
|
|
||||||
|
**Port already in use:**
|
||||||
|
```bash
|
||||||
|
# Windows
|
||||||
|
netstat -ano | findstr :3050
|
||||||
|
|
||||||
|
# Change port in docker-compose.yml if needed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prisma errors:**
|
||||||
|
```bash
|
||||||
|
npm run db:generate
|
||||||
|
npm run db:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resources
|
||||||
|
|
||||||
|
- **DOCKER_SETUP.md** - Comprehensive troubleshooting
|
||||||
|
- **docker/README.md** - Common Docker commands
|
||||||
|
- **MIGRATION_FROM_SUPABASE.md** - Migration help
|
||||||
|
|
||||||
|
### Support
|
||||||
|
|
||||||
|
1. Check the documentation files
|
||||||
|
2. Review logs: `docker-compose logs -f`
|
||||||
|
3. Check service health: `docker-compose ps`
|
||||||
|
4. Open an issue on GitHub
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
Your application now has:
|
||||||
|
|
||||||
|
- ✅ **Self-hosted PostgreSQL** - Full control over your data
|
||||||
|
- ✅ **Redis caching** - Improved performance
|
||||||
|
- ✅ **Docker Compose** - Easy deployment
|
||||||
|
- ✅ **Health checks** - Automatic monitoring
|
||||||
|
- ✅ **Data persistence** - Volumes for data safety
|
||||||
|
- ✅ **Database UI** - Adminer for easy management
|
||||||
|
- ✅ **Development mode** - Run only what you need
|
||||||
|
- ✅ **Production ready** - Optimized Docker builds
|
||||||
|
- ✅ **Complete docs** - Multiple guides and references
|
||||||
|
|
||||||
|
## 🎊 Success!
|
||||||
|
|
||||||
|
You're now ready to develop and deploy QR Master with your own PostgreSQL database!
|
||||||
|
|
||||||
|
**Demo Credentials:**
|
||||||
|
- Email: demo@qrmaster.com
|
||||||
|
- Password: demo123
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy coding!** 🚀
|
||||||
|
|
||||||
|
Need more help? Check the documentation or run the setup scripts.
|
||||||
|
|
||||||
@@ -1,291 +0,0 @@
|
|||||||
# 🚀 Side Project Marketing Strategy
|
|
||||||
|
|
||||||
> **"Engineering as Marketing"** – Kostenlose Micro-Tools bauen, um SEO-Traffic abzufangen und in zahlende Kunden zu konvertieren.
|
|
||||||
|
|
||||||
**Status:** Planung abgeschlossen, bereit für Implementierung
|
|
||||||
**Autor:** QR Master Team
|
|
||||||
**Letzte Aktualisierung:** 2026-01-08
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
Wir nutzen die bewiesene "Engineering as Marketing" Strategie (bekannt von HubSpot's Website Grader, Ahrefs' Free Tools, Shopify's Business Tools), um organischen Traffic über spezialisierte, kostenlose QR-Generatoren zu gewinnen.
|
|
||||||
|
|
||||||
### Das Konzept in einem Satz
|
|
||||||
|
|
||||||
> Anstatt gegen "QR Code Generator" (DA 90+ Konkurrenz) zu kämpfen, bauen wir 10 spezialisierte Tools für Long-Tail-Keywords wie "WiFi QR Code erstellen" oder "VCard QR Generator".
|
|
||||||
|
|
||||||
### Warum das funktioniert
|
|
||||||
|
|
||||||
1. **Weniger Konkurrenz:** "WiFi QR Code Generator" hat 1/10 der Konkurrenz von "QR Code Generator"
|
|
||||||
2. **Höhere Kaufabsicht:** Wer "Restaurant Menu QR Code" sucht, ist bereit für ein Premium-Tool
|
|
||||||
3. **Natürliche Backlinks:** Leute teilen nützliche Tools ("Hier, dieser Generator ist kostenlos")
|
|
||||||
4. **Zero Marginal Cost:** Client-Side Generierung = 0€ Serverkosten pro User
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ROI Projektion (Konservativ)
|
|
||||||
|
|
||||||
| Metrik | Monat 3 | Monat 6 | Monat 12 |
|
|
||||||
|--------|---------|---------|----------|
|
|
||||||
| Organischer Traffic (alle Tools) | 2.000 | 10.000 | 25.000 |
|
|
||||||
| Free Signups (20% Conv.) | 400 | 2.000 | 5.000 |
|
|
||||||
| Paid Customers (3% der Signups) | 12 | 60 | 150 |
|
|
||||||
| **Zusätzlicher MRR** | **108€** | **540€** | **1.350€** |
|
|
||||||
|
|
||||||
> **Benchmarks verwendet:** 2-3% Free-to-Paid Conversion (Industry Standard), 20% Tool-to-Signup (optimistisch, aber erreichbar mit gutem UX).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Die Tools-Roadmap
|
|
||||||
|
|
||||||
### Phase 1: Quick Wins (Woche 1-2)
|
|
||||||
|
|
||||||
Fokus auf **hohes Suchvolumen + geringe Komplexität**.
|
|
||||||
|
|
||||||
| Tool | URL | Geschätztes SV | Implementierungs-Aufwand |
|
|
||||||
|------|-----|----------------|-------------------------|
|
|
||||||
| **WiFi QR Generator** | `/tools/wifi-qr-code` | 40.000/Monat | 4h |
|
|
||||||
| **VCard QR Generator** | `/tools/vcard-qr-code` | 15.000/Monat | 4h |
|
|
||||||
| **WhatsApp QR Generator** | `/tools/whatsapp-qr-code` | 20.000/Monat | 3h |
|
|
||||||
|
|
||||||
### Phase 2: Monetization Focus (Woche 3-4)
|
|
||||||
|
|
||||||
Fokus auf **hohe Conversion-Wahrscheinlichkeit** (B2B Use Cases).
|
|
||||||
|
|
||||||
| Tool | URL | Geschätztes SV | Upsell-Hook |
|
|
||||||
|------|-----|----------------|-------------|
|
|
||||||
| **App Store Link QR** | `/tools/app-store-qr-code` | 5.000/Monat | Smart Routing (iOS/Android) |
|
|
||||||
| **PDF to QR** | `/tools/pdf-qr-code` | 15.000/Monat | PDF Hosting (benötigt Account) |
|
|
||||||
| **Menu QR Generator** | `/tools/menu-qr-code` | 8.000/Monat | Multi-Sprache, Analytics |
|
|
||||||
|
|
||||||
### Phase 3: Differenzierung (Monat 2+)
|
|
||||||
|
|
||||||
Fokus auf **Unique Features** die Konkurrenten nicht haben.
|
|
||||||
|
|
||||||
| Tool | URL | Differenzierung |
|
|
||||||
|------|-----|-----------------|
|
|
||||||
| **Barcode Generator** | `/tools/barcode-generator` | EAN/UPC/ISBN Unterstützung |
|
|
||||||
| **Bitcoin/Crypto QR** | `/tools/bitcoin-qr-code` | Multi-Wallet Format |
|
|
||||||
| **AI Art QR (Viral)** | `/tools/ai-qr-code` | Stable Diffusion Integration |
|
|
||||||
|
|
||||||
## Geplantes Portfolio: Kostenlose Statische Generatoren (15 Typen)
|
|
||||||
|
|
||||||
Wir werden die folgenden 15 statischen QR-Code-Typen anbieten. Diese sind **dauerhaft kostenlos** und erfordern keine Server-Infrastruktur für Redirects (im Gegensatz zu dynamischen Codes).
|
|
||||||
|
|
||||||
> **Wichtig:** Alle diese Generatoren stehen sowohl **öffentlich als SEO-Landingpages** zur Verfügung (zur Neukundengewinnung), als auch im **eingeloggten Bereich** für registrierte Nutzer (für Komfort und Zentralisierung).
|
|
||||||
|
|
||||||
1. **URL / Link**: Der Standard. Öffnet eine Webseite.
|
|
||||||
2. **Text**: Zeigt reinen Text an (bis zu 300 Zeichen).
|
|
||||||
3. **WiFi**: Verbindet direkt mit einem WLAN-Netzwerk (WPA/WEP/Open).
|
|
||||||
4. **VCard / Kontakt**: Speichert einen Kontakt direkt im Adressbuch.
|
|
||||||
5. **WhatsApp**: Startet einen Chat mit einer Nummer (und optionalem Text).
|
|
||||||
6. **E-Mail**: Öffnet das E-Mail-Programm mit Empfänger, Betreff und Body.
|
|
||||||
7. **SMS**: Bereitet eine SMS an eine Nummer vor.
|
|
||||||
8. **Anruf / Tel**: Startet einen Anruf an eine Nummer.
|
|
||||||
9. **Event / Kalender**: Fügt einen Termin zum Kalender hinzu (.ics).
|
|
||||||
10. **Geo / Maps**: Öffnet einen Standort in Google Maps/Apple Maps.
|
|
||||||
11. **Facebook**: Öffnet ein Profil oder eine Seite.
|
|
||||||
12. **Instagram**: Öffnet ein Instagram-Profil.
|
|
||||||
13. **Twitter / X**: Öffnet ein Profil oder erstellt einen Tweet.
|
|
||||||
14. **YouTube**: Öffnet ein Video oder einen Kanal.
|
|
||||||
15. **TikTok**: Öffnet ein TikTok-Profil.
|
|
||||||
|
|
||||||
Diese Breite deckt 99% der "Everyday Use Cases" ab und maximiert die SEO-Angriffsfläche.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technische Architektur
|
|
||||||
|
|
||||||
### Warum Client-Side Generierung?
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ USER BROWSER │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
|
|
||||||
│ │ Form Input │ -> │ qrcode.js │ -> │ Canvas/SVG │ │
|
|
||||||
│ │ (SSID, PW) │ │ (generation) │ │ (download) │ │
|
|
||||||
│ └─────────────┘ └──────────────┘ └────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ KEINE Server-Calls! │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Vorteile:**
|
|
||||||
- **Privatsphäre:** Passwörter verlassen nie den Browser
|
|
||||||
- **Speed:** Instant Generation (kein Network Latency)
|
|
||||||
- **Kosten:** 0€ pro generiertem Code
|
|
||||||
- **Scale:** Kein Backend-Limit
|
|
||||||
|
|
||||||
### Datei-Struktur (Next.js)
|
|
||||||
|
|
||||||
```
|
|
||||||
src/app/(marketing)/tools/
|
|
||||||
├── wifi-qr-code/
|
|
||||||
│ ├── page.tsx # Server Component (SEO)
|
|
||||||
│ └── WiFiGenerator.tsx # Client Component (Interaktion)
|
|
||||||
├── vcard-qr-code/
|
|
||||||
│ ├── page.tsx
|
|
||||||
│ └── VCardGenerator.tsx
|
|
||||||
└── [weitere tools]/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shared Components
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/components/tools/QRDownloadButtons.tsx
|
|
||||||
// Wiederverwendbare Download-Buttons für alle Tools
|
|
||||||
|
|
||||||
// src/components/tools/UpgradePrompt.tsx
|
|
||||||
// "Willst du Scans tracken?" CTA Box
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## SEO-Strategie pro Tool-Page
|
|
||||||
|
|
||||||
Jede Seite folgt dem gleichen bewährten Muster:
|
|
||||||
|
|
||||||
### 1. Above the Fold: Sofort nutzbar
|
|
||||||
|
|
||||||
```
|
|
||||||
┌────────────────────────────────────────┐
|
|
||||||
│ H1: Free WiFi QR Code Generator │
|
|
||||||
│ Subline: Teile dein WLAN in Sekunden │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ [SSID] [Password] [WPA▼] │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ [ Generate QR Code ] │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
└────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Regel:** Der User muss SOFORT interagieren können. Kein langer Intro-Text.
|
|
||||||
|
|
||||||
### 2. Schema Markup (Pflicht!)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "SoftwareApplication",
|
|
||||||
"name": "WiFi QR Code Generator",
|
|
||||||
"applicationCategory": "UtilitiesApplication",
|
|
||||||
"operatingSystem": "Web Browser",
|
|
||||||
"offers": {
|
|
||||||
"@type": "Offer",
|
|
||||||
"price": "0",
|
|
||||||
"priceCurrency": "EUR"
|
|
||||||
},
|
|
||||||
"aggregateRating": {
|
|
||||||
"@type": "AggregateRating",
|
|
||||||
"ratingValue": "4.8",
|
|
||||||
"ratingCount": "1247"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. FAQ Section (Long-Tail Keywords)
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Häufig gestellte Fragen
|
|
||||||
|
|
||||||
### Wie funktioniert ein WiFi QR Code?
|
|
||||||
Der QR Code enthält deine WLAN-Daten im Format...
|
|
||||||
|
|
||||||
### Ist es sicher, mein WiFi Passwort in einem QR Code zu speichern?
|
|
||||||
Ja, der QR Code wird nur lokal in deinem Browser generiert...
|
|
||||||
|
|
||||||
### Kann ich den QR Code später bearbeiten?
|
|
||||||
Dieser Generator erstellt statische Codes. Für editierbare...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Conversion Prompt (Der Hook)
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ ✅ QR Code erfolgreich erstellt! │
|
|
||||||
│ │
|
|
||||||
│ ⚠️ Hinweis: Dies ist ein statischer Code. │
|
|
||||||
│ Wenn du dein Passwort änderst, musst du neu drucken. │
|
|
||||||
│ │
|
|
||||||
│ → Erstelle einen dynamischen Code (jederzeit änderbar) │
|
|
||||||
│ │
|
|
||||||
│ Bonus: Sieh wer deinen Code scannt (Datum, Standort) │
|
|
||||||
│ │
|
|
||||||
│ [ Kostenlos registrieren ] │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conversion Optimierung
|
|
||||||
|
|
||||||
### Die "Limitation Awareness" Methode
|
|
||||||
|
|
||||||
Jedes Tool zeigt nach der Generierung **sanft** die Limitierungen auf:
|
|
||||||
|
|
||||||
| Tool | Statische Limitation | Upsell-Feature |
|
|
||||||
|------|---------------------|----------------|
|
|
||||||
| WiFi | Passwort-Änderung = Neudruck | Dynamischer Code (editierbar) |
|
|
||||||
| VCard | Kontakt-Update = Neudruck | Immer aktuelle Visitenkarte |
|
|
||||||
| Menu | Neue Speisekarte = Neudruck | PDF-Hosting + Analytics |
|
|
||||||
| App Store | Nur ein Store-Link | Smart Device Detection |
|
|
||||||
|
|
||||||
### Email Capture vor Download
|
|
||||||
|
|
||||||
**Optional (A/B testen):**
|
|
||||||
```
|
|
||||||
"Gib deine Email ein, um den QR als hochauflösende PNG zu erhalten"
|
|
||||||
```
|
|
||||||
→ Baut Email-Liste, auch wenn User nicht sofort konvertiert.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Erfolgsmetriken (KPIs)
|
|
||||||
|
|
||||||
| KPI | Tool | Ziel (Monat 3) |
|
|
||||||
|-----|------|----------------|
|
|
||||||
| **Organic Sessions** | Google Analytics | 2.000/Monat |
|
|
||||||
| **QR Generations** | PostHog Event | 500/Monat |
|
|
||||||
| **Signup Clicks** | PostHog Event | 100/Monat |
|
|
||||||
| **Actual Signups** | DB Query | 50/Monat |
|
|
||||||
| **Paid Conversion** | Stripe | 5/Monat |
|
|
||||||
|
|
||||||
### Tracking Events implementieren
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Auf jeder Tool-Page
|
|
||||||
posthog.capture('tool_qr_generated', {
|
|
||||||
tool: 'wifi',
|
|
||||||
format: 'png'
|
|
||||||
});
|
|
||||||
|
|
||||||
posthog.capture('tool_signup_cta_clicked', {
|
|
||||||
tool: 'wifi'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Nächste Schritte
|
|
||||||
|
|
||||||
1. [ ] **Heute:** WiFi QR Generator implementieren (`/tools/wifi-qr-code`)
|
|
||||||
2. [ ] **Diese Woche:** VCard + WhatsApp Generator
|
|
||||||
3. [ ] **Nächste Woche:** Google Search Console monitoren für erste Impressions
|
|
||||||
4. [ ] **Monat 2:** A/B Test Email-Capture vs. Direct Download
|
|
||||||
5. [ ] **Monat 3:** Phase 2 Tools (App Store, PDF, Menu)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Referenzen & Inspiration
|
|
||||||
|
|
||||||
- [HubSpot Website Grader](https://website.grader.com/) – Das Original "Engineering as Marketing"
|
|
||||||
- [Ahrefs Free Tools](https://ahrefs.com/free-seo-tools) – 12+ Free Tools als Lead Magnets
|
|
||||||
- [Shopify Business Tools](https://www.shopify.com/tools) – Logo Maker, Invoice Generator, etc.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Dieses Dokument wird regelmäßig aktualisiert basierend auf Traffic-Daten und Conversion-Rates.*
|
|
||||||
461
TODO.md
@@ -1,461 +0,0 @@
|
|||||||
# QR Master — Growth TODO
|
|
||||||
|
|
||||||
Based on: 3-month audit (PostHog + GSC + AI citations + full codebase review, April 2026)
|
|
||||||
Domain age at audit: ~3 months | Visitors (90d): 169 | AI citations (33d): 1,849
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to use this file
|
|
||||||
|
|
||||||
Sections are ordered by impact-per-hour-of-work. Don't skip to section 5 before section 1.
|
|
||||||
Each task has the exact file to touch and what to change. No hand-waving.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Section 1 — Retention (do this week, nothing else matters until it's done)
|
|
||||||
|
|
||||||
Cohort data shows ~10% week-1 retention, near-zero by week 3. Root cause: **no email is sent after signup**. Zero. The `sendWelcomeEmail` function does not exist. Users sign up and hear nothing.
|
|
||||||
|
|
||||||
### 1.1 — Welcome email sequence (3 emails)
|
|
||||||
|
|
||||||
**File to edit:** `src/lib/email.ts`
|
|
||||||
Add three new exported functions after the existing `sendPasswordResetEmail`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Email 1: Sent immediately on signup
|
|
||||||
export async function sendWelcomeEmail(email: string, name: string) {}
|
|
||||||
|
|
||||||
// Email 2: Sent on Day 3 if user has 0 QR codes (check via cron or on login)
|
|
||||||
export async function sendActivationNudgeEmail(email: string, name: string) {}
|
|
||||||
|
|
||||||
// Email 3: Sent on Day 7 if user has ≥1 QR code but is still on FREE plan
|
|
||||||
export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount: number) {}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Email 1 — Welcome (Day 0)**
|
|
||||||
- Subject: `Your QR Master account is ready`
|
|
||||||
- Body: One action only → create first QR code. Link to `/create`.
|
|
||||||
- Mention the 3 free dynamic codes.
|
|
||||||
- 4–5 sentences max, plain layout.
|
|
||||||
- Sender: `Timo from QR Master <timo@qrmaster.net>`
|
|
||||||
|
|
||||||
**Email 2 — Activation nudge (Day 3, no QR codes created)**
|
|
||||||
- Subject: `Still haven't made your first QR code?`
|
|
||||||
- Trigger condition: `user.createdAt < now - 3 days AND qrCodes.count === 0`
|
|
||||||
- Body: One screenshot or step-by-step (3 steps). One CTA → `/create`.
|
|
||||||
- Don't send if they've already created one.
|
|
||||||
|
|
||||||
**Email 3 — Upgrade nudge (Day 7, has codes, still FREE)**
|
|
||||||
- Subject: `You've created {n} QR codes — here's what you're missing`
|
|
||||||
- Trigger condition: `qrCodes.count >= 1 AND plan === 'FREE' AND createdAt < now - 7 days`
|
|
||||||
- Body: 3 specific things they can't do on free: analytics breakdown, custom branding, more than 3 codes.
|
|
||||||
- CTA → `/pricing`
|
|
||||||
|
|
||||||
**File to edit:** `src/app/(main)/api/auth/signup/route.ts`
|
|
||||||
Add `sendWelcomeEmail` call after the user is created (line ~88, after `db.user.create`):
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// After db.user.create(...)
|
|
||||||
try {
|
|
||||||
await sendWelcomeEmail(user.email, user.name ?? 'there');
|
|
||||||
} catch (emailError) {
|
|
||||||
// Don't fail signup if email fails
|
|
||||||
console.error('Welcome email failed:', emailError);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**For emails 2 and 3:** Either add a cron job (`/api/cron/retention-emails`) or check + send on each login in `/api/auth/simple-login` route. Cron is cleaner. Vercel cron syntax:
|
|
||||||
|
|
||||||
```json
|
|
||||||
// vercel.json
|
|
||||||
{
|
|
||||||
"crons": [{ "path": "/api/cron/retention-emails", "schedule": "0 10 * * *" }]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 1.2 — Cancel flow (before Stripe portal)
|
|
||||||
|
|
||||||
**File to edit:** `src/app/(main)/(app)/settings/page.tsx`
|
|
||||||
Right now: button → Stripe portal. Users cancel without any friction or reason capture.
|
|
||||||
|
|
||||||
Replace the `Manage Subscription` button with a handler that opens a modal first:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// Add state
|
|
||||||
const [showCancelModal, setShowCancelModal] = useState(false);
|
|
||||||
const [cancelReason, setCancelReason] = useState('');
|
|
||||||
|
|
||||||
// Replace direct portal redirect with:
|
|
||||||
const handleManageSubscription = () => {
|
|
||||||
if (plan !== 'FREE') {
|
|
||||||
setShowCancelModal(true); // intercept — show exit survey first
|
|
||||||
} else {
|
|
||||||
// FREE users can just go to portal normally
|
|
||||||
openStripePortal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cancel modal — 4 steps:**
|
|
||||||
|
|
||||||
1. **Survey:** "Before you go — what's the main reason?" (radio: too expensive / not using it / missing feature / other)
|
|
||||||
2. **Save offer** based on reason:
|
|
||||||
- `too_expensive` → *"What if we gave you 25% off for 2 months?"* → button calls `/api/stripe/apply-discount`
|
|
||||||
- `not_using` → *"Want to pause instead of cancel?"* → pause link or info
|
|
||||||
- `missing_feature` → *"Tell us what's missing"* → textarea → submit to `/api/feedback`, then let them proceed
|
|
||||||
- `other` → short text → submit to `/api/feedback`, then proceed
|
|
||||||
3. **Confirm:** "Still want to cancel? Your plan stays active until [end of billing period]." → button opens Stripe portal
|
|
||||||
4. **Post-cancel:** Stripe webhook already exists at `/api/stripe/webhook` — add a case for `customer.subscription.deleted` that logs the cancellation reason and (optionally) triggers a win-back email in 30 days.
|
|
||||||
|
|
||||||
**Save the reasons to DB.** Add a `cancellationReason` field to the User model in `prisma/schema.prisma` so you actually learn why people leave.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Section 2 — Signup flow (2–3 hours of work, immediate conversion lift)
|
|
||||||
|
|
||||||
Current form: Name → Email → Password → Confirm Password → [Create Account] → divider → Google button
|
|
||||||
Problem: Google should be first. Confirm Password should not exist.
|
|
||||||
|
|
||||||
### 2.1 — Move Google OAuth above the form
|
|
||||||
|
|
||||||
**File:** `src/app/(main)/(auth)/signup/SignupClient.tsx`
|
|
||||||
|
|
||||||
Move the Google button block (currently after the `<form>`) to above the `<form>`. The divider "Or continue with" becomes "Or sign up with email" below the Google button.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// New order in the Card:
|
|
||||||
<CardContent className="p-6">
|
|
||||||
{/* Google FIRST */}
|
|
||||||
<Button type="button" variant="outline" className="w-full" onClick={handleGoogleSignIn}>
|
|
||||||
{/* Google SVG */} Sign up with Google
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div className="relative my-6">
|
|
||||||
{/* divider */}
|
|
||||||
<span className="px-2 bg-white text-gray-500">Or sign up with email</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Email form SECOND */}
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
...
|
|
||||||
</form>
|
|
||||||
</CardContent>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 — Remove the Confirm Password field
|
|
||||||
|
|
||||||
**File:** `src/app/(main)/(auth)/signup/SignupClient.tsx`
|
|
||||||
|
|
||||||
Delete the `confirmPassword` state, the `confirmPassword` Input field, and the `if (password !== confirmPassword)` check. Replace with a password visibility toggle on the Password field instead:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
|
|
||||||
// In the Input:
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
label="Password"
|
|
||||||
type={showPassword ? 'text' : 'password'}
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
placeholder="••••••••"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-3 top-9 text-gray-400 hover:text-gray-600"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 — Add value reinforcement to the signup page
|
|
||||||
|
|
||||||
**File:** `src/app/(main)/(auth)/signup/SignupClient.tsx`
|
|
||||||
|
|
||||||
Change the subtitle under "Create Account" from:
|
|
||||||
> *"Start creating QR codes in seconds"*
|
|
||||||
|
|
||||||
To:
|
|
||||||
> *"No credit card required — 3 dynamic QR codes free forever"*
|
|
||||||
|
|
||||||
Also remove the prominent "← Back to Home" bordered button. It's visually competing with the form. Replace with a small text link at the bottom of the card:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<p className="text-center text-xs text-gray-400 mt-4">
|
|
||||||
Already have an account? <Link href="/login">Sign in</Link> · <Link href="/">Back to home</Link>
|
|
||||||
</p>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Section 3 — Homepage copy (half a day)
|
|
||||||
|
|
||||||
### 3.1 — Headline rewrite
|
|
||||||
|
|
||||||
**File:** `src/i18n/en.json`
|
|
||||||
Current: `"title": "Create QR Codes That Work Everywhere"`
|
|
||||||
|
|
||||||
Replace with one of (pick based on your gut for the audience):
|
|
||||||
|
|
||||||
```json
|
|
||||||
"title": "The Free QR Code Generator That Doesn't Expire"
|
|
||||||
```
|
|
||||||
or
|
|
||||||
```json
|
|
||||||
"title": "Track Every Scan. Update Every Link. Free Forever."
|
|
||||||
```
|
|
||||||
|
|
||||||
Subtitle — current: *"Generate static and dynamic QR codes with tracking, custom branding, and bulk generation. Free forever."*
|
|
||||||
Replace:
|
|
||||||
```json
|
|
||||||
"subtitle": "Dynamic QR codes you can edit anytime without reprinting — see exactly who scanned, where, and on what device. Starts free, no credit card."
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 — Feature bullets: features → outcomes
|
|
||||||
|
|
||||||
**File:** `src/i18n/en.json`, `hero.features` array
|
|
||||||
|
|
||||||
```json
|
|
||||||
"features": [
|
|
||||||
"Change your link anytime — no reprinting needed",
|
|
||||||
"See scan location, device, and time of day",
|
|
||||||
"Your QR codes work forever, even on the free plan",
|
|
||||||
"Match your brand: custom colors in under a minute"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 — Hero right column: replace animation with product screenshot
|
|
||||||
|
|
||||||
**File:** `src/components/marketing/Hero.tsx`
|
|
||||||
|
|
||||||
The flipping cards grid (lines ~130–175) should be replaced with either:
|
|
||||||
- A static screenshot of the analytics dashboard (real scan data, anonymized)
|
|
||||||
- A live mini-generator: paste a URL → QR code renders inline (highest conversion)
|
|
||||||
|
|
||||||
A live mini-generator in the hero is the highest-impact change on the whole homepage. Even a simplified version that just renders a basic QR code and links to `/create` for customization would work.
|
|
||||||
|
|
||||||
### 3.4 — Add a social proof number to the hero
|
|
||||||
|
|
||||||
**File:** `src/components/marketing/Hero.tsx` or `src/components/marketing/StatsStrip.tsx`
|
|
||||||
|
|
||||||
Add one concrete number directly in or immediately below the hero — before the fold. Options:
|
|
||||||
- `"1M+ QR codes generated"` (if true or close)
|
|
||||||
- `"Trusted by 500+ businesses"` (if defensible)
|
|
||||||
- `"Cited by ChatGPT, Perplexity & Google AI"` (actually true based on your AI data — this is a unique claim almost no competitor can make)
|
|
||||||
|
|
||||||
The last one is genuinely differentiated and verified.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Section 4 — Content: the 5 posts that will move GSC rankings
|
|
||||||
|
|
||||||
These are ordered by current GSC impression data — they already have Google's attention, they just need the content to justify moving up.
|
|
||||||
|
|
||||||
### 4.1 — Barcode generator companion post
|
|
||||||
|
|
||||||
**Current situation:** `/tools/barcode-generator` has 1,160 GSC impressions at position 71. No blog post supports it.
|
|
||||||
|
|
||||||
**Create:** `src/app/(main)/(marketing)/blog/barcode-vs-qr-code/` (or add to `src/lib/blog-data.ts`)
|
|
||||||
**Title:** *"Barcode vs QR Code: What's the Difference and When to Use Each"*
|
|
||||||
**Target query:** `barcode vs qr code` / `difference between barcode and qr code`
|
|
||||||
**Must include:** a comparison table (scanners required, data capacity, use cases, editability), a "when to use each" section, internal links to `/tools/barcode-generator` and `/dynamic-qr-code-generator`.
|
|
||||||
|
|
||||||
### 4.2 — Teams QR code blog post
|
|
||||||
|
|
||||||
**Current situation:** `/tools/teams-qr-code` gets 20 PostHog visitors with 0% bounce and ranks ~position 22 in GSC — the closest page to page 1 you have.
|
|
||||||
|
|
||||||
**Create:** blog post targeting `microsoft teams qr code` / `teams meeting qr code`
|
|
||||||
**Title:** *"How to Create a Microsoft Teams QR Code for Instant Meeting Joins"*
|
|
||||||
**Internal link:** back to `/tools/teams-qr-code` from the post and vice versa (the tool page should link to this post for content depth).
|
|
||||||
|
|
||||||
### 4.3 — WiFi QR code post update
|
|
||||||
|
|
||||||
**Current situation:** `/tools/wifi-qr-code` ranks ~position 44 (close to page 4). 14 PostHog visitors, 0% bounce.
|
|
||||||
|
|
||||||
**File:** The wifi tool page has German keywords mixed into English metadata (`wlan qr code erstellen`, `wifi passwort qr code`). Decide: is this page targeting English or German? Mixed intent hurts both. For the English version, clean up to English-only keywords. For German, create a dedicated `/de/` route (there's already a German path at `(marketing-de)`).
|
|
||||||
|
|
||||||
**Action:** Add a content section below the WiFi generator tool with at minimum: a "common WiFi QR code uses" section (restaurants, hotels, offices, Airbnb), a FAQ block (3–5 questions), and an internal link to the main QR generator.
|
|
||||||
|
|
||||||
### 4.4 — Fix the 2025-dated content
|
|
||||||
|
|
||||||
**File:** `src/lib/blog-data.ts`
|
|
||||||
|
|
||||||
The post with slug `qr-code-tracking-guide-2025` has `2025` in its URL. Update:
|
|
||||||
- `slug`: change to `qr-code-tracking-guide` or `qr-code-tracking-guide-2026`
|
|
||||||
- `title`: update year references in the content
|
|
||||||
- `dateModified`: set to current date
|
|
||||||
- Add a redirect in `next.config.mjs` from the old slug to the new one:
|
|
||||||
|
|
||||||
```js
|
|
||||||
async redirects() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
source: '/blog/qr-code-tracking-guide-2025',
|
|
||||||
destination: '/blog/qr-code-tracking-guide-2026',
|
|
||||||
permanent: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.5 — "Best QR code generator 2026" — make it actually compete
|
|
||||||
|
|
||||||
**File:** `src/lib/blog-data.ts` — post slug `best-qr-code-generator-2026` exists but has ~9 GSC position and 0 clicks from 38 impressions.
|
|
||||||
|
|
||||||
This post needs to be the most comprehensive comparison page on the site. It currently isn't doing the job. It needs:
|
|
||||||
- A feature comparison table: QR Master vs Bitly vs QR Code Generator vs Beaconstac (columns: free plan limits, dynamic QR, analytics, branding, bulk, pricing)
|
|
||||||
- Honest pros/cons for each (being fake-neutral is immediately obvious and AI systems penalise it)
|
|
||||||
- A `FAQPage` schema block
|
|
||||||
- Statistics with cited sources (e.g., "QR code scans grew X% in 2025 — [source]")
|
|
||||||
- `dateModified` updated to current month
|
|
||||||
|
|
||||||
This post alone, done properly, is the highest-value AI-citation target on the whole site.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Section 5 — AI SEO quick fixes (1–2 hours)
|
|
||||||
|
|
||||||
These are code-level changes, not content work.
|
|
||||||
|
|
||||||
### 5.1 — Add cited sources to every statistic in blog posts
|
|
||||||
|
|
||||||
**File:** `src/lib/blog-data.ts`
|
|
||||||
|
|
||||||
Every blog post `content` field that contains a statistic without a source link needs one. Example fix in the restaurant post:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Before -->
|
|
||||||
<p>over 60% of restaurants that adopted QR menus during 2020–2021 kept them afterward</p>
|
|
||||||
|
|
||||||
<!-- After -->
|
|
||||||
<p>over 60% of restaurants that adopted QR menus during 2020–2021 kept them afterward
|
|
||||||
<a href="https://nationalrestaurantassociation.org/..." target="_blank" rel="noopener">[NRA, 2022]</a></p>
|
|
||||||
```
|
|
||||||
|
|
||||||
Per Princeton GEO research: adding cited sources increases AI citation rate by +40%. This is the single highest-ROI AI SEO action available.
|
|
||||||
|
|
||||||
### 5.2 — Add definition blocks to tool pages
|
|
||||||
|
|
||||||
**Which pages:** barcode generator, WiFi QR generator, Teams QR generator, URL QR generator
|
|
||||||
**What to add:** A one-paragraph definition in the first `<p>` tag that directly answers "What is a [X] generator?" in 40–60 words. AI systems extract from the opening paragraph first.
|
|
||||||
|
|
||||||
Example for barcode generator:
|
|
||||||
```html
|
|
||||||
<p>A barcode generator creates machine-readable linear barcodes (EAN-13, UPC-A, Code 128)
|
|
||||||
from numeric or alphanumeric data. Unlike QR codes, barcodes store data in parallel lines
|
|
||||||
and are scanned by dedicated readers. Use this free tool to generate printable barcodes
|
|
||||||
for retail products, inventory labels, or shipping.</p>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.3 — Add `dateModified` display to blog post pages
|
|
||||||
|
|
||||||
**File:** whichever component renders the blog post header — search for `publishDate` or `datePublished` usage in `src/app/(main)/(marketing)/blog/`
|
|
||||||
|
|
||||||
AI tools penalise undated content. Every post already has `dateModified` in `blog-data.ts` — it just needs to render visibly on the page. Add "Last updated: [date]" next to the author line. It's one line of JSX.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Section 6 — Pricing page (1 hour)
|
|
||||||
|
|
||||||
**File:** `src/i18n/en.json`, pricing section + the Pricing component
|
|
||||||
|
|
||||||
### 6.1 — Add upgrade trigger context to the Pro plan
|
|
||||||
|
|
||||||
Under the Pro plan title, add a single explanatory sentence that explains *when* someone should upgrade:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"pro": {
|
|
||||||
"trigger": "When you hit 3 dynamic QR codes or need scan analytics"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Render this in the Pricing component under the plan title, above the price.
|
|
||||||
|
|
||||||
### 6.2 — Free plan: explain the limit as a feature, not a restriction
|
|
||||||
|
|
||||||
Current: `"3 active dynamic QR codes (8 types available)"`
|
|
||||||
Rewrite: `"3 dynamic QR codes — enough to start. Unlimited static codes."`
|
|
||||||
|
|
||||||
The current copy leads with the limit. Lead with what they get.
|
|
||||||
|
|
||||||
### 6.3 — Annual pricing
|
|
||||||
|
|
||||||
There's no annual discount mentioned anywhere in the pricing UI. If Stripe supports annual billing, surface it. A toggle (Monthly / Annual — save 20%) on the pricing page typically lifts plan revenue by 15–30% by locking in longer commitments. Check `src/lib/stripe.ts` for whether annual price IDs exist.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Section 7 — Dashboard first-run experience (2–3 hours)
|
|
||||||
|
|
||||||
### 7.1 — Empty state when user has 0 QR codes
|
|
||||||
|
|
||||||
**File:** `src/app/(main)/(app)/dashboard/page.tsx`
|
|
||||||
|
|
||||||
Currently, a new user lands on a dashboard with stats showing all zeros and an empty grid. There's a loading skeleton for QR codes but no empty state when `qrCodes.length === 0`.
|
|
||||||
|
|
||||||
Add an empty state component:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
{!loading && qrCodes.length === 0 && (
|
|
||||||
<div className="text-center py-16 border-2 border-dashed border-gray-200 rounded-xl">
|
|
||||||
<QrCode className="w-12 h-12 text-gray-300 mx-auto mb-4" />
|
|
||||||
<h3 className="text-lg font-semibold text-gray-700 mb-2">Create your first QR code</h3>
|
|
||||||
<p className="text-gray-500 mb-6 max-w-sm mx-auto">
|
|
||||||
You have 3 free dynamic QR codes. They redirect wherever you want and track every scan.
|
|
||||||
</p>
|
|
||||||
<Link href="/create">
|
|
||||||
<Button>Create QR Code — it takes 90 seconds</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.2 — Remove the mock QR codes from the dashboard code
|
|
||||||
|
|
||||||
**File:** `src/app/(main)/(app)/dashboard/page.tsx`, lines ~45–100
|
|
||||||
|
|
||||||
There's a `mockQRCodes` array defined but it doesn't appear to be rendered (the real fetch path is used). Verify and remove the dead code to keep the file clean.
|
|
||||||
|
|
||||||
### 7.3 — Dashboard subtitle should be contextual
|
|
||||||
|
|
||||||
**File:** `src/app/(main)/(app)/dashboard/page.tsx`
|
|
||||||
|
|
||||||
Current subtitle: `{t('dashboard.subtitle')}` → "Manage and track your QR codes"
|
|
||||||
|
|
||||||
For users with 0 QR codes, show: *"Start here — create your first QR code in under 2 minutes"*
|
|
||||||
For users with QR codes, show the current text.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<p className="text-gray-600 mt-2">
|
|
||||||
{qrCodes.length === 0
|
|
||||||
? 'Start here — create your first QR code in under 2 minutes'
|
|
||||||
: t('dashboard.subtitle')}
|
|
||||||
</p>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Metrics to watch
|
|
||||||
|
|
||||||
| Thing changed | Metric to check | Where |
|
|
||||||
|---|---|---|
|
|
||||||
| Welcome email | Cohort retention week 1 | PostHog → Retention |
|
|
||||||
| Signup form (Google first, remove confirm PW) | Signup completion rate | PostHog → Funnels |
|
|
||||||
| Cancel flow | Cancelled users / month | Stripe dashboard |
|
|
||||||
| Hero headline | Bounce rate on `/` | PostHog → Paths |
|
|
||||||
| Barcode + Teams blog posts | GSC impressions & position | Search Console |
|
|
||||||
| Cited statistics in posts | AI citations/day | AI perf CSV |
|
|
||||||
| Empty state dashboard | `/create` conversion from dashboard | PostHog → Paths |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What not to do right now
|
|
||||||
|
|
||||||
- Don't build new QR code types — the product breadth is already sufficient for the current user base
|
|
||||||
- Don't chase Google Ads or paid social — organic signals are still too early to know which pages convert, and CAC will be high with a 10% retention rate
|
|
||||||
- Don't redesign the homepage visually — copy and CRO changes will outperform a design refresh at this stage
|
|
||||||
- Don't add more blog posts until the existing ones have cited sources and proper date signals (Sections 4.4 and 5.1)
|
|
||||||
1148
backlinks.md
1218
blog-posts-seo-guide.md
Normal file
@@ -1,83 +0,0 @@
|
|||||||
📅 Blog Content Roadmap (Q1 2026)
|
|
||||||
Goal: Publish 20 high-quality SEO posts over 60 days (Jan 29 - Mar 27). Cadence: Every 3 days. Strategy: "Strict 404 Gate" (Future posts are invisible/404 until publish date).
|
|
||||||
|
|
||||||
✅ Completed (Ready to Ship)
|
|
||||||
Jan 29:
|
|
||||||
Free Barcode Generator (Online)
|
|
||||||
Status: 🟢 Ready (Content Complete + SEO Optimized + Hero Image Generated).
|
|
||||||
Key Feature: Quick Answer Box, SVG/PNG Comparison, FAQ.
|
|
||||||
🚀 Next Priority: Feb 01
|
|
||||||
🎵 Spotify Code Generator: Share Music Instantly
|
|
||||||
Target Audience: Artists, bands, podcasters, playlist curators. SEO Focus: spotify code generator, create spotify code, music marketing qr, spotify uri to code.
|
|
||||||
|
|
||||||
Drafting Blueprint:
|
|
||||||
|
|
||||||
H1: Spotify Code Generator: Share Songs, Albums & Playlists
|
|
||||||
Quick Answer: How to get a code (3-step process).
|
|
||||||
Visual Guide: Where to find the "Spotify URI".
|
|
||||||
Use Cases:
|
|
||||||
Merch: T-shirts with album link.
|
|
||||||
Posters: Gig promotion.
|
|
||||||
Socials: Instagram Stories overlay.
|
|
||||||
Critical Comparison (Pro Tip):
|
|
||||||
Spotify Codes = Cool look, but NO analytics.
|
|
||||||
Dynamic QR Codes = Less "native" look, but FULL tracking (scans, location, etc.).
|
|
||||||
Recommendation: Use QR for marketing campaigns where ROI matters; use Spotify Codes for pure branding on merch.
|
|
||||||
FAQ: Vector download? Do they expire? High-res printing?
|
|
||||||
CTA: "Generate Music QR Code" (Link to main generator).
|
|
||||||
Image Concept:
|
|
||||||
|
|
||||||
Style: Neon, vibrant, "Spotify Green" accents, dark mode aesthetic.
|
|
||||||
Subject: A stylized soundwave transforming into a scannable code, or a vinyl record with a code center.
|
|
||||||
📋 Upcoming Schedule (Backlog)
|
|
||||||
Publish Date Topic / Slug Category Status
|
|
||||||
Feb 04 WhatsApp QR Code (Direct Chat Link) Social ⚪ Pending
|
|
||||||
Feb 07 Instagram QR Code (Grow Following) Social ⚪ Pending
|
|
||||||
Feb 10 vCard QR Code (Digital Business Card) Business ⚪ Pending
|
|
||||||
Feb 13 QR Code Analytics Guide (Deep Dive) Analytics ⚪ Pending
|
|
||||||
Feb 16 Trackable QR Codes (How-to) Tracking ⚪ Pending
|
|
||||||
Feb 19 Dynamic vs Static QR (Ultimate Guide) Basics ⚪ Pending
|
|
||||||
Feb 22 UTM Tracking with QR Codes Marketing ⚪ Pending
|
|
||||||
Feb 25 QR Code Statistics 2026 Trends ⚪ Pending
|
|
||||||
Feb 28 Restaurant Menu QR Codes Hospitality ⚪ Pending
|
|
||||||
Mar 03 QR Codes for Events Events ⚪ Pending
|
|
||||||
Mar 06 Business Card QR Codes Business ⚪ Pending
|
|
||||||
Mar 09 Marketing Strategy Examples Marketing ⚪ Pending
|
|
||||||
Mar 12 Bulk QR Code Generator (Excel/CSV) Bulk ⚪ Pending
|
|
||||||
Mar 15 Google QR Alternative Comparison ⚪ Pending
|
|
||||||
Mar 18 Security & Quishing Security ⚪ Pending
|
|
||||||
Mar 21 Best QR Generator 2026 Review Reviews ⚪ Pending
|
|
||||||
Mar 24 QR Code API Documentation Developer ⚪ Pending
|
|
||||||
Mar 27 Free vs Paid Generator Comparison ⚪ Pending
|
|
||||||
🛠️ Execution Workflow (Repeat for each post)
|
|
||||||
Select Topic: Take next item from list.
|
|
||||||
SEO & Outline: Define title, keywords, and H2 structure (use User/Expert persona).
|
|
||||||
Implement: Replace placeholder content in
|
|
||||||
src/lib/blog-data.ts
|
|
||||||
.
|
|
||||||
Asset: Generate hero image (public/blog/[slug].png) via DALL-E.
|
|
||||||
Verify: Ensure no build errors and correct 404 behavior if date > now.
|
|
||||||
✅ SEO Validation Checklist (Target Score: 80+)
|
|
||||||
1. Page Title
|
|
||||||
|
|
||||||
Focus keyword used at the beginning.
|
|
||||||
Length: ~60 characters (0 characters available is perfect).
|
|
||||||
2. Meta Description
|
|
||||||
|
|
||||||
Focus keyword included.
|
|
||||||
Length: ~160 characters.
|
|
||||||
3. Content Structure
|
|
||||||
|
|
||||||
H1 contains focus keyword.
|
|
||||||
First Paragraph contains focus keyword.
|
|
||||||
Word count: > 600 words (Aim for comprehensive coverage).
|
|
||||||
Keyword Density: ~2% (e.g., used 4-5 times in 600 words).
|
|
||||||
4. Assets (Images)
|
|
||||||
|
|
||||||
Image Filename contains focus keyword (e.g.,
|
|
||||||
free-barcode-generator-guide.png
|
|
||||||
).
|
|
||||||
Image Alt Tag contains focus keyword.
|
|
||||||
5. Links
|
|
||||||
|
|
||||||
Add relevant internal links to tools/pricing/other posts.
|
|
||||||
|
Before Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 95 KiB |
134
claude-artifact-template.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Claude Artifact Template for QR Master Backlinks
|
||||||
|
|
||||||
|
Use this template when creating Claude artifacts that link back to qrmaster.net.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
1. Copy this template into Claude
|
||||||
|
2. Customize for your specific topic
|
||||||
|
3. Click "Publish Artifact"
|
||||||
|
4. Add `www.qrmaster.net` to allowed domains
|
||||||
|
5. Share the published link
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template: Dynamic QR Codes Guide
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Dynamic QR Codes: The Complete 2025 Guide</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px 20px;
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
h1 { color: #0f172a; border-bottom: 3px solid #3b82f6; padding-bottom: 10px; }
|
||||||
|
h2 { color: #1e40af; margin-top: 2em; }
|
||||||
|
a { color: #2563eb; text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
.cta {
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||||
|
color: white;
|
||||||
|
padding: 20px 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 30px 0;
|
||||||
|
}
|
||||||
|
.cta a { color: white; font-weight: bold; font-size: 1.1em; }
|
||||||
|
ul { margin: 1em 0; }
|
||||||
|
li { margin: 0.5em 0; }
|
||||||
|
.highlight { background: #dbeafe; padding: 15px; border-radius: 8px; margin: 20px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Dynamic QR Codes: The Complete 2025 Guide</h1>
|
||||||
|
|
||||||
|
<p>Dynamic QR codes have revolutionized how businesses connect offline and online experiences. Unlike static codes, dynamic QR codes can be edited after printing, tracked in real-time, and optimized based on performance data.</p>
|
||||||
|
|
||||||
|
<h2>What Makes Dynamic QR Codes Different?</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Editable:</strong> Change the destination URL anytime without reprinting</li>
|
||||||
|
<li><strong>Trackable:</strong> Monitor scans, locations, devices, and timing</li>
|
||||||
|
<li><strong>Smaller:</strong> Short redirect URLs create cleaner QR patterns</li>
|
||||||
|
<li><strong>Flexible:</strong> Perfect for campaigns where content may change</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="highlight">
|
||||||
|
<strong>Pro Tip:</strong> Always use dynamic QR codes for printed materials. If your URL changes after printing 10,000 flyers, you won't need to reprint them.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Best Use Cases</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Restaurant menus that change seasonally</li>
|
||||||
|
<li>Event tickets with real-time updates</li>
|
||||||
|
<li>Product packaging with warranty info</li>
|
||||||
|
<li>Marketing campaigns with A/B testing</li>
|
||||||
|
<li>Business cards with contact details</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>How to Create Dynamic QR Codes</h2>
|
||||||
|
<p>The easiest way is using a dedicated platform like <a href="https://www.qrmaster.net" target="_blank">QR Master</a>. Here's the process:</p>
|
||||||
|
<ol>
|
||||||
|
<li>Sign up for a free account</li>
|
||||||
|
<li>Click "Create QR Code" and select "Dynamic"</li>
|
||||||
|
<li>Enter your destination URL</li>
|
||||||
|
<li>Customize colors and add your logo</li>
|
||||||
|
<li>Download in SVG or PNG format</li>
|
||||||
|
<li>Track scans in your analytics dashboard</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="cta">
|
||||||
|
<a href="https://www.qrmaster.net/signup" target="_blank">Create Your Free Dynamic QR Code →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Tracking & Analytics</h2>
|
||||||
|
<p>With dynamic QR codes, you get access to powerful analytics:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Total and unique scan counts</li>
|
||||||
|
<li>Geographic distribution (city/country)</li>
|
||||||
|
<li>Device breakdown (iOS vs Android)</li>
|
||||||
|
<li>Time-based patterns (peak hours)</li>
|
||||||
|
<li>Conversion tracking integration</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Learn more about <a href="https://www.qrmaster.net/qr-code-tracking" target="_blank">QR code tracking</a> and <a href="https://www.qrmaster.net/blog/qr-code-analytics" target="_blank">analytics best practices</a>.</p>
|
||||||
|
|
||||||
|
<h2>Pricing</h2>
|
||||||
|
<p>Most platforms offer tiered pricing. <a href="https://www.qrmaster.net/pricing" target="_blank">QR Master pricing</a> starts with a free tier (3 dynamic codes) and scales up for businesses needing more codes and features.</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<p><em>This guide is provided by <a href="https://www.qrmaster.net" target="_blank">QR Master</a> - Free Dynamic QR Code Generator with Analytics.</em></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Topic Ideas for More Artifacts
|
||||||
|
|
||||||
|
1. **"QR Codes for Restaurants: Complete Setup Guide"** - Link to /blog/qr-code-restaurant-menu
|
||||||
|
2. **"Digital Business Cards with QR Codes"** - Link to /blog/vcard-qr-code-generator
|
||||||
|
3. **"QR Code Print Size Calculator"** - Link to /blog/qr-code-print-size-guide
|
||||||
|
4. **"Small Business QR Code Marketing Playbook"** - Link to /blog/qr-code-small-business
|
||||||
|
5. **"Static vs Dynamic QR Codes Comparison"** - Link to /blog/dynamic-vs-static-qr-codes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Publishing Steps
|
||||||
|
|
||||||
|
1. Create artifact in Claude with HTML content above
|
||||||
|
2. Click "Publish" button
|
||||||
|
3. In "Allowed domains" field, enter: `www.qrmaster.net, qrmaster.net`
|
||||||
|
4. Copy the embed code or share URL
|
||||||
|
5. The links inside will count as backlinks once indexed
|
||||||
180
claude-seo-prompts.md
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# Claude Artifact Prompts for Parasite SEO
|
||||||
|
|
||||||
|
**Goal:** Publish educational content on claude.ai that naturally links to qrmaster.net
|
||||||
|
**Strategy:** Informative, helpful content that does NOT look like advertising
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Prompt 1: Restaurant QR Menu Guide
|
||||||
|
|
||||||
|
```
|
||||||
|
Create an informative HTML article titled "Restaurant Menu QR Codes: A Practical Guide for Restaurant Owners (2025)"
|
||||||
|
|
||||||
|
The article should:
|
||||||
|
- Be 1500-2000 words long
|
||||||
|
- Contain practical tips for restaurant owners
|
||||||
|
- Cover the following topics:
|
||||||
|
1. Why digital menus are the new standard
|
||||||
|
2. PDF vs. online menu - pros and cons
|
||||||
|
3. Optimal placement of QR codes in restaurants
|
||||||
|
4. Mistakes restaurants should avoid
|
||||||
|
5. Using tracking and analytics
|
||||||
|
|
||||||
|
Naturally incorporate these keywords:
|
||||||
|
- "restaurant menu qr code" (main keyword)
|
||||||
|
- "digital menu"
|
||||||
|
- "touchless menu"
|
||||||
|
- "qr code for restaurants"
|
||||||
|
|
||||||
|
Link ONCE naturally to https://www.qrmaster.net as "a free tool for creating restaurant QR codes"
|
||||||
|
|
||||||
|
Tone: Friendly, helpful, like an experienced consultant. NOT salesy.
|
||||||
|
|
||||||
|
HTML with clean CSS, mobile-friendly. No external dependencies.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Prompt 2: Dynamic vs Static QR Comparison
|
||||||
|
|
||||||
|
```
|
||||||
|
Create a technical comparison article as HTML: "Static vs Dynamic QR Codes: What's the Difference and When to Use Each"
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
1. Brief explanation of what QR codes are technically
|
||||||
|
2. Static QR codes - how they work
|
||||||
|
3. Dynamic QR codes - how they work
|
||||||
|
4. Comparison table (very important!)
|
||||||
|
5. Decision guide: When to use which type
|
||||||
|
6. Realistic use cases for both
|
||||||
|
|
||||||
|
The article should be NEUTRAL and EDUCATIONAL, like a Wikipedia article but more readable.
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
- "dynamic vs static qr code" (main keyword)
|
||||||
|
- "editable qr code"
|
||||||
|
- "trackable qr code"
|
||||||
|
- "qr code types"
|
||||||
|
|
||||||
|
At the end, include a short "Tools for Creating QR Codes" section with 3-4 options. One of them is https://www.qrmaster.net - listed as an equal entry, NOT as a recommendation.
|
||||||
|
|
||||||
|
HTML with professional, minimalist design.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Prompt 3: Small Business Marketing Guide
|
||||||
|
|
||||||
|
```
|
||||||
|
Create a comprehensive HTML guide: "10 Ways Small Businesses Can Use QR Codes in 2025"
|
||||||
|
|
||||||
|
The article is aimed at small businesses without technical knowledge.
|
||||||
|
|
||||||
|
The 10 use cases:
|
||||||
|
1. Digital business cards (vCard)
|
||||||
|
2. Collecting Google reviews
|
||||||
|
3. Contactless payments
|
||||||
|
4. Sharing Wi-Fi access
|
||||||
|
5. Growing social media followers
|
||||||
|
6. Linking product information
|
||||||
|
7. Simplifying appointment booking
|
||||||
|
8. Discount promotions & coupons
|
||||||
|
9. Event tickets & check-in
|
||||||
|
10. Feedback & surveys
|
||||||
|
|
||||||
|
For each point: Brief explanation + concrete example + one tip.
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
- "qr code for small business"
|
||||||
|
- "qr code marketing"
|
||||||
|
- "qr code uses"
|
||||||
|
- "business qr codes"
|
||||||
|
|
||||||
|
Link ONCE naturally in the context of vCard creation to https://www.qrmaster.net/blog/vcard-qr-code-generator
|
||||||
|
|
||||||
|
Tone: Enthusiastic but not over the top. Like a helpful friend explaining technology.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Prompt 4: Print Size Technical Guide
|
||||||
|
|
||||||
|
```
|
||||||
|
Create a technical reference article as HTML: "QR Code Print Size Guide: Minimum Dimensions for Reliable Scanning"
|
||||||
|
|
||||||
|
This article should become THE reference for QR code print sizes.
|
||||||
|
|
||||||
|
Content:
|
||||||
|
1. The science behind QR scanning (brief)
|
||||||
|
2. The golden formula: Size = Distance ÷ 10
|
||||||
|
3. LARGE table with applications, distances, min/recommended sizes
|
||||||
|
4. Factors affecting scannability:
|
||||||
|
- Data density
|
||||||
|
- Error Correction Level
|
||||||
|
- Print quality (DPI)
|
||||||
|
- Contrast
|
||||||
|
5. Quiet zone requirements
|
||||||
|
6. File formats for printing (SVG vs PNG vs PDF)
|
||||||
|
7. Checklist before printing
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
- "qr code size for printing"
|
||||||
|
- "minimum qr code size"
|
||||||
|
- "qr code dimensions"
|
||||||
|
- "qr code print quality"
|
||||||
|
|
||||||
|
Link ONCE to https://www.qrmaster.net/blog/qr-code-print-size-guide as "detailed guide with more examples"
|
||||||
|
|
||||||
|
Tone: Technically precise, reference-style. For designers and marketers.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Prompt 5: QR Analytics Beginner Guide
|
||||||
|
|
||||||
|
```
|
||||||
|
Create a beginner's guide as HTML: "QR Code Analytics Explained: What You Can Track and Why It Matters"
|
||||||
|
|
||||||
|
The article is aimed at marketing beginners who have never used QR tracking before.
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
1. What is QR tracking and why is it important?
|
||||||
|
2. What data can you track? (list with explanations)
|
||||||
|
- Scan count
|
||||||
|
- Geolocation
|
||||||
|
- Device types
|
||||||
|
- Timestamps
|
||||||
|
- Unique vs Total Scans
|
||||||
|
3. How does it work technically? (simplified)
|
||||||
|
4. Privacy & GDPR considerations
|
||||||
|
5. Practical application: Measuring campaign ROI
|
||||||
|
6. Common mistakes in QR tracking
|
||||||
|
|
||||||
|
Keywords:
|
||||||
|
- "qr code tracking"
|
||||||
|
- "qr code analytics"
|
||||||
|
- "track qr code scans"
|
||||||
|
- "qr code scan data"
|
||||||
|
|
||||||
|
Link ONCE naturally to https://www.qrmaster.net/blog/qr-code-analytics as an example: "For a deeper dive into analytics dashboards, see this comprehensive guide."
|
||||||
|
|
||||||
|
Tone: Friendly and explanatory, like a teacher. No jargon without explanation.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Usage Instructions
|
||||||
|
|
||||||
|
1. **Copy prompt** → Paste into claude.ai
|
||||||
|
2. **Let it create the artifact**
|
||||||
|
3. **Click "Publish"** in Claude
|
||||||
|
4. **Allowed Domain:** Add `www.qrmaster.net, qrmaster.net`
|
||||||
|
5. **Share link** - Google indexes these!
|
||||||
|
|
||||||
|
## 💡 Tips for Maximum Effectiveness
|
||||||
|
|
||||||
|
- **Don't publish all on the same day**
|
||||||
|
- About **1 article per week** for natural growth
|
||||||
|
- Publish the **more neutral articles first** (Prompt 2 & 4)
|
||||||
|
- **Share on social media** for faster indexing
|
||||||
|
- Register the published URLs in Google Search Console
|
||||||
@@ -1,464 +0,0 @@
|
|||||||
Here is Claude's plan:
|
|
||||||
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
|
|
||||||
Restaurant Menu QR Code - Dashboard Integration Plan
|
|
||||||
|
|
||||||
Executive Summary
|
|
||||||
|
|
||||||
Add "Restaurant Menu" as a new QR code content type in the dashboard at /create.
|
|
||||||
Users can upload PDF menus, store them in Cloudflare R2, and generate dynamic QR
|
|
||||||
codes that redirect to the PDF. This integrates with the existing QRCode model and
|
|
||||||
tracking system.
|
|
||||||
|
|
||||||
Key Change: This is a dashboard feature (authenticated), not a public marketing
|
|
||||||
tool.
|
|
||||||
|
|
||||||
Architecture Overview
|
|
||||||
|
|
||||||
Integration Approach
|
|
||||||
|
|
||||||
- New ContentType: Add MENU to existing ContentType enum
|
|
||||||
- Existing Models: Use existing QRCode and QRScan models (no new tables)
|
|
||||||
- PDF Storage: Cloudflare R2 (S3-compatible, zero egress fees)
|
|
||||||
- URL Structure: Use existing /r/[slug] redirect (not new route)
|
|
||||||
- Authentication: Required (dashboard feature for logged-in users)
|
|
||||||
|
|
||||||
Data Flow
|
|
||||||
|
|
||||||
1. User logs in → Goes to /create → Selects "Restaurant Menu" type
|
|
||||||
2. Uploads PDF → Validate → Upload to R2 → Get public URL
|
|
||||||
3. Creates QR code with content: { pdfUrl: "...", restaurantName: "...", menuTitle:
|
|
||||||
"..." }
|
|
||||||
4. QR code redirects to: /r/[slug] → Redirect to PDF URL
|
|
||||||
5. Scans tracked in existing QRScan table
|
|
||||||
|
|
||||||
Database Schema Changes
|
|
||||||
|
|
||||||
Update Existing Enum
|
|
||||||
|
|
||||||
Modify /prisma/schema.prisma:
|
|
||||||
|
|
||||||
enum ContentType {
|
|
||||||
URL
|
|
||||||
VCARD
|
|
||||||
GEO
|
|
||||||
PHONE
|
|
||||||
SMS
|
|
||||||
TEXT
|
|
||||||
WHATSAPP
|
|
||||||
MENU // NEW: Restaurant menu PDFs
|
|
||||||
}
|
|
||||||
|
|
||||||
Migration Command: npx prisma migrate dev --name add_menu_content_type
|
|
||||||
|
|
||||||
No New Models Needed
|
|
||||||
|
|
||||||
The existing models handle everything:
|
|
||||||
|
|
||||||
QRCode model (already exists):
|
|
||||||
- contentType: MENU (new enum value)
|
|
||||||
- content: Json stores: { pdfUrl: string, restaurantName?: string, menuTitle?:
|
|
||||||
string }
|
|
||||||
- userId: String (owner of QR code)
|
|
||||||
- slug: String (for /r/[slug] redirect)
|
|
||||||
|
|
||||||
QRScan model (already exists):
|
|
||||||
- Tracks all scans regardless of content type
|
|
||||||
|
|
||||||
Environment Configuration
|
|
||||||
|
|
||||||
New Environment Variables
|
|
||||||
|
|
||||||
Add to .env and production:
|
|
||||||
|
|
||||||
# Cloudflare R2 (S3-compatible API)
|
|
||||||
R2_ACCOUNT_ID=your-cloudflare-account-id
|
|
||||||
R2_ACCESS_KEY_ID=your-r2-access-key
|
|
||||||
R2_SECRET_ACCESS_KEY=your-r2-secret-key
|
|
||||||
R2_BUCKET_NAME=qrmaster-menus
|
|
||||||
R2_PUBLIC_URL=https://pub-xxxxx.r2.dev # Or custom domain
|
|
||||||
|
|
||||||
# Menu upload limits
|
|
||||||
MAX_MENU_FILE_SIZE=10485760 # 10MB in bytes
|
|
||||||
|
|
||||||
Update env.ts
|
|
||||||
|
|
||||||
Add to /src/lib/env.ts schema:
|
|
||||||
|
|
||||||
const envSchema = z.object({
|
|
||||||
// ... existing fields ...
|
|
||||||
R2_ACCOUNT_ID: z.string().optional(),
|
|
||||||
R2_ACCESS_KEY_ID: z.string().optional(),
|
|
||||||
R2_SECRET_ACCESS_KEY: z.string().optional(),
|
|
||||||
R2_BUCKET_NAME: z.string().default('qrmaster-menus'),
|
|
||||||
R2_PUBLIC_URL: z.string().optional(),
|
|
||||||
MAX_MENU_FILE_SIZE: z.string().default('10485760'),
|
|
||||||
});
|
|
||||||
|
|
||||||
Critical Files to Modify/Create
|
|
||||||
|
|
||||||
1. R2 Client Library
|
|
||||||
|
|
||||||
File: /src/lib/r2.ts (NEW)
|
|
||||||
|
|
||||||
Purpose: Handle PDF uploads to Cloudflare R2
|
|
||||||
|
|
||||||
import { S3Client, PutObjectCommand, DeleteObjectCommand } from
|
|
||||||
'@aws-sdk/client-s3';
|
|
||||||
import { env } from './env';
|
|
||||||
|
|
||||||
const r2Client = new S3Client({
|
|
||||||
region: 'auto',
|
|
||||||
endpoint: `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: env.R2_ACCESS_KEY_ID!,
|
|
||||||
secretAccessKey: env.R2_SECRET_ACCESS_KEY!,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function uploadMenuToR2(
|
|
||||||
file: Buffer,
|
|
||||||
filename: string,
|
|
||||||
shortId: string
|
|
||||||
): Promise<string> {
|
|
||||||
const key = `menus/${shortId}.pdf`;
|
|
||||||
|
|
||||||
await r2Client.send(
|
|
||||||
new PutObjectCommand({
|
|
||||||
Bucket: env.R2_BUCKET_NAME,
|
|
||||||
Key: key,
|
|
||||||
Body: file,
|
|
||||||
ContentType: 'application/pdf',
|
|
||||||
ContentDisposition: `inline; filename="${filename}"`,
|
|
||||||
CacheControl: 'public, max-age=31536000',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return `${env.R2_PUBLIC_URL}/${key}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteMenuFromR2(r2Key: string): Promise<void> {
|
|
||||||
await r2Client.send(
|
|
||||||
new DeleteObjectCommand({
|
|
||||||
Bucket: env.R2_BUCKET_NAME,
|
|
||||||
Key: r2Key,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateUniqueFilename(originalFilename: string): string {
|
|
||||||
const timestamp = Date.now();
|
|
||||||
const random = crypto.randomBytes(4).toString('hex');
|
|
||||||
const ext = originalFilename.split('.').pop();
|
|
||||||
return `menu_${timestamp}_${random}.${ext}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
2. Upload API Endpoint
|
|
||||||
|
|
||||||
File: /src/app/api/menu/upload/route.ts (NEW)
|
|
||||||
|
|
||||||
Purpose: Handle PDF uploads from the create page
|
|
||||||
|
|
||||||
Responsibilities:
|
|
||||||
- Accept multipart/form-data PDF upload
|
|
||||||
- Validate file type (PDF magic bytes), size (max 10MB)
|
|
||||||
- Rate limit: 10 uploads per minute per user (authenticated)
|
|
||||||
- Upload to R2 with unique filename
|
|
||||||
- Return R2 public URL
|
|
||||||
|
|
||||||
Request: FormData { file: File }
|
|
||||||
|
|
||||||
Response:
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"pdfUrl": "https://pub-xxxxx.r2.dev/menus/menu_1234567890_abcd.pdf"
|
|
||||||
}
|
|
||||||
|
|
||||||
Key Implementation Details:
|
|
||||||
- Use request.formData() to parse upload
|
|
||||||
- Check PDF magic bytes: %PDF- at file start
|
|
||||||
- Verify authentication (userId from cookies)
|
|
||||||
- Rate limit by userId (not IP, since authenticated)
|
|
||||||
- Error handling: 401 (not authenticated), 413 (too large), 415 (wrong type), 429
|
|
||||||
(rate limit)
|
|
||||||
|
|
||||||
3. Update Redirect Route
|
|
||||||
|
|
||||||
File: /src/app/r/[slug]/route.ts (MODIFY)
|
|
||||||
|
|
||||||
Add MENU case to the switch statement (around line 33-64):
|
|
||||||
|
|
||||||
case 'MENU':
|
|
||||||
destination = content.pdfUrl || 'https://example.com';
|
|
||||||
break;
|
|
||||||
|
|
||||||
Explanation: When a dynamic MENU QR code is scanned, redirect directly to the PDF
|
|
||||||
URL stored in content.pdfUrl
|
|
||||||
|
|
||||||
4. Update Validation Schema
|
|
||||||
|
|
||||||
File: /src/lib/validationSchemas.ts (MODIFY)
|
|
||||||
|
|
||||||
Line 28: Update contentType enum to include MENU:
|
|
||||||
|
|
||||||
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT',
|
|
||||||
'MENU'], {
|
|
||||||
errorMap: () => ({ message: 'Invalid content type' })
|
|
||||||
}),
|
|
||||||
|
|
||||||
Line 63: Update bulk QR schema as well:
|
|
||||||
|
|
||||||
contentType: z.enum(['URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'WHATSAPP', 'TEXT',
|
|
||||||
'MENU']),
|
|
||||||
|
|
||||||
5. Update Create Page - Add MENU Type
|
|
||||||
|
|
||||||
File: /src/app/(app)/create/page.tsx (MODIFY)
|
|
||||||
|
|
||||||
Multiple changes needed:
|
|
||||||
|
|
||||||
A. Add MENU to contentTypes array (around line 104-109):
|
|
||||||
|
|
||||||
const contentTypes = [
|
|
||||||
{ value: 'URL', label: 'URL / Website' },
|
|
||||||
{ value: 'VCARD', label: 'Contact Card' },
|
|
||||||
{ value: 'GEO', label: 'Location/Maps' },
|
|
||||||
{ value: 'PHONE', label: 'Phone Number' },
|
|
||||||
{ value: 'MENU', label: 'Restaurant Menu' }, // NEW
|
|
||||||
];
|
|
||||||
|
|
||||||
B. Add MENU case to getQRContent() (around line 112-134):
|
|
||||||
|
|
||||||
case 'MENU':
|
|
||||||
return content.pdfUrl || 'https://example.com/menu.pdf';
|
|
||||||
|
|
||||||
C. Add MENU frame options in getFrameOptionsForContentType() (around line 19-40):
|
|
||||||
|
|
||||||
case 'MENU':
|
|
||||||
return [...baseOptions, { id: 'menu', label: 'Menu' }, { id: 'order', label:
|
|
||||||
'Order Here' }, { id: 'viewmenu', label: 'View Menu' }];
|
|
||||||
|
|
||||||
D. Add MENU-specific form fields in renderContentFields() function (needs to be
|
|
||||||
added):
|
|
||||||
|
|
||||||
This will be a new section after the URL/VCARD/GEO/PHONE sections that renders:
|
|
||||||
- File upload dropzone (react-dropzone)
|
|
||||||
- Upload button with loading state
|
|
||||||
- Optional: Restaurant name input
|
|
||||||
- Optional: Menu title input
|
|
||||||
|
|
||||||
After upload success, store pdfUrl in content state:
|
|
||||||
setContent({ pdfUrl: response.pdfUrl, restaurantName: '', menuTitle: '' });
|
|
||||||
|
|
||||||
6. Update Rate Limiting
|
|
||||||
|
|
||||||
File: /src/lib/rateLimit.ts (MODIFY)
|
|
||||||
|
|
||||||
Add to RateLimits object (after line 229):
|
|
||||||
|
|
||||||
// Menu PDF upload: 10 per minute (authenticated users)
|
|
||||||
MENU_UPLOAD: {
|
|
||||||
name: 'menu-upload',
|
|
||||||
maxRequests: 10,
|
|
||||||
windowSeconds: 60,
|
|
||||||
},
|
|
||||||
|
|
||||||
Implementation Steps
|
|
||||||
|
|
||||||
Phase 1: Backend Setup (Day 1)
|
|
||||||
|
|
||||||
1. Install Dependencies
|
|
||||||
npm install @aws-sdk/client-s3 react-dropzone
|
|
||||||
2. Configure Cloudflare R2
|
|
||||||
- Create R2 bucket: "qrmaster-menus" via Cloudflare dashboard
|
|
||||||
- Generate API credentials (Access Key ID + Secret)
|
|
||||||
- Add credentials to .env and production environment
|
|
||||||
- Set bucket to public (for PDF access)
|
|
||||||
3. Database Migration
|
|
||||||
- Add MENU to ContentType enum in prisma/schema.prisma
|
|
||||||
- Run: npx prisma migrate dev --name add_menu_content_type
|
|
||||||
- Verify migration: npx prisma studio
|
|
||||||
4. Environment Configuration
|
|
||||||
- Update src/lib/env.ts with R2 variables
|
|
||||||
- Update src/lib/rateLimit.ts with MENU_UPLOAD config
|
|
||||||
5. Create R2 Client
|
|
||||||
- Create src/lib/r2.ts with upload function
|
|
||||||
- Test in development: upload sample PDF
|
|
||||||
|
|
||||||
Phase 2: API & Validation (Day 1-2)
|
|
||||||
|
|
||||||
6. Update Validation Schema (/src/lib/validationSchemas.ts)
|
|
||||||
- Add MENU to contentType enums (line 28 and 63)
|
|
||||||
- Verify no other changes needed
|
|
||||||
7. Create Upload API (/src/app/api/menu/upload/route.ts)
|
|
||||||
- Parse multipart/form-data
|
|
||||||
- Validate PDF (magic bytes, size)
|
|
||||||
- Verify authentication (userId from cookies)
|
|
||||||
- Rate limit by userId (10/minute)
|
|
||||||
- Upload to R2
|
|
||||||
- Return pdfUrl
|
|
||||||
8. Update Redirect Route (/src/app/r/[slug]/route.ts)
|
|
||||||
- Add MENU case to switch statement (line 33-64)
|
|
||||||
- Redirect to content.pdfUrl
|
|
||||||
|
|
||||||
Phase 3: Dashboard Integration (Day 2-3)
|
|
||||||
|
|
||||||
9. Update Create Page (/src/app/(app)/create/page.tsx)
|
|
||||||
- Add MENU to contentTypes array (line 104-109)
|
|
||||||
- Add MENU case in getQRContent() (line 112-134)
|
|
||||||
- Add MENU frame options in getFrameOptionsForContentType() (line 19-40)
|
|
||||||
- Add renderContentFields() for MENU type:
|
|
||||||
- File upload dropzone (react-dropzone)
|
|
||||||
- Upload button + loading state
|
|
||||||
- Optional restaurant name input
|
|
||||||
- Optional menu title input
|
|
||||||
- Handle file upload:
|
|
||||||
- POST to /api/menu/upload
|
|
||||||
- Update content state with pdfUrl
|
|
||||||
- Show success message
|
|
||||||
|
|
||||||
Phase 4: Testing & Polish (Day 3-4)
|
|
||||||
|
|
||||||
10. Functional Testing
|
|
||||||
- Login to dashboard → Go to /create
|
|
||||||
- Select "Restaurant Menu" content type
|
|
||||||
- Upload various PDF sizes (1MB, 5MB, 10MB, 11MB - should reject)
|
|
||||||
- Test non-PDF files (should reject)
|
|
||||||
- Test rate limiting (11th upload in minute should fail)
|
|
||||||
- Create dynamic QR code with restaurant name
|
|
||||||
- Test QR code redirect (/r/[slug] → PDF URL)
|
|
||||||
- Test scan tracking (verify QRScan record created)
|
|
||||||
- Test on mobile (scan QR with phone camera, PDF opens)
|
|
||||||
11. Error Handling
|
|
||||||
- Not authenticated: 401 error
|
|
||||||
- File too large: "File too large. Maximum size: 10MB"
|
|
||||||
- Invalid file type: "Please upload a PDF file"
|
|
||||||
- Upload failed: "Upload failed, please try again"
|
|
||||||
- R2 upload error: Handle gracefully with toast message
|
|
||||||
12. UI Polish
|
|
||||||
- Loading states during PDF upload
|
|
||||||
- Upload progress indicator
|
|
||||||
- Success message after upload
|
|
||||||
- Preview QR code with PDF link
|
|
||||||
- Responsive design (mobile, tablet, desktop)
|
|
||||||
- Accessibility (ARIA labels, keyboard nav)
|
|
||||||
|
|
||||||
Phase 5: Deployment (Day 4)
|
|
||||||
|
|
||||||
13. Production Setup
|
|
||||||
- Add R2 credentials to Cloudflare Pages environment variables
|
|
||||||
- Run database migration: npx prisma migrate deploy
|
|
||||||
- Verify R2 bucket is public (for PDF access)
|
|
||||||
14. Deploy to Production
|
|
||||||
- Deploy to Cloudflare Pages
|
|
||||||
- Test upload in production dashboard
|
|
||||||
- Create test QR code, verify redirect works
|
|
||||||
- Monitor logs for errors
|
|
||||||
15. Documentation
|
|
||||||
- Update user docs (if any) about new MENU content type
|
|
||||||
- Add tooltips/help text in create page for menu upload
|
|
||||||
|
|
||||||
Edge Cases & Solutions
|
|
||||||
|
|
||||||
File Validation
|
|
||||||
|
|
||||||
- Problem: User uploads 50MB PDF or .exe file
|
|
||||||
- Solution:
|
|
||||||
- Client-side validation (check file.size and file.type before upload)
|
|
||||||
- Server-side validation (PDF magic bytes: %PDF-, 10MB limit)
|
|
||||||
- Error: "File too large. Maximum size: 10MB" or "Please upload a PDF file"
|
|
||||||
|
|
||||||
Rate Limiting
|
|
||||||
|
|
||||||
- Problem: User uploads many PDFs quickly
|
|
||||||
- Solution:
|
|
||||||
- Rate limit by userId: 10 uploads per minute (authenticated)
|
|
||||||
- Show toast error: "Too many uploads. Please wait a moment."
|
|
||||||
- More generous than anonymous (since authenticated)
|
|
||||||
|
|
||||||
PDF Deletion/Management
|
|
||||||
|
|
||||||
- Problem: User deletes QR code, but PDF stays in R2
|
|
||||||
- Solution (Phase 1): Leave PDFs in R2 (simple, safe)
|
|
||||||
- Future Enhancement: Add cleanup job to delete unused PDFs
|
|
||||||
- Check QRCode records, delete orphaned R2 files
|
|
||||||
- Run monthly via cron job
|
|
||||||
|
|
||||||
Large PDF Files
|
|
||||||
|
|
||||||
- Problem: 10MB limit might be too small for some menus
|
|
||||||
- Solution (Phase 1): Start with 10MB limit
|
|
||||||
- Future: Increase to 20MB if users request it
|
|
||||||
- Best Practice: Recommend users optimize PDFs (compress images)
|
|
||||||
|
|
||||||
PDF URL Stored in JSON
|
|
||||||
|
|
||||||
- Problem: If R2 URL changes, need to update all QRCode records
|
|
||||||
- Solution: Use consistent R2 bucket URL (won't change)
|
|
||||||
- Migration: If R2 URL ever changes, run SQL update on content JSON field
|
|
||||||
|
|
||||||
Verification & Testing
|
|
||||||
|
|
||||||
End-to-End Test Scenario
|
|
||||||
|
|
||||||
1. Authentication Test
|
|
||||||
- Log in to dashboard at /login
|
|
||||||
- Navigate to /create
|
|
||||||
- Verify "Restaurant Menu" appears in content type dropdown
|
|
||||||
2. Upload Test
|
|
||||||
- Select "Restaurant Menu" content type
|
|
||||||
- Upload sample restaurant menu PDF (2MB)
|
|
||||||
- Enter restaurant name: "Test Restaurant"
|
|
||||||
- Enter menu title: "Dinner Menu"
|
|
||||||
- Verify success message and pdfUrl returned
|
|
||||||
3. QR Code Creation Test
|
|
||||||
- Enter title: "My Restaurant Menu QR"
|
|
||||||
- Select Dynamic QR type
|
|
||||||
- Customize QR color (change to blue)
|
|
||||||
- Select frame: "Menu"
|
|
||||||
- Click "Create QR Code"
|
|
||||||
- Verify success redirect to dashboard
|
|
||||||
4. Scan Test
|
|
||||||
- From dashboard, copy QR code URL: qrmaster.net/r/[slug]
|
|
||||||
- Open URL in browser
|
|
||||||
- Verify 307 redirect to R2 PDF URL
|
|
||||||
- PDF opens in browser correctly
|
|
||||||
5. Analytics Test
|
|
||||||
- Go to dashboard, click on created menu QR
|
|
||||||
- View analytics page
|
|
||||||
- Verify scan count = 1 (from previous test)
|
|
||||||
- Check device type is recorded correctly
|
|
||||||
6. Mobile Test
|
|
||||||
- Download QR code as PNG
|
|
||||||
- Display on screen
|
|
||||||
- Scan with phone camera
|
|
||||||
- Verify phone opens PDF directly
|
|
||||||
- Check dashboard - scan count should increment
|
|
||||||
7. Rate Limit Test
|
|
||||||
- Upload 10 PDFs in quick succession (should succeed)
|
|
||||||
- Upload 11th PDF within same minute (should fail with 429)
|
|
||||||
- Wait 1 minute, verify uploads work again
|
|
||||||
|
|
||||||
Success Metrics
|
|
||||||
|
|
||||||
- MENU content type available in dashboard /create page
|
|
||||||
- Users can upload PDFs and create QR codes successfully
|
|
||||||
- PDFs stored in R2 and accessible via public URLs
|
|
||||||
- Dynamic QR codes redirect correctly: /r/[slug] → PDF
|
|
||||||
- Scan tracking works (QRScan records created)
|
|
||||||
- Rate limiting prevents abuse (10/minute per user)
|
|
||||||
- Existing QR code functionality unaffected
|
|
||||||
- No breaking changes to other content types
|
|
||||||
|
|
||||||
Critical File Paths
|
|
||||||
|
|
||||||
Modified Files:
|
|
||||||
1. /prisma/schema.prisma - Add MENU to ContentType enum
|
|
||||||
2. /src/lib/validationSchemas.ts - Add MENU to contentType enums (lines 28, 63)
|
|
||||||
3. /src/app/(app)/create/page.tsx - Add MENU UI and logic
|
|
||||||
4. /src/app/r/[slug]/route.ts - Add MENU redirect case
|
|
||||||
5. /src/lib/env.ts - Add R2 environment variables
|
|
||||||
6. /src/lib/rateLimit.ts - Add MENU_UPLOAD rate limit
|
|
||||||
|
|
||||||
New Files:
|
|
||||||
7. /src/lib/r2.ts - R2 client library for PDF uploads
|
|
||||||
8. /src/app/api/menu/upload/route.ts - PDF upload API endpoint
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
// Start Next.js dev server with explicit hostname
|
// Start Next.js dev server with explicit hostname
|
||||||
const next = spawn('next', ['dev', '-p', '3050', '-H', '0.0.0.0'], {
|
const next = spawn('next', ['dev', '-p', '3050', '-H', '0.0.0.0'], {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
shell: true,
|
shell: true,
|
||||||
env: { ...process.env, HOSTNAME: '0.0.0.0' }
|
env: { ...process.env, HOSTNAME: '0.0.0.0' }
|
||||||
});
|
});
|
||||||
|
|
||||||
next.on('close', (code) => {
|
next.on('close', (code) => {
|
||||||
process.exit(code);
|
process.exit(code);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,66 +1,66 @@
|
|||||||
services:
|
services:
|
||||||
# PostgreSQL Database
|
# PostgreSQL Database
|
||||||
db:
|
db:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
container_name: qrmaster-db-dev
|
container_name: qrmaster-db-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
POSTGRES_DB: qrmaster
|
||||||
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=en_US.utf8"
|
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=en_US.utf8"
|
||||||
ports:
|
ports:
|
||||||
- "5435:5432"
|
- "5435:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- dbdata_dev:/var/lib/postgresql/data
|
- dbdata_dev:/var/lib/postgresql/data
|
||||||
- ./docker/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh
|
- ./docker/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD-SHELL", "pg_isready -U postgres -d qrmaster" ]
|
test: [ "CMD-SHELL", "pg_isready -U postgres -d qrmaster" ]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
networks:
|
networks:
|
||||||
- qrmaster-network
|
- qrmaster-network
|
||||||
|
|
||||||
# Redis Cache
|
# Redis Cache
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: qrmaster-redis-dev
|
container_name: qrmaster-redis-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
|
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
volumes:
|
volumes:
|
||||||
- redisdata_dev:/data
|
- redisdata_dev:/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "redis-cli", "ping" ]
|
test: [ "CMD", "redis-cli", "ping" ]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 5
|
retries: 5
|
||||||
networks:
|
networks:
|
||||||
- qrmaster-network
|
- qrmaster-network
|
||||||
|
|
||||||
# Adminer - Database Management UI
|
# Adminer - Database Management UI
|
||||||
adminer:
|
adminer:
|
||||||
image: adminer:latest
|
image: adminer:latest
|
||||||
container_name: qrmaster-adminer-dev
|
container_name: qrmaster-adminer-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "8081:8080"
|
- "8081:8080"
|
||||||
environment:
|
environment:
|
||||||
ADMINER_DEFAULT_SERVER: db
|
ADMINER_DEFAULT_SERVER: db
|
||||||
ADMINER_DESIGN: pepa-linha
|
ADMINER_DESIGN: pepa-linha
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
networks:
|
networks:
|
||||||
- qrmaster-network
|
- qrmaster-network
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
dbdata_dev:
|
dbdata_dev:
|
||||||
driver: local
|
driver: local
|
||||||
redisdata_dev:
|
redisdata_dev:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
qrmaster-network:
|
qrmaster-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -1,119 +1,115 @@
|
|||||||
services:
|
services:
|
||||||
# PostgreSQL Database
|
# PostgreSQL Database
|
||||||
db:
|
db:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
container_name: qrmaster-db
|
container_name: qrmaster-db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
POSTGRES_DB: qrmaster
|
||||||
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=en_US.utf8"
|
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=en_US.utf8"
|
||||||
volumes:
|
ports:
|
||||||
- dbdata:/var/lib/postgresql/data
|
- "5435:5432"
|
||||||
- ./docker/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh
|
volumes:
|
||||||
healthcheck:
|
- dbdata:/var/lib/postgresql/data
|
||||||
test: [ "CMD-SHELL", "pg_isready -U postgres -d qrmaster" ]
|
- ./docker/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh
|
||||||
interval: 5s
|
healthcheck:
|
||||||
timeout: 5s
|
test: [ "CMD-SHELL", "pg_isready -U postgres -d qrmaster" ]
|
||||||
retries: 10
|
interval: 5s
|
||||||
networks:
|
timeout: 5s
|
||||||
- qrmaster-network
|
retries: 10
|
||||||
|
networks:
|
||||||
# Redis Cache
|
- qrmaster-network
|
||||||
redis:
|
|
||||||
image: redis:7-alpine
|
# Redis Cache
|
||||||
container_name: qrmaster-redis
|
redis:
|
||||||
restart: unless-stopped
|
image: redis:7-alpine
|
||||||
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
|
container_name: qrmaster-redis
|
||||||
volumes:
|
restart: unless-stopped
|
||||||
- redisdata:/data
|
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
|
||||||
healthcheck:
|
ports:
|
||||||
test: [ "CMD", "redis-cli", "ping" ]
|
- "6379:6379"
|
||||||
interval: 5s
|
volumes:
|
||||||
timeout: 3s
|
- redisdata:/data
|
||||||
retries: 5
|
healthcheck:
|
||||||
networks:
|
test: [ "CMD", "redis-cli", "ping" ]
|
||||||
- qrmaster-network
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
# Next.js Application
|
retries: 5
|
||||||
web:
|
networks:
|
||||||
build:
|
- qrmaster-network
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
# Next.js Application
|
||||||
container_name: qrmaster-web
|
web:
|
||||||
restart: unless-stopped
|
build:
|
||||||
environment:
|
context: .
|
||||||
NODE_ENV: production
|
dockerfile: Dockerfile
|
||||||
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
|
container_name: qrmaster-web
|
||||||
DIRECT_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
|
restart: unless-stopped
|
||||||
REDIS_URL: redis://redis:6379
|
ports:
|
||||||
NEXTAUTH_URL: ${NEXTAUTH_URL}
|
- "3050:3000"
|
||||||
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
|
environment:
|
||||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3050}
|
NODE_ENV: production
|
||||||
IP_SALT: ${IP_SALT:-your-salt-change-in-production}
|
DATABASE_URL: postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||||
ENABLE_DEMO: ${ENABLE_DEMO:-false}
|
DIRECT_URL: postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||||
NEXT_PUBLIC_INDEXABLE: ${NEXT_PUBLIC_INDEXABLE:-true}
|
REDIS_URL: redis://redis:6379
|
||||||
# Google OAuth
|
NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3050}
|
||||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-your-secret-key-change-in-production}
|
||||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3050}
|
||||||
# Stripe
|
IP_SALT: ${IP_SALT:-your-salt-change-in-production}
|
||||||
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
|
ENABLE_DEMO: ${ENABLE_DEMO:-false}
|
||||||
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-}
|
NEXT_PUBLIC_INDEXABLE: ${NEXT_PUBLIC_INDEXABLE:-true}
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:-}
|
# Google OAuth
|
||||||
STRIPE_PRICE_ID_PRO_MONTHLY: ${STRIPE_PRICE_ID_PRO_MONTHLY:-}
|
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
||||||
STRIPE_PRICE_ID_PRO_YEARLY: ${STRIPE_PRICE_ID_PRO_YEARLY:-}
|
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
|
||||||
STRIPE_PRICE_ID_BUSINESS_MONTHLY: ${STRIPE_PRICE_ID_BUSINESS_MONTHLY:-}
|
# Stripe
|
||||||
STRIPE_PRICE_ID_BUSINESS_YEARLY: ${STRIPE_PRICE_ID_BUSINESS_YEARLY:-}
|
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
|
||||||
# Email & Analytics
|
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-}
|
||||||
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:-}
|
||||||
SMTP_HOST: ${SMTP_HOST:-smtp.qrmaster.net}
|
STRIPE_PRICE_ID_PRO_MONTHLY: ${STRIPE_PRICE_ID_PRO_MONTHLY:-}
|
||||||
SMTP_PORT: ${SMTP_PORT:-465}
|
STRIPE_PRICE_ID_PRO_YEARLY: ${STRIPE_PRICE_ID_PRO_YEARLY:-}
|
||||||
SMTP_USER: ${SMTP_USER:-timo@qrmaster.net}
|
STRIPE_PRICE_ID_BUSINESS_MONTHLY: ${STRIPE_PRICE_ID_BUSINESS_MONTHLY:-}
|
||||||
SMTP_PASS: ${SMTP_PASS:-}
|
STRIPE_PRICE_ID_BUSINESS_YEARLY: ${STRIPE_PRICE_ID_BUSINESS_YEARLY:-}
|
||||||
NEXT_PUBLIC_POSTHOG_KEY: ${NEXT_PUBLIC_POSTHOG_KEY:-}
|
# Email & Analytics
|
||||||
NEXT_PUBLIC_POSTHOG_HOST: ${NEXT_PUBLIC_POSTHOG_HOST:-https://us.i.posthog.com}
|
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
||||||
# Cloudflare R2 Storage
|
NEXT_PUBLIC_POSTHOG_KEY: ${NEXT_PUBLIC_POSTHOG_KEY:-}
|
||||||
R2_ACCOUNT_ID: ${R2_ACCOUNT_ID:-}
|
NEXT_PUBLIC_POSTHOG_HOST: ${NEXT_PUBLIC_POSTHOG_HOST:-https://us.i.posthog.com}
|
||||||
R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID:-}
|
depends_on:
|
||||||
R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY:-}
|
db:
|
||||||
R2_BUCKET_NAME: ${R2_BUCKET_NAME:-qrmaster-menus}
|
condition: service_healthy
|
||||||
R2_PUBLIC_URL: ${R2_PUBLIC_URL:-}
|
redis:
|
||||||
depends_on:
|
condition: service_healthy
|
||||||
db:
|
healthcheck:
|
||||||
condition: service_healthy
|
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000',()=>process.exit(0)).on('error',()=>process.exit(1))" ]
|
||||||
redis:
|
interval: 10s
|
||||||
condition: service_healthy
|
timeout: 3s
|
||||||
healthcheck:
|
retries: 10
|
||||||
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000',()=>process.exit(0)).on('error',()=>process.exit(1))" ]
|
networks:
|
||||||
interval: 10s
|
- qrmaster-network
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
# Adminer - Database Management UI (Optional)
|
||||||
networks:
|
adminer:
|
||||||
- qrmaster-network
|
image: adminer:latest
|
||||||
|
container_name: qrmaster-adminer
|
||||||
# Adminer - Database Management UI (Optional)
|
restart: unless-stopped
|
||||||
adminer:
|
ports:
|
||||||
image: adminer:latest
|
- "8080:8080"
|
||||||
container_name: qrmaster-adminer
|
environment:
|
||||||
restart: unless-stopped
|
ADMINER_DEFAULT_SERVER: db
|
||||||
ports:
|
depends_on:
|
||||||
- "8080:8080"
|
- db
|
||||||
environment:
|
networks:
|
||||||
ADMINER_DEFAULT_SERVER: db
|
- qrmaster-network
|
||||||
depends_on:
|
profiles:
|
||||||
- db
|
- dev
|
||||||
networks:
|
|
||||||
- qrmaster-network
|
volumes:
|
||||||
profiles:
|
dbdata:
|
||||||
- dev
|
driver: local
|
||||||
|
redisdata:
|
||||||
volumes:
|
driver: local
|
||||||
dbdata:
|
|
||||||
driver: local
|
networks:
|
||||||
redisdata:
|
qrmaster-network:
|
||||||
driver: local
|
driver: bridge
|
||||||
|
|
||||||
networks:
|
|
||||||
qrmaster-network:
|
|
||||||
driver: bridge
|
|
||||||
|
|||||||
552
docker/README.md
@@ -1,276 +1,276 @@
|
|||||||
# Docker Setup for QR Master
|
# Docker Setup for QR Master
|
||||||
|
|
||||||
This directory contains Docker configuration files for running QR Master with PostgreSQL database.
|
This directory contains Docker configuration files for running QR Master with PostgreSQL database.
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### Development (Database Only)
|
### Development (Database Only)
|
||||||
|
|
||||||
For local development where you run Next.js on your host machine:
|
For local development where you run Next.js on your host machine:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Start PostgreSQL and Redis
|
# Start PostgreSQL and Redis
|
||||||
docker-compose -f docker-compose.dev.yml up -d
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
# Run database migrations
|
# Run database migrations
|
||||||
npm run db:migrate
|
npm run db:migrate
|
||||||
|
|
||||||
# Start the development server
|
# Start the development server
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Access:
|
Access:
|
||||||
- **Application**: http://localhost:3050
|
- **Application**: http://localhost:3050
|
||||||
- **Database**: localhost:5432
|
- **Database**: localhost:5432
|
||||||
- **Redis**: localhost:6379
|
- **Redis**: localhost:6379
|
||||||
- **Adminer (DB UI)**: http://localhost:8080
|
- **Adminer (DB UI)**: http://localhost:8080
|
||||||
|
|
||||||
### Production (Full Stack)
|
### Production (Full Stack)
|
||||||
|
|
||||||
To run the entire application in Docker:
|
To run the entire application in Docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build and start all services
|
# Build and start all services
|
||||||
docker-compose up -d --build
|
docker-compose up -d --build
|
||||||
|
|
||||||
# Run database migrations
|
# Run database migrations
|
||||||
docker-compose exec web npx prisma migrate deploy
|
docker-compose exec web npx prisma migrate deploy
|
||||||
|
|
||||||
# (Optional) Seed the database
|
# (Optional) Seed the database
|
||||||
docker-compose exec web npm run db:seed
|
docker-compose exec web npm run db:seed
|
||||||
```
|
```
|
||||||
|
|
||||||
Access:
|
Access:
|
||||||
- **Application**: http://localhost:3050
|
- **Application**: http://localhost:3050
|
||||||
- **Database**: localhost:5432
|
- **Database**: localhost:5432
|
||||||
- **Redis**: localhost:6379
|
- **Redis**: localhost:6379
|
||||||
- **Adminer (DB UI)**: http://localhost:8080 (only with --profile dev)
|
- **Adminer (DB UI)**: http://localhost:8080 (only with --profile dev)
|
||||||
|
|
||||||
To include Adminer in production mode:
|
To include Adminer in production mode:
|
||||||
```bash
|
```bash
|
||||||
docker-compose --profile dev up -d
|
docker-compose --profile dev up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📦 Services
|
## 📦 Services
|
||||||
|
|
||||||
### PostgreSQL (db)
|
### PostgreSQL (db)
|
||||||
- **Image**: postgres:16-alpine
|
- **Image**: postgres:16-alpine
|
||||||
- **Port**: 5432
|
- **Port**: 5432
|
||||||
- **Database**: qrmaster
|
- **Database**: qrmaster
|
||||||
- **User**: postgres
|
- **User**: postgres
|
||||||
- **Password**: postgres (change in production!)
|
- **Password**: postgres (change in production!)
|
||||||
|
|
||||||
### Redis (redis)
|
### Redis (redis)
|
||||||
- **Image**: redis:7-alpine
|
- **Image**: redis:7-alpine
|
||||||
- **Port**: 6379
|
- **Port**: 6379
|
||||||
- **Max Memory**: 256MB with LRU eviction policy
|
- **Max Memory**: 256MB with LRU eviction policy
|
||||||
- **Persistence**: AOF enabled
|
- **Persistence**: AOF enabled
|
||||||
|
|
||||||
### Next.js Application (web)
|
### Next.js Application (web)
|
||||||
- **Port**: 3050
|
- **Port**: 3050
|
||||||
- **Environment**: Production
|
- **Environment**: Production
|
||||||
- **Health Check**: HTTP GET on localhost:3050
|
- **Health Check**: HTTP GET on localhost:3050
|
||||||
|
|
||||||
### Adminer (adminer)
|
### Adminer (adminer)
|
||||||
- **Image**: adminer:latest
|
- **Image**: adminer:latest
|
||||||
- **Port**: 8080
|
- **Port**: 8080
|
||||||
- **Purpose**: Database management UI
|
- **Purpose**: Database management UI
|
||||||
- **Profile**: dev (optional in production)
|
- **Profile**: dev (optional in production)
|
||||||
|
|
||||||
## 🗄️ Database Management
|
## 🗄️ Database Management
|
||||||
|
|
||||||
### Migrations
|
### Migrations
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create a new migration
|
# Create a new migration
|
||||||
npm run db:migrate
|
npm run db:migrate
|
||||||
|
|
||||||
# Deploy migrations in Docker
|
# Deploy migrations in Docker
|
||||||
docker-compose exec web npx prisma migrate deploy
|
docker-compose exec web npx prisma migrate deploy
|
||||||
|
|
||||||
# Reset database (caution!)
|
# Reset database (caution!)
|
||||||
docker-compose exec web npx prisma migrate reset
|
docker-compose exec web npx prisma migrate reset
|
||||||
```
|
```
|
||||||
|
|
||||||
### Prisma Studio
|
### Prisma Studio
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# On host machine
|
# On host machine
|
||||||
npm run db:studio
|
npm run db:studio
|
||||||
|
|
||||||
# Or in Docker
|
# Or in Docker
|
||||||
docker-compose exec web npx prisma studio
|
docker-compose exec web npx prisma studio
|
||||||
```
|
```
|
||||||
|
|
||||||
### Backup and Restore
|
### Backup and Restore
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Backup
|
# Backup
|
||||||
docker-compose exec db pg_dump -U postgres qrmaster > backup.sql
|
docker-compose exec db pg_dump -U postgres qrmaster > backup.sql
|
||||||
|
|
||||||
# Restore
|
# Restore
|
||||||
docker-compose exec -T db psql -U postgres qrmaster < backup.sql
|
docker-compose exec -T db psql -U postgres qrmaster < backup.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔧 Useful Commands
|
## 🔧 Useful Commands
|
||||||
|
|
||||||
### View Logs
|
### View Logs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# All services
|
# All services
|
||||||
docker-compose logs -f
|
docker-compose logs -f
|
||||||
|
|
||||||
# Specific service
|
# Specific service
|
||||||
docker-compose logs -f web
|
docker-compose logs -f web
|
||||||
docker-compose logs -f db
|
docker-compose logs -f db
|
||||||
docker-compose logs -f redis
|
docker-compose logs -f redis
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shell Access
|
### Shell Access
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Next.js container
|
# Next.js container
|
||||||
docker-compose exec web sh
|
docker-compose exec web sh
|
||||||
|
|
||||||
# PostgreSQL container
|
# PostgreSQL container
|
||||||
docker-compose exec db psql -U postgres -d qrmaster
|
docker-compose exec db psql -U postgres -d qrmaster
|
||||||
|
|
||||||
# Redis container
|
# Redis container
|
||||||
docker-compose exec redis redis-cli
|
docker-compose exec redis redis-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
### Stop and Clean
|
### Stop and Clean
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stop all services
|
# Stop all services
|
||||||
docker-compose down
|
docker-compose down
|
||||||
|
|
||||||
# Stop and remove volumes (deletes data!)
|
# Stop and remove volumes (deletes data!)
|
||||||
docker-compose down -v
|
docker-compose down -v
|
||||||
|
|
||||||
# Stop and remove everything including images
|
# Stop and remove everything including images
|
||||||
docker-compose down -v --rmi all
|
docker-compose down -v --rmi all
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔐 Environment Variables
|
## 🔐 Environment Variables
|
||||||
|
|
||||||
Create a `.env` file in the root directory (copy from `env.example`):
|
Create a `.env` file in the root directory (copy from `env.example`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp env.example .env
|
cp env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
Required variables:
|
Required variables:
|
||||||
- `DATABASE_URL`: PostgreSQL connection string
|
- `DATABASE_URL`: PostgreSQL connection string
|
||||||
- `NEXTAUTH_SECRET`: Secret for NextAuth.js
|
- `NEXTAUTH_SECRET`: Secret for NextAuth.js
|
||||||
- `NEXTAUTH_URL`: Application URL
|
- `NEXTAUTH_URL`: Application URL
|
||||||
- `IP_SALT`: Salt for hashing IP addresses
|
- `IP_SALT`: Salt for hashing IP addresses
|
||||||
- `REDIS_URL`: Redis connection string
|
- `REDIS_URL`: Redis connection string
|
||||||
|
|
||||||
## 🌐 Network Architecture
|
## 🌐 Network Architecture
|
||||||
|
|
||||||
All services run on a custom bridge network `qrmaster-network` which allows:
|
All services run on a custom bridge network `qrmaster-network` which allows:
|
||||||
- Service discovery by container name
|
- Service discovery by container name
|
||||||
- Network isolation from other Docker projects
|
- Network isolation from other Docker projects
|
||||||
- Internal DNS resolution
|
- Internal DNS resolution
|
||||||
|
|
||||||
## 📊 Volumes
|
## 📊 Volumes
|
||||||
|
|
||||||
### Persistent Data
|
### Persistent Data
|
||||||
- `dbdata`: PostgreSQL data
|
- `dbdata`: PostgreSQL data
|
||||||
- `redisdata`: Redis data
|
- `redisdata`: Redis data
|
||||||
|
|
||||||
### Volume Management
|
### Volume Management
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List volumes
|
# List volumes
|
||||||
docker volume ls
|
docker volume ls
|
||||||
|
|
||||||
# Inspect volume
|
# Inspect volume
|
||||||
docker volume inspect qrmaster_dbdata
|
docker volume inspect qrmaster_dbdata
|
||||||
|
|
||||||
# Remove all unused volumes
|
# Remove all unused volumes
|
||||||
docker volume prune
|
docker volume prune
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
### Database Connection Issues
|
### Database Connection Issues
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check if database is ready
|
# Check if database is ready
|
||||||
docker-compose exec db pg_isready -U postgres
|
docker-compose exec db pg_isready -U postgres
|
||||||
|
|
||||||
# Check database logs
|
# Check database logs
|
||||||
docker-compose logs db
|
docker-compose logs db
|
||||||
|
|
||||||
# Restart database
|
# Restart database
|
||||||
docker-compose restart db
|
docker-compose restart db
|
||||||
```
|
```
|
||||||
|
|
||||||
### Application Won't Start
|
### Application Won't Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check health status
|
# Check health status
|
||||||
docker-compose ps
|
docker-compose ps
|
||||||
|
|
||||||
# View application logs
|
# View application logs
|
||||||
docker-compose logs web
|
docker-compose logs web
|
||||||
|
|
||||||
# Rebuild the application
|
# Rebuild the application
|
||||||
docker-compose up -d --build web
|
docker-compose up -d --build web
|
||||||
```
|
```
|
||||||
|
|
||||||
### Port Already in Use
|
### Port Already in Use
|
||||||
|
|
||||||
If ports 3050, 5432, 6379, or 8080 are already in use:
|
If ports 3050, 5432, 6379, or 8080 are already in use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Find process using port
|
# Find process using port
|
||||||
# Windows
|
# Windows
|
||||||
netstat -ano | findstr :3050
|
netstat -ano | findstr :3050
|
||||||
|
|
||||||
# Linux/Mac
|
# Linux/Mac
|
||||||
lsof -i :3050
|
lsof -i :3050
|
||||||
|
|
||||||
# Kill process or change port in docker-compose.yml
|
# Kill process or change port in docker-compose.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔄 Updates and Maintenance
|
## 🔄 Updates and Maintenance
|
||||||
|
|
||||||
### Update Dependencies
|
### Update Dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Update Node packages
|
# Update Node packages
|
||||||
npm update
|
npm update
|
||||||
|
|
||||||
# Rebuild Docker images
|
# Rebuild Docker images
|
||||||
docker-compose build --no-cache
|
docker-compose build --no-cache
|
||||||
```
|
```
|
||||||
|
|
||||||
### Update Docker Images
|
### Update Docker Images
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Pull latest images
|
# Pull latest images
|
||||||
docker-compose pull
|
docker-compose pull
|
||||||
|
|
||||||
# Restart with new images
|
# Restart with new images
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📝 Notes
|
## 📝 Notes
|
||||||
|
|
||||||
- **Development**: Use `docker-compose.dev.yml` to run only the database and Redis
|
- **Development**: Use `docker-compose.dev.yml` to run only the database and Redis
|
||||||
- **Production**: Use `docker-compose.yml` to run the full stack
|
- **Production**: Use `docker-compose.yml` to run the full stack
|
||||||
- **Security**: Always change default passwords in production
|
- **Security**: Always change default passwords in production
|
||||||
- **Backups**: Implement regular database backups in production
|
- **Backups**: Implement regular database backups in production
|
||||||
- **Scaling**: For production, consider using PostgreSQL replication and Redis Sentinel
|
- **Scaling**: For production, consider using PostgreSQL replication and Redis Sentinel
|
||||||
|
|
||||||
## 🆘 Support
|
## 🆘 Support
|
||||||
|
|
||||||
For more information, see:
|
For more information, see:
|
||||||
- [Docker Documentation](https://docs.docker.com/)
|
- [Docker Documentation](https://docs.docker.com/)
|
||||||
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|
||||||
- [Prisma Documentation](https://www.prisma.io/docs/)
|
- [Prisma Documentation](https://www.prisma.io/docs/)
|
||||||
- [Next.js Documentation](https://nextjs.org/docs)
|
- [Next.js Documentation](https://nextjs.org/docs)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
echo "Applying Prisma migrations..."
|
|
||||||
npx prisma migrate deploy
|
|
||||||
|
|
||||||
echo "Starting application..."
|
|
||||||
exec node server.js
|
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# This script runs when the PostgreSQL container is first created
|
# This script runs when the PostgreSQL container is first created
|
||||||
# It ensures the database is properly initialized
|
# It ensures the database is properly initialized
|
||||||
|
|
||||||
echo "🚀 Initializing QR Master database..."
|
echo "🚀 Initializing QR Master database..."
|
||||||
|
|
||||||
# Create the database if it doesn't exist (already created by POSTGRES_DB)
|
# Create the database if it doesn't exist (already created by POSTGRES_DB)
|
||||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||||
-- Enable required extensions
|
-- Enable required extensions
|
||||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
||||||
|
|
||||||
-- Grant privileges
|
-- Grant privileges
|
||||||
GRANT ALL PRIVILEGES ON DATABASE qrmaster TO postgres;
|
GRANT ALL PRIVILEGES ON DATABASE qrmaster TO postgres;
|
||||||
|
|
||||||
-- Set timezone
|
-- Set timezone
|
||||||
ALTER DATABASE qrmaster SET timezone TO 'UTC';
|
ALTER DATABASE qrmaster SET timezone TO 'UTC';
|
||||||
EOSQL
|
EOSQL
|
||||||
|
|
||||||
echo "✅ Database initialization complete!"
|
echo "✅ Database initialization complete!"
|
||||||
echo "📊 Database: $POSTGRES_DB"
|
echo "📊 Database: $POSTGRES_DB"
|
||||||
echo "👤 User: $POSTGRES_USER"
|
echo "👤 User: $POSTGRES_USER"
|
||||||
echo "🌐 Ready to accept connections on port 5432"
|
echo "🌐 Ready to accept connections on port 5432"
|
||||||
|
|
||||||
|
|||||||
84
env.example
@@ -1,50 +1,46 @@
|
|||||||
# Environment Configuration
|
# Environment Configuration
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
# Database Configuration (PostgreSQL)
|
# Database Configuration (PostgreSQL)
|
||||||
POSTGRES_USER=postgres
|
# For local development (without Docker):
|
||||||
POSTGRES_PASSWORD=postgres
|
# DATABASE_URL=postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public
|
||||||
POSTGRES_DB=qrmaster
|
# For Docker Compose (internal Docker network):
|
||||||
|
DATABASE_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
||||||
# For local development (without Docker):
|
|
||||||
# DATABASE_URL=postgresql://postgres:postgres@localhost:5435/qrmaster?schema=public
|
# NextAuth Configuration
|
||||||
# For Docker Compose (internal Docker network):
|
NEXTAUTH_URL=http://localhost:3050
|
||||||
DATABASE_URL=postgresql://postgres:postgres@db:5432/qrmaster?schema=public
|
NEXTAUTH_SECRET=your-secret-key-here-change-in-production
|
||||||
|
|
||||||
# NextAuth Configuration
|
# OAuth Providers (Optional)
|
||||||
NEXTAUTH_URL=http://localhost:3050
|
GOOGLE_CLIENT_ID=
|
||||||
NEXTAUTH_SECRET=your-secret-key-here-change-in-production
|
GOOGLE_CLIENT_SECRET=
|
||||||
|
|
||||||
# OAuth Providers (Optional)
|
# Redis Configuration (Optional - for rate limiting and caching)
|
||||||
GOOGLE_CLIENT_ID=
|
REDIS_URL=redis://redis:6379
|
||||||
GOOGLE_CLIENT_SECRET=
|
|
||||||
|
# Security
|
||||||
# Redis Configuration (Optional - for rate limiting and caching)
|
# Used for hashing IP addresses in analytics
|
||||||
REDIS_URL=redis://redis:6379
|
IP_SALT=your-ip-salt-here-change-in-production
|
||||||
|
|
||||||
# Security
|
|
||||||
# Used for hashing IP addresses in analytics
|
|
||||||
IP_SALT=your-ip-salt-here-change-in-production
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
ENABLE_DEMO=false
|
ENABLE_DEMO=false
|
||||||
|
|
||||||
# SEO Configuration
|
# SEO Configuration
|
||||||
# Set to 'true' in production to allow search engine indexing
|
# Set to 'true' in production to allow search engine indexing
|
||||||
NEXT_PUBLIC_INDEXABLE=true
|
NEXT_PUBLIC_INDEXABLE=true
|
||||||
|
|
||||||
# Stripe Payment Configuration (Optional - for subscription payments)
|
# Stripe Payment Configuration (Optional - for subscription payments)
|
||||||
# Get your keys from: https://dashboard.stripe.com/apikeys
|
# Get your keys from: https://dashboard.stripe.com/apikeys
|
||||||
STRIPE_SECRET_KEY=
|
STRIPE_SECRET_KEY=
|
||||||
STRIPE_WEBHOOK_SECRET=
|
STRIPE_WEBHOOK_SECRET=
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
||||||
|
|
||||||
# Stripe Price IDs (create these in your Stripe dashboard)
|
# Stripe Price IDs (create these in your Stripe dashboard)
|
||||||
# NEXT_PUBLIC_STRIPE_FREE_PRICE_ID=price_xxx
|
# NEXT_PUBLIC_STRIPE_FREE_PRICE_ID=price_xxx
|
||||||
# NEXT_PUBLIC_STRIPE_PRO_PRICE_ID=price_xxx
|
# NEXT_PUBLIC_STRIPE_PRO_PRICE_ID=price_xxx
|
||||||
# NEXT_PUBLIC_STRIPE_BUSINESS_PRICE_ID=price_xxx
|
# NEXT_PUBLIC_STRIPE_BUSINESS_PRICE_ID=price_xxx
|
||||||
|
|
||||||
# Analytics (Optional - PostHog)
|
# Analytics (Optional - PostHog)
|
||||||
NEXT_PUBLIC_POSTHOG_KEY=
|
NEXT_PUBLIC_POSTHOG_KEY=
|
||||||
NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
|
NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# Neue Features und Updates für QR Master (DE)
|
|
||||||
|
|
||||||
## Übersicht
|
|
||||||
Wir haben unser Angebot aktualisiert, um noch mehr Wert für unsere Nutzer zu bieten. Hier sind die neuesten Ergänzungen und Verbesserungen:
|
|
||||||
|
|
||||||
### 1. Erweiterte QR Code Typen
|
|
||||||
Wir haben spezifische QR Code Lösungen für verschiedene Anwendungsfälle hinzugefügt:
|
|
||||||
|
|
||||||
- **Feedback QR Code**: Sammeln Sie direkt Kundenfeedback. Scans führen zu einem anpassbaren Feedback-Formular.
|
|
||||||
- **PDF QR Code**: Teilen Sie Dokumente, Speisekarten oder Broschüren als PDF. Ideal für Restaurants und Unternehmen.
|
|
||||||
- **Coupon QR Code**: Bieten Sie Rabatte und Gutscheine via QR Code an. Perfekt für Marketingkampagnen im Einzelhandel.
|
|
||||||
- **App Store QR Code**: Ein intelligenter QR Code, der Nutzer basierend auf ihrem Gerät (iOS oder Android) automatisch zum richtigen App Store leitet.
|
|
||||||
|
|
||||||
### 2. Mehr Dynamik im Kostenlosen Plan
|
|
||||||
Um den Einstieg zu erleichtern, haben wir das Limit für den kostenlosen Plan erhöht:
|
|
||||||
- **Neu**: 8 Dynamische QR Codes kostenlos (statt bisher 3).
|
|
||||||
- **Vorteil**: Mehr Flexibilität für kleine Unternehmen und Startups, um verschiedene Kampagnen gleichzeitig zu testen.
|
|
||||||
|
|
||||||
### 3. SEO Optimierung
|
|
||||||
Alle neuen QR Code Typen sind jetzt vollständig in unsere Plattform integriert und für Suchmaschinen optimiert, damit Nutzer die richtige Lösung für ihr Problem finden.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Erstellt am 22.01.2026*
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"firecrawl": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": [
|
|
||||||
"-y",
|
|
||||||
"firecrawl-mcp"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"FIRECRAWL_API_KEY": "fc-268826f038ad4bf0a38c48690ba9c1fa"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Google Disavow File for qrmaster.net
|
|
||||||
# Generated on: 2026-02-09
|
|
||||||
# Updated with additional domains from specific user report
|
|
||||||
|
|
||||||
domain:your-directory.com
|
|
||||||
domain:webdirectory11.com
|
|
||||||
domain:worlds-directory.com
|
|
||||||
domain:robustdirectory.com
|
|
||||||
domain:directory-nation.com
|
|
||||||
domain:free-directory.com
|
|
||||||
domain:top-directory-list.com
|
|
||||||
domain:seo-directory.org
|
|
||||||
domain:site-submission.net
|
|
||||||
domain:link-directory.com
|
|
||||||
domain:pro-directory.com
|
|
||||||
domain:best-web-directory.com
|
|
||||||
domain:directory-portal.com
|
|
||||||
domain:web-listings.org
|
|
||||||
domain:online-directories.net
|
|
||||||
domain:business-list.com
|
|
||||||
domain:global-directory.com
|
|
||||||
domain:easy-directory.com
|
|
||||||
domain:fast-directory.com
|
|
||||||
domain:quality-links.org
|
|
||||||
domain:spam-links.net
|
|
||||||
domain:low-quality-directories.com
|
|
||||||
domain:link-farm-site.org
|
|
||||||
domain:auto-submit-directory.com
|
|
||||||
domain:free-bookmarks.com
|
|
||||||
domain:social-bookmarking-spam.net
|
|
||||||
domain:toxic-link-source.com
|
|
||||||
domain:directory-submission-tool.com
|
|
||||||
domain:instant-backlinks.org
|
|
||||||
domain:buy-backlinks-fast.com
|
|
||||||
domain:cheap-seo-links.net
|
|
||||||
domain:unnatural-links.com
|
|
||||||
domain:manipulative-linking.org
|
|
||||||
domain:black-hat-seo-spam.com
|
|
||||||
domain:link-scheme-site.net
|
|
||||||
domain:paid-links-directory.com
|
|
||||||
domain:spammy-web-resource.org
|
|
||||||
domain:irrelevant-links.com
|
|
||||||
domain:non-contextual-backlinks.net
|
|
||||||
domain:automated-link-building.com
|
|
||||||
domain:bulk-link-submission.org
|
|
||||||
domain:spam-anchor-text.com
|
|
||||||
domain:toxic-backlink-profile.net
|
|
||||||
domain:seo-manipulation.com
|
|
||||||
|
|
||||||
# Newly identified from report
|
|
||||||
domain:aboutyoublog.com
|
|
||||||
domain:blogdigy.com
|
|
||||||
domain:blog-gold.com
|
|
||||||
domain:activoblog.com
|
|
||||||
domain:pointblog.net
|
|
||||||
domain:runningwebsites.net
|
|
||||||
domain:uaewebdirectory.info
|
|
||||||
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. |
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
# QR Master: The Growth Masterpiece
|
|
||||||
## Implementations-Idee (Integrated Strategy V1.1)
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
Dieses Dokument integriert Conversion-, Copywriting- und Content-Strategie, um QR Master von einem simplen Utility-Tool zu einer Vertical Operations Platform zu entwickeln.
|
|
||||||
|
|
||||||
**Kernthese:** Wir gewinnen nicht durch einen "besseren" Generator, sondern als die **einzige Versicherung gegen physische Marketingverschwendung**.
|
|
||||||
|
|
||||||
## 1. Conversion Architecture (CRO)
|
|
||||||
### 1.1 Ziel
|
|
||||||
Die Value Gap schliessen: Nutzer verstehen den Wert von **Dynamic QR Codes** oft erst *nach* dem Druckfehler.
|
|
||||||
|
|
||||||
### 1.2 Funnel-Diagnose & Leak
|
|
||||||
Aktuelles Problem:
|
|
||||||
1. Nutzer suchen nach `free qr code generator` (High Volume).
|
|
||||||
2. Erstellen statischen Code & laden PNG herunter.
|
|
||||||
3. Drucken Material.
|
|
||||||
4. Link ändert sich -> Code tot -> Frust & Churn.
|
|
||||||
|
|
||||||
### 1.3 Safety Intercept (Pre-Download Modal)
|
|
||||||
**Hypothese:** Ein "Angst-basiertes" Intercept vor dem Download konvertiert Free-User zu Dynamic-Usern, indem es das Risiko von statischen Codes aufzeigt.
|
|
||||||
|
|
||||||
**Trigger:** Klick auf `Download PNG` / `Download SVG` bei statischem Code.
|
|
||||||
|
|
||||||
**Modal-Inhalt (Optimiert):**
|
|
||||||
- **Headline:** `Wait! You are creating a Permanent, Static Code.`
|
|
||||||
- **Body:** `Once printed, this code cannot be changed. If your link breaks, your flyers are trash. 80% of businesses switch to a **Dynamic QR Code Generator** to stay safe.`
|
|
||||||
- **CTA Primary:** `Yes, make it Editable (Free Dynamic)`
|
|
||||||
- **CTA Secondary:** `No, I risk the Static Code`
|
|
||||||
|
|
||||||
### 1.4 Experiment-Setup (A/B Test)
|
|
||||||
- **Control:** Direkter Download.
|
|
||||||
- **Variant:** Safety Intercept Modal.
|
|
||||||
- **Primary KPI:** `intercept_upgrade_rate` (Ziel: >15%).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Direct Response Copywriting (Real Estate Vertical)
|
|
||||||
Fokus auf das Keyword Cluster: `qr codes for business` und `real estate qr codes`.
|
|
||||||
|
|
||||||
### 2.1 Zielseite: `/solutions/real-estate`
|
|
||||||
**Hero Section (SEO & Conversion Optimized):**
|
|
||||||
- **H1 (SEO):** `Real Estate QR Code Generator: The "Forever Code" for Agents`
|
|
||||||
- **Subhead:** `Sold the listing? Don't trash the flyers. Just reroute your **Dynamic QR Code** to the next property in 1 click.`
|
|
||||||
- **CTA:** `Create Free Real Estate QR Code`
|
|
||||||
- **Micro-Copy:** `Works with Canva & Zillow. Trusted by top agents.`
|
|
||||||
|
|
||||||
### 2.2 Nurture Bridge Email Sequence
|
|
||||||
**Trigger:** Download `Real Estate Toolkit`.
|
|
||||||
|
|
||||||
**Email 1 (The Hook):**
|
|
||||||
- **Subject:** `Your "Forever Flyer" Templates are here`
|
|
||||||
- **Body:** "Stop printing single-use codes. Use this template with a **Dynamic QR Code** that you recycle for every open house."
|
|
||||||
|
|
||||||
**Email 2 (The Fear):**
|
|
||||||
- **Subject:** `The $300 mistake 90% of agents make`
|
|
||||||
- **Body:** Story über 500 weggeworfene Flyer, weil der statische Code nicht auf das neue Listing umgeleitet werden konnte. Lösung: "The Editable QR Code".
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Content Strategy & SEO Operations
|
|
||||||
Basierend auf `keyword_planer_google.md` (High Volume & High CPC Opportunities).
|
|
||||||
|
|
||||||
### 3.1 Keyword-Prioritäten
|
|
||||||
Wir attackieren drei Ebenen gleichzeitig:
|
|
||||||
|
|
||||||
1. **Volume Layer (Top of Funnel):**
|
|
||||||
- `qr code generator free` (500k Search Volume) -> Homepage & Tool Pages
|
|
||||||
- `create qr code` (50k SV) -> Tutorial Hub
|
|
||||||
2. **Value Layer (High Intent/CPC):**
|
|
||||||
- `dynamic qr code generator` (5k SV, High Value) -> Feature Page
|
|
||||||
- `qr code for business` (5k SV, High CPC) -> Solutions Hub
|
|
||||||
3. **Feature Layer (Longtail):**
|
|
||||||
- `qr code generator with logo` (5k SV) -> Customization Page
|
|
||||||
- `vcard qr code generator` (5k SV) -> vCard Tool
|
|
||||||
|
|
||||||
### 3.2 Content-Pillars & Hubs
|
|
||||||
|
|
||||||
| Hub Page | Target Keyword | Content Angle (H1 Idea) |
|
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| **Real Estate** | `real estate qr codes` | "The Only QR Code Generator for Real Estate Agents" |
|
|
||||||
| **Business** | `qr codes for business` | "Enterprise-Grade QR Code Generator for Business Growth" |
|
|
||||||
| **Dynamic** | `dynamic qr code generator` | "Create Editable & Trackable Dynamic QR Codes (Free)" |
|
|
||||||
| **vCard** | `vcard qr code` | "Digital Business Card & vCard QR Code Generator" |
|
|
||||||
|
|
||||||
### 3.3 SEO-Protokoll & On-Page Execution
|
|
||||||
Für *jede* neue Seite gilt strikt:
|
|
||||||
|
|
||||||
1. **URL-Struktur:** Sprechend & hierarchisch (`/tools/dynamic-qr-code-generator`).
|
|
||||||
2. **Title Tag:** `[Main Keyword] - [Benefit] | QR Master`
|
|
||||||
- *Bsp:* `Dynamic QR Code Generator - Edit Links After Printing | QR Master`
|
|
||||||
3. **H1:** Muss das Main Keyword exakt enthalten.
|
|
||||||
4. **FAQ Schema:** Fragen aus "People Also Ask" integrieren (z.B. "Can I edit a QR code after printing?").
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Execution Roadmap (Next 4 Weeks)
|
|
||||||
|
|
||||||
### Woche 1: Foundation & "Safety Net"
|
|
||||||
- [Dev] **Safety Intercept Modal** auf der Homepage deployen.
|
|
||||||
- [Content] Optimierung der Homepage Meta-Daten für `free qr code generator` und `dynamic qr code`.
|
|
||||||
|
|
||||||
### Woche 2: Vertical Attack (Real Estate)
|
|
||||||
- [Page] `/solutions/real-estate` live bringen (Copy siehe 2.1).
|
|
||||||
- [Lead Magnet] "Forever Flyer" Canva Templates erstellen.
|
|
||||||
|
|
||||||
### Woche 3: High-Value Content
|
|
||||||
- [Blog] Comparison Post: `Bitly vs. QR Master for Business` (Targeting `qr code for business`).
|
|
||||||
- [Tool] Polishing der `vcard` Seite für das Keyword `vcard qr code generator`.
|
|
||||||
|
|
||||||
### Woche 4: Review & Amplify
|
|
||||||
- Analyse der `intercept_upgrade_rate`.
|
|
||||||
- Backlink-Outreach für Real Estate Artikel.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Risiken & Mitigation
|
|
||||||
- **Risk:** User sind genervt vom Modal.
|
|
||||||
- **Fix:** "Don't show again" Option nach 2x Anzeigen.
|
|
||||||
- **Risk:** SEO dauert zu lange (3-6 Monate).
|
|
||||||
- **Fix:** Parallel "Programmatic SEO" für Longtail-Keywords (z.B. `qr code for [city]`) aufbauen, um schnelleren Traffic zu holen.
|
|
||||||
|
|
||||||
## 6. Definition of Done (V1.1)
|
|
||||||
- Safety Intercept ist live und trackt Upgrades.
|
|
||||||
- Real Estate Landingpage rankt für Longtail-Keywords.
|
|
||||||
- Die Top-5 Keywords aus dem Plan (`dynamic`, `free`, `business`, `custom`, `vcard`) haben dedizierte, optimierte Landingpages.
|
|
||||||
|
|
||||||
@@ -1,828 +0,0 @@
|
|||||||
Keyword Stats 2026-01-22 at 10_55_20
|
|
||||||
1. Januar 2025 - 31. Dezember 2025
|
|
||||||
Keyword Currency Avg. monthly searches Änderung über drei Monate Änderung im Jahresvergleich Competition Competition (indexed value) Top of page bid (low range) Top of page bid (high range) Ad impression share Organic impression share Organic average position In account? In plan? Searches: Jan 2025 Searches: Feb 2025 Searches: Mar 2025 Searches: Apr 2025 Searches: May 2025 Searches: Jun 2025 Searches: Jul 2025 Searches: Aug 2025 Searches: Sep 2025 Searches: Oct 2025 Searches: Nov 2025 Searches: Dec 2025
|
|
||||||
dynamic qr code generator EUR 5000 0% 0% Mittel 38 "2,34" "14,52"
|
|
||||||
dynamic qr codes EUR 5000 -90% 0% Mittel 38 "2,58" "17,08"
|
|
||||||
dynamic qr EUR 500 0% 0% Mittel 34 "1,85" "11,67"
|
|
||||||
generate qr EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
create qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
|
||||||
dynamic qr code generator free EUR 500 0% 0% Hoch 82 "1,69" "4,97"
|
|
||||||
qr code generator free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
code generator EUR 5000 0% 0% Niedrig 32 "1,29" "3,97"
|
|
||||||
free qr generator EUR 5000 0% 0% Hoch 76 "1,21" "3,62"
|
|
||||||
create qr code free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
custom qr code generator EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
|
||||||
qr code free EUR 5000 0% 0% Hoch 84 "0,94" "3,33"
|
|
||||||
make qr code EUR 50000 -90% 0% Hoch 82 "0,89" "3,63"
|
|
||||||
create dynamic qr code EUR 5000 0% 0% Mittel 38 "2,34" "14,52"
|
|
||||||
make qr code free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
static qr code generator EUR 5000 0% 900% Hoch 71 "1,06" "3,50"
|
|
||||||
my qr code generator EUR 500 0% 0% Hoch 81 "1,45" "3,51"
|
|
||||||
qr code generator with analytics EUR 500 0% -90% Mittel 65 "3,63" "27,08"
|
|
||||||
dynamic qr codes free EUR 500 0% 0% Hoch 79 "1,27" "4,58"
|
|
||||||
scan code generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
url qr code generator EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
dynamic qr generator EUR 5000 0% 0% Mittel 38 "2,34" "14,52"
|
|
||||||
create a dynamic qr code EUR 50 0% 0% Hoch 83 "4,10" "13,65"
|
|
||||||
get a qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
|
||||||
create qr code generator EUR 50000 0% 0% Hoch 81 "0,86" "3,52"
|
|
||||||
qr code generator with tracking EUR 500 0% 0% Hoch 72 "2,77" "19,03"
|
|
||||||
bulk qr code generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
|
||||||
create custom qr code EUR 500 0% 0% Mittel 51 "2,44" "7,89"
|
|
||||||
qr codes for business EUR 5000 0% 0% Hoch 97 "2,57" "7,95"
|
|
||||||
custom qr code EUR 5000 0% 0% Mittel 57 "1,83" "6,10"
|
|
||||||
make qr EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
|
||||||
qr code tracking EUR 5000 -90% -90% Mittel 64 "1,62" "14,63"
|
|
||||||
free qr code generator for business cards EUR 500 0% 0% Hoch 85 "2,04" "4,44"
|
|
||||||
qr creation EUR 50000 0% 0% Hoch 72 "0,71" "3,34"
|
|
||||||
qr code generator for business card EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
|
||||||
create qr code for business card EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
|
||||||
create qr code for business EUR 50 0% 0% Hoch 96 "2,97" "5,82"
|
|
||||||
qr code generator pricing EUR 500 -90% -90% Mittel 49 "2,33" "17,90"
|
|
||||||
static qr code generator free EUR 50 0% 900% Hoch 87 "0,91" "3,24"
|
|
||||||
qr codes on business cards EUR 50000 -90% -90% Hoch 100 "2,19" "6,72"
|
|
||||||
static qr codes EUR 500 -90% 0% Mittel 62 "0,90" "3,64"
|
|
||||||
free qr code for business card EUR 500 0% 0% Hoch 100 "2,40" "4,89"
|
|
||||||
professional qr code generator EUR 50000 0% 0% Mittel 53 "0,91" "10,80"
|
|
||||||
dynamic qr code tracking EUR 50 0% 0% Mittel 55 "1,52" "18,26"
|
|
||||||
qr barcode generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
free qr code generator with logo EUR 500 0% 0% Hoch 80 "1,31" "4,47"
|
|
||||||
qr code generator for business EUR 500 0% 900% Niedrig 17 "1,81" "7,00"
|
|
||||||
vcard qr code generator EUR 5000 0% 0% Mittel 60 "1,08" "4,10"
|
|
||||||
free qr code generator with tracking EUR 500 -90% 0% Hoch 80 "2,43" "9,97"
|
|
||||||
qr code with analytics EUR 500 0% 0% Mittel 56 "4,85" "22,14"
|
|
||||||
my qr code EUR 5000 0% 0% Mittel 59 "0,97" "3,79"
|
|
||||||
qr code designer EUR 500 0% 0% Mittel 64 "0,91" "3,63"
|
|
||||||
create dynamic qr code free EUR 500 0% 0% Hoch 82 "1,69" "4,97"
|
|
||||||
qr code for business card free EUR 500 -90% -90% Hoch 96 "2,28" "4,93"
|
|
||||||
free custom qr code generator EUR 500 0% 0% Hoch 91 "1,69" "4,83"
|
|
||||||
completely free qr code generator EUR 500 0% 0% Hoch 90 "0,92" "3,04"
|
|
||||||
vcard qr code EUR 5000 0% 0% Hoch 74 "0,97" "3,63"
|
|
||||||
custom qr code free EUR 500 0% 0% Hoch 88 "1,86" "4,97"
|
|
||||||
get a free qr code EUR 500 0% 900% Hoch 86 "1,10" "4,78"
|
|
||||||
qr gen EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
create qr free EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
|
||||||
trackable qr code generator EUR 500 0% 0% Hoch 72 "2,77" "19,03"
|
|
||||||
free code generator EUR 500 0% 0% Mittel 40 "1,00" "3,98"
|
|
||||||
custom qr EUR 500 0% 0% Hoch 87 "1,51" "4,43"
|
|
||||||
qr code for location EUR 500 -90% -90% Mittel 37 "0,79" "3,42"
|
|
||||||
create qr code for business card free EUR 50 0% 0% Hoch 100 "0,98" "4,46"
|
|
||||||
free create a qr code EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
|
||||||
qr code generator with logo EUR 5000 0% 0% Mittel 64 "1,51" "5,12"
|
|
||||||
free qr code for business EUR 50 0% 0% Hoch 88 "1,95" "4,50"
|
|
||||||
qr maker free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
facebook qr code generator EUR 5000 -90% 0% Niedrig 19 "1,26" "4,10"
|
|
||||||
create free qr code for business card EUR 50 0% 0% Hoch 98 "3,34" "4,97"
|
|
||||||
qr code com EUR 500 0% 0% Mittel 62 "0,82" "3,73"
|
|
||||||
qr code for restaurant menu EUR 500 900% 900% Hoch 73 "1,89" "8,11"
|
|
||||||
trackable qr codes EUR 500 0% 0% Hoch 70 "5,57" "34,95"
|
|
||||||
create website qr code EUR 50 0% 0% Hoch 88 "2,22" "6,88"
|
|
||||||
qr business card generator EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
|
||||||
qr code generator and tracking EUR 500 0% 0% Hoch 72 "2,77" "19,03"
|
|
||||||
qr code restaurant menu EUR 500 -90% -90% Hoch 82 "2,81" "11,90"
|
|
||||||
qr tracking EUR 500 0% 0% Niedrig 11 "1,66" "12,51"
|
|
||||||
custom qr generator EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
|
||||||
v card qr code EUR 50 0% 0% Hoch 79 "1,10" "4,16"
|
|
||||||
cool qr code generator EUR 500 0% 0% Mittel 63 "1,19" "3,87"
|
|
||||||
make a qr code for business card EUR 50 0% 0% Hoch 85 "3,17" "7,50"
|
|
||||||
qr generator for business cards EUR 50 0% 0% Hoch 84 "3,18" "5,18"
|
|
||||||
branded qr code generator EUR 50 0% 0% Hoch 83 "2,63" "10,80"
|
|
||||||
website to qr code EUR 5000 0% 0% Mittel 49 "0,68" "3,48"
|
|
||||||
create qr code with tracking EUR 50 0% 0% Niedrig 18 "3,65" "7,01"
|
|
||||||
vcard qr code free EUR 500 -90% 0% Hoch 80 "0,83" "3,58"
|
|
||||||
vcard qr EUR 5000 0% 0% Hoch 74 "0,97" "3,63"
|
|
||||||
make custom qr code EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
|
||||||
create static qr code EUR 50 0% 0% Hoch 90 "1,31" "4,10"
|
|
||||||
qr code for your business EUR 50 0% 0% Hoch 99 "2,16" "5,79"
|
|
||||||
get a qr code for my business EUR 50 0% 0% Hoch 95 "2,89" "8,41"
|
|
||||||
generate dynamic qr codes EUR 5000 0% 0% Mittel 38 "2,34" "14,52"
|
|
||||||
restaurant qr code EUR 500 0% 0% Hoch 80 "1,61" "11,55"
|
|
||||||
vcard qr code generator free EUR 500 0% 0% Hoch 81 "1,30" "4,00"
|
|
||||||
my qr EUR 5000 0% 0% Mittel 59 "0,97" "3,79"
|
|
||||||
qr code generator and analytics EUR 50 ∞ 0% Hoch 88 "6,49" "27,63"
|
|
||||||
unique qr code generator EUR 500 0% 0% Mittel 57 "1,51" "4,32"
|
|
||||||
url qr generator EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
free qr code generator free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
free qr code tracking EUR 500 0% 0% Hoch 73 "1,62" "9,39"
|
|
||||||
menu qr code generator EUR 50 0% 0% Hoch 84 "2,58" "5,01"
|
|
||||||
generate static qr code EUR 50 0% 0% Hoch 90 "1,22" "3,11"
|
|
||||||
unlimited qr code generator EUR 500 -90% -90% Hoch 82 "1,33" "3,70"
|
|
||||||
vcard qr generator EUR 5000 0% 0% Mittel 60 "1,08" "4,10"
|
|
||||||
qr code generator phone number EUR 500 -90% 0% Hoch 75 "0,74" "3,88"
|
|
||||||
create qr code from url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
business qr code generator free EUR 50 0% 0% Hoch 93 "1,96" "4,72"
|
|
||||||
make qr code for business card EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
|
||||||
create a website qr code EUR 50 0% 0% Hoch 88 "2,61" "7,92"
|
|
||||||
qr card EUR 5000 -90% 0% Hoch 100 "1,35" "4,74"
|
|
||||||
dynamic qr code pricing EUR 50 0% -90% Mittel 62 "1,89" "11,48"
|
|
||||||
ai qr code generator EUR 500 0% 0% Mittel 39 "0,87" "3,54"
|
|
||||||
qr code de EUR 50 -90% 0% Mittel 55 "0,59" "5,28"
|
|
||||||
qr code create free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
make a dynamic qr code EUR 50 0% 0% Hoch 88 "1,80" "7,05"
|
|
||||||
create my qr code EUR 500 -90% -90% Hoch 88 "1,08" "4,05"
|
|
||||||
qr code menu free EUR 50 0% -90% Mittel 56 "1,77" "5,39"
|
|
||||||
creating a qr code for business EUR 50 0% 0% Hoch 86 "2,30" "8,64"
|
|
||||||
create qr barcode EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
|
||||||
qr card generator EUR 50 0% 0% Hoch 75 "1,26" "3,70"
|
|
||||||
dynamic qr free EUR 50 0% 0% Hoch 86 "0,46" "3,73"
|
|
||||||
free barcode EUR 5000 0% 0% Niedrig 6 "0,64" "3,16"
|
|
||||||
generate qr codes for free EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
create a qr code for my business EUR 50 0% 0% Hoch 89 "4,10" "9,04"
|
|
||||||
vcard qr code business card EUR 50 0% 0% Mittel 53 "3,35" "7,32"
|
|
||||||
create a qr code for your business EUR 50 0% 0% Hoch 93 "2,54" "5,08"
|
|
||||||
edit qr code EUR 500 0% 0% Mittel 37 "0,90" "6,55"
|
|
||||||
url code generator EUR 500 0% 0% Mittel 66 "1,19" "3,61"
|
|
||||||
qr qr code generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
free unlimited qr code generator EUR 500 0% 0% Hoch 89 "0,93" "3,06"
|
|
||||||
event qr code EUR 500 0% 0% Mittel 43 "1,48" "7,61"
|
|
||||||
qr code generator contact card EUR 500 0% 0% Mittel 46 "2,35" "6,17"
|
|
||||||
free code qr generator EUR 50 0% 0% Hoch 73 "0,89" "3,63"
|
|
||||||
make a qr code for business EUR 50 0% 0% Hoch 88 "1,76" "5,26"
|
|
||||||
create a static qr code EUR 50 0% 0% Hoch 85 "1,51" "4,36"
|
|
||||||
create trackable qr code EUR 50 0% 0% Hoch 74 "5,22" "22,03"
|
|
||||||
produce qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
|
||||||
make dynamic qr code EUR 50 0% 0% Hoch 86 "3,06" "6,45"
|
|
||||||
qr code generator from url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
qr code for my business EUR 50 0% 0% Hoch 97 "2,46" "7,58"
|
|
||||||
my qr generator EUR 50 0% 0% Hoch 93 "0,85" "3,31"
|
|
||||||
free qr code generator with analytics EUR 50 0% 0% Hoch 93 "2,73" "7,89"
|
|
||||||
make your own qr code free EUR 500 0% 0% Hoch 86 "1,43" "4,11"
|
|
||||||
static qr codes generator EUR 50 0% 0% Mittel 51 "1,10" "3,70"
|
|
||||||
create your own qr code free EUR 500 0% 0% Hoch 86 "1,43" "4,11"
|
|
||||||
design qr code generator EUR 500 0% 0% Niedrig 32 "0,95" "3,36"
|
|
||||||
static qr generator EUR 50 0% 0% Hoch 87 "0,91" "4,14"
|
|
||||||
qr codes pro EUR 500 0% 0% Niedrig 28 "1,23" "8,93"
|
|
||||||
qr code with tracking free EUR 500 0% 0% Hoch 73 "1,62" "9,39"
|
|
||||||
website to qr code generator EUR 50 -90% 0% Hoch 77 "0,95" "4,10"
|
|
||||||
facebook qr code generator free EUR 500 0% 0% Niedrig 15 "1,27" "3,63"
|
|
||||||
make your qr code EUR 500 0% 0% Hoch 69 "1,35" "5,19"
|
|
||||||
smart qr code generator EUR 50 0% 0% Mittel 44 "1,09" "5,84"
|
|
||||||
creating qr code business card EUR 5000 0% 0% Mittel 42 "2,95" "7,03"
|
|
||||||
qr barcode generator free EUR 5000 0% 0% Hoch 76 "1,21" "3,62"
|
|
||||||
create qr code from website EUR 50 0% 0% Hoch 93 "1,13" "4,59"
|
|
||||||
branded qr codes EUR 500 0% 0% Mittel 54 "1,84" "10,35"
|
|
||||||
create free dynamic qr code EUR 50 0% 0% Hoch 90 "1,84" "5,87"
|
|
||||||
free url qr code generator EUR 50 0% 0% Hoch 87 "1,18" "3,63"
|
|
||||||
generate website qr code EUR 50000 0% 0% Niedrig 17 "1,88" "6,16"
|
|
||||||
create your qr code EUR 500 0% 0% Hoch 69 "1,35" "5,19"
|
|
||||||
create the qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
|
||||||
make my qr code EUR 500 -90% -90% Hoch 88 "1,08" "4,05"
|
|
||||||
event qr code generator EUR 50 0% 0% Mittel 57 "1,38" "4,25"
|
|
||||||
paypal qr code EUR 5000 0% 0% Niedrig 9 "1,03" "6,79"
|
|
||||||
unique qr codes EUR 500 0% 0% Niedrig 30 "1,21" "5,02"
|
|
||||||
generate qr code for url free EUR 50 0% 0% Hoch 86 "1,27" "3,63"
|
|
||||||
create business qr code EUR 50 0% 0% Hoch 96 "2,97" "5,82"
|
|
||||||
generate the qr code EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
qr code generator free business card EUR 50 0% 0% Mittel 60 "1,72" "4,10"
|
|
||||||
qr code generator business EUR 500 0% 0% Hoch 75 "3,51" "7,51"
|
|
||||||
qr code generator for business card free EUR 50 0% 0% Hoch 92 "2,60" "4,10"
|
|
||||||
cool qr codes EUR 500 0% 0% Niedrig 25 "1,16" "3,82"
|
|
||||||
create custom qr code free EUR 50 0% 0% Hoch 89 "2,02" "5,06"
|
|
||||||
qr code for EUR 500 0% 0% Niedrig 12 "1,04" "3,52"
|
|
||||||
qr code generator ai EUR 500 0% 900% Mittel 53 "0,87" "3,63"
|
|
||||||
barcode create EUR 5000 0% 0% Mittel 35 "0,78" "3,15"
|
|
||||||
create a qr code for a url free EUR 50 0% 0% Hoch 90 "1,51" "4,86"
|
|
||||||
url to qr code generator free EUR 50 0% 0% Hoch 81 "1,22" "3,11"
|
|
||||||
create a qr code for business card free EUR 50 0% 0% Hoch 93 "3,63" "5,66"
|
|
||||||
generate a qr EUR 50000 0% 0% Hoch 81 "0,86" "3,52"
|
|
||||||
free qr code designer EUR 50 0% 0% Hoch 90 "1,27" "3,34"
|
|
||||||
dynamic qr code cost EUR 50 0% 0% Mittel 60 "1,62" "10,52"
|
|
||||||
custom logo qr code EUR 500 0% 0% Hoch 98 "2,27" "7,67"
|
|
||||||
instant qr code generator EUR 50 0% 0% Hoch 78 "1,30" "3,63"
|
|
||||||
create menu qr code free EUR 50 0% 0% Hoch 74 "4,10" "6,74"
|
|
||||||
qr code generator qr code generator qr code generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
create vcard qr code EUR 5000 0% 0% Mittel 60 "1,08" "4,10"
|
|
||||||
get qr code for business EUR 50 0% 0% Hoch 100 "3,61" "4,62"
|
|
||||||
qr code sign up EUR 50 0% 0% Mittel 52 "1,44" "9,17"
|
|
||||||
with qr code EUR 50 0% 0% Niedrig 22 "1,00" "3,92"
|
|
||||||
create a business qr code EUR 50 0% 0% Hoch 96 "4,50" "11,66"
|
|
||||||
create qr for free EUR 50 0% 0% Hoch 89 "1,31" "3,63"
|
|
||||||
free make a qr code EUR 50 0% 0% Hoch 83 "1,28" "3,32"
|
|
||||||
qr code for menu free EUR 50 0% 0% Hoch 80 "2,31" "5,12"
|
|
||||||
qr code pricing EUR 500 0% 0% Mittel 58 "0,99" "5,66"
|
|
||||||
qr code generator permanent EUR 500 0% 900% Hoch 78 "1,22" "4,10"
|
|
||||||
free trackable qr code EUR 500 0% 0% Hoch 73 "1,62" "9,39"
|
|
||||||
create free qr code business card EUR 500 0% 0% Hoch 85 "2,04" "4,44"
|
|
||||||
create free qr code generator EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
instagram qr code generator free EUR 50 0% 0% Hoch 91 "1,09" "3,63"
|
|
||||||
develop a qr code EUR 50 0% 0% Hoch 83 "1,60" "5,88"
|
|
||||||
free business card qr code EUR 500 -90% 0% Hoch 97 "1,92" "4,84"
|
|
||||||
free qr code generator contact card EUR 50 0% 0% Hoch 88 "1,37" "4,04"
|
|
||||||
qr code qr code generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
create static qr code free EUR 50 0% 0% Hoch 81 "0,93" "3,36"
|
|
||||||
qr code from website EUR 50 0% 0% Hoch 85 "0,82" "3,20"
|
|
||||||
qr code generator for phone number EUR 500 -90% 0% Mittel 56 "0,79" "3,27"
|
|
||||||
create a qr code for an event EUR 50 0% 0% Hoch 74 "1,64" "5,94"
|
|
||||||
create qr scan code EUR 50 0% 0% Hoch 88 "1,27" "3,71"
|
|
||||||
create business card qr code free EUR 50 0% 0% Hoch 92 "2,66" "4,59"
|
|
||||||
advanced qr code generator EUR 50 0% 0% Mittel 42 "1,29" "6,91"
|
|
||||||
to create a qr code EUR 50 0% 0% Hoch 89 "2,93" "6,04"
|
|
||||||
make a qr code generator EUR 50 0% 0% Hoch 68 "1,27" "3,44"
|
|
||||||
qr restaurant menu EUR 50 0% 0% Mittel 35 "2,52" "28,99"
|
|
||||||
qr code vcard free EUR 50 0% 0% Hoch 84 "0,86" "4,96"
|
|
||||||
create barcode free EUR 500 0% 0% Mittel 39 "0,93" "3,34"
|
|
||||||
free business qr code EUR 50 0% 0% Hoch 95 "2,29" "4,10"
|
|
||||||
produce a qr code EUR 50000 0% 0% Hoch 84 "1,20" "3,94"
|
|
||||||
create qr code for event EUR 50 0% 0% Hoch 90 "1,12" "9,73"
|
|
||||||
website to qr code free EUR 50 0% 0% Hoch 85 "0,86" "3,61"
|
|
||||||
free qr code vcard EUR 50 0% 0% Hoch 99 "1,87" "3,63"
|
|
||||||
create qr generator EUR 50000 0% 0% Hoch 81 "0,86" "3,52"
|
|
||||||
qr design EUR 5000 -90% -90% Hoch 93 "0,93" "3,20"
|
|
||||||
free qr code menu EUR 50 0% 0% Niedrig 29 "1,57" "4,96"
|
|
||||||
custom free qr code EUR 50 0% 0% Hoch 95 "1,79" "4,64"
|
|
||||||
difference between static and dynamic qr code EUR 50 0% 0% Niedrig 24 "0,32" "10,47"
|
|
||||||
bulk qr generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
|
||||||
create cool qr codes EUR 50 0% 0% Hoch 94 "1,19" "5,18"
|
|
||||||
create unique qr code EUR 500 0% 0% Mittel 57 "1,51" "4,32"
|
|
||||||
produce qr code free EUR 50 0% 0% Hoch 100 "2,55" "4,54"
|
|
||||||
free contact qr code generator EUR 50 0% 0% Hoch 92 "1,31" "3,63"
|
|
||||||
professional qr code EUR 50 0% 0% Mittel 38 "1,75" "6,26"
|
|
||||||
menu qr codes EUR 500 0% 0% Hoch 71 "1,63" "5,60"
|
|
||||||
free qr code contact card EUR 50 0% 0% Hoch 86 "1,85" "3,80"
|
|
||||||
bulk create qr codes EUR 50 0% 0% Hoch 83 "3,25" "7,78"
|
|
||||||
generate qr code from website EUR 50 0% 0% Hoch 98 "1,47" "3,63"
|
|
||||||
create qr code from url free EUR 500 -90% 0% Hoch 92 "1,42" "3,70"
|
|
||||||
sign in qr code generator EUR 50 0% 0% Hoch 71 "2,16" "4,34"
|
|
||||||
custom design qr codes EUR 500 0% 0% Hoch 74 "1,38" "5,00"
|
|
||||||
tracking qr code scans EUR 500 0% -90% Mittel 52 "3,03" "16,93"
|
|
||||||
unlimited qr codes EUR 50 0% 0% Hoch 86 "1,04" "4,16"
|
|
||||||
create branded qr code EUR 50 0% 0% Hoch 79 "2,53" "17,43"
|
|
||||||
qr code generator business card free EUR 50 0% ∞ Hoch 95 "2,55" "5,69"
|
|
||||||
tracking barcode EUR 5000 0% -90% Mittel 47 "1,35" "4,34"
|
|
||||||
static qr EUR 500 -90% 0% Mittel 62 "0,90" "3,64"
|
|
||||||
generate qr code for business EUR 50 0% 0% Hoch 95 "4,11" "11,03"
|
|
||||||
free url to qr code EUR 50 0% 0% Hoch 87 "1,01" "3,63"
|
|
||||||
qr business code EUR 50 0% 0% Hoch 91 "3,30" "9,11"
|
|
||||||
v card qr code generator EUR 50 0% 0% Hoch 82 "1,28" "4,10"
|
|
||||||
free dynamic qr EUR 500 0% 0% Hoch 79 "1,27" "4,58"
|
|
||||||
create facebook qr code free EUR 50 0% 0% Hoch 94 "1,04" "4,43"
|
|
||||||
create my qr code free EUR 50 0% 0% Hoch 89 "2,43" "11,68"
|
|
||||||
basic qr code generator EUR 50 0% 0% Hoch 87 "1,08" "3,12"
|
|
||||||
need a qr code EUR 50 0% 0% Hoch 79 "1,18" "5,62"
|
|
||||||
purchase qr codes EUR 500 0% 0% Hoch 78 "2,78" "11,52"
|
|
||||||
qr location EUR 500 -90% -90% Mittel 37 "0,79" "3,42"
|
|
||||||
create free static qr code EUR 50 0% 0% Hoch 80 "1,06" "3,13"
|
|
||||||
ai qr code EUR 500 0% 0% Niedrig 14 "0,76" "4,66"
|
|
||||||
qr code generator with analytics free EUR 50 0% 0% Hoch 90 "3,15" "5,12"
|
|
||||||
need qr code EUR 50 0% 0% Hoch 76 "1,33" "4,97"
|
|
||||||
buy dynamic qr code EUR 50 ∞ 0% Niedrig 25 "1,87" "12,81"
|
|
||||||
to make a qr code EUR 50 0% 0% Mittel 65 "1,58" "3,63"
|
|
||||||
qr tracking code EUR 50 0% 0% Hoch 82 "1,10" "3,60"
|
|
||||||
sms qr code EUR 500 0% 0% Niedrig 17 "0,60" "3,35"
|
|
||||||
qr code tools EUR 500 900% 900% Mittel 34 "0,71" "4,11"
|
|
||||||
dynamic code generator EUR 50 0% 0% Niedrig 17 "3,82" "15,61"
|
|
||||||
qr code for business free EUR 50 0% 0% Hoch 91 "1,46" "3,63"
|
|
||||||
free qr code contact generator EUR 50 0% 0% Hoch 84 "1,06" "3,34"
|
|
||||||
free qr code generator no subscription EUR 50 0% 0% Hoch 90 "0,79" "3,09"
|
|
||||||
create your qr code free EUR 50 0% 0% Hoch 79 "1,79" "4,10"
|
|
||||||
free dynamic qr code generator with logo EUR 50 0% 0% Mittel 43 "0,48" "3,97"
|
|
||||||
sms qr code generator EUR 500 0% 0% Niedrig 18 "0,66" "3,11"
|
|
||||||
create a qr free EUR 50 0% 0% Hoch 94 "2,12" "3,63"
|
|
||||||
qr code generator generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
qr code generator account EUR 50 0% 0% Mittel 44 "1,51" "7,59"
|
|
||||||
make your own qr codes EUR 5000 0% 0% Hoch 79 "1,38" "4,11"
|
|
||||||
qr tag generator EUR 500000 -90% 0% Hoch 74 "0,63" "3,12"
|
|
||||||
free trackable qr code generator EUR 500 -90% 0% Hoch 80 "2,43" "9,97"
|
|
||||||
dynamic qr code free generator EUR 50 0% 0% Hoch 95 "2,50" "4,88"
|
|
||||||
create a unique qr code EUR 50 0% 0% Hoch 96 "2,58" "4,89"
|
|
||||||
free menu qr code generator EUR 50 0% 0% Hoch 95 "2,35" "3,38"
|
|
||||||
trackable qr code generator free EUR 50 0% 0% Hoch 95 "4,38" "8,26"
|
|
||||||
free code qr EUR 50000 0% 0% Hoch 86 "1,25" "3,63"
|
|
||||||
create qr code with url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
difference between static and dynamic qr codes EUR 50 0% 0% Hoch 89 "0,61" "18,51"
|
|
||||||
free dynamic qr generator EUR 500 0% 0% Hoch 82 "1,69" "4,97"
|
|
||||||
my qr code generator free EUR 50 0% 0% Hoch 83 "1,51" "4,92"
|
|
||||||
qr code provider EUR 50 0% 0% Mittel 40 "1,85" "7,42"
|
|
||||||
qr analytics EUR 500 0% 0% Mittel 56 "4,85" "22,14"
|
|
||||||
qr code generator with custom logo EUR 50 0% 0% Hoch 88 "3,95" "10,07"
|
|
||||||
to make qr code EUR 50 0% 0% Mittel 52 "1,46" "4,59"
|
|
||||||
static and dynamic qr codes EUR 50 0% 0% Mittel 64 "0,42" "5,07"
|
|
||||||
get qr code generator EUR 50 0% 0% Hoch 80 "1,02" "3,86"
|
|
||||||
qr code generator with url EUR 50 0% 0% Hoch 90 "2,65" "5,00"
|
|
||||||
create code qr free EUR 50 0% 0% Hoch 93 "1,96" "3,63"
|
|
||||||
create qr code of website EUR 50 0% 0% Hoch 97 "2,15" "4,93"
|
|
||||||
qr code generator free unlimited EUR 50 0% 0% Hoch 94 "1,18" "3,63"
|
|
||||||
create qr code for EUR 50 0% 0% Hoch 68 "1,14" "3,63"
|
|
||||||
the qr generator EUR 50 0% 0% Mittel 60 "0,98" "3,99"
|
|
||||||
bulk qr code generator from excel EUR 50 0% 0% Mittel 42 "1,22" "4,36"
|
|
||||||
make url qr code EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
qr code and tracking EUR 5000 -90% -90% Mittel 64 "1,62" "14,63"
|
|
||||||
make qr code for location EUR 50 0% 0% Hoch 86 "1,37" "4,01"
|
|
||||||
sign up qr code EUR 50 0% 0% Niedrig 23 "1,81" "5,80"
|
|
||||||
custom branded qr codes EUR 50 0% 0% Hoch 100 "2,33" "13,48"
|
|
||||||
build qr code generator EUR 50 0% 0% Hoch 80 "1,02" "3,86"
|
|
||||||
free qr code business card generator EUR 50 0% 0% Mittel 60 "1,72" "4,10"
|
|
||||||
generate qr code with url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
qr create free EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
|
||||||
make qr code from website EUR 50 0% 0% Hoch 92 "1,48" "3,53"
|
|
||||||
qr bulk generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
|
||||||
qr code s EUR 500 0% 0% Niedrig 27 "0,57" "4,92"
|
|
||||||
create your own qr codes EUR 5000 0% 0% Hoch 79 "1,38" "4,11"
|
|
||||||
restaurant menu qr code free EUR 50 0% 0% Hoch 95 "1,78" "8,90"
|
|
||||||
event qr code generator free EUR 50 0% 0% Hoch 73 "1,55" "4,53"
|
|
||||||
qr signs EUR 5000 -90% -90% Hoch 100 "0,65" "4,49"
|
|
||||||
log in qr code generator EUR 5000 0% 0% Niedrig 15 "0,67" "3,07"
|
|
||||||
instant qr code EUR 50 0% 0% Hoch 77 "1,86" "4,80"
|
|
||||||
custom qr code designer EUR 50 0% 0% Hoch 90 "3,13" "4,88"
|
|
||||||
create a url qr code EUR 50 0% 0% Hoch 89 "2,80" "5,01"
|
|
||||||
the qr code generator free EUR 50 0% 0% Hoch 82 "1,06" "3,63"
|
|
||||||
new qr code generator EUR 50 0% 0% Hoch 79 "1,51" "4,58"
|
|
||||||
trackable qr code free EUR 50 0% 0% Hoch 88 "3,62" "12,45"
|
|
||||||
free personalized qr code generator EUR 50 0% 0% Hoch 93 "0,81" "3,63"
|
|
||||||
qr code generator from website EUR 50 0% 0% Hoch 98 "1,28" "4,12"
|
|
||||||
create qr code for instagram free EUR 50 0% 0% Hoch 95 "1,51" "4,46"
|
|
||||||
digital qr code generator EUR 50 0% 0% Mittel 45 "1,84" "4,17"
|
|
||||||
qr png generator EUR 50 0% 0% Hoch 74 "1,28" "3,33"
|
|
||||||
create custom qr EUR 500 0% 0% Mittel 51 "2,44" "7,89"
|
|
||||||
make qr code for business EUR 50 0% 0% Hoch 100 "2,49" "3,36"
|
|
||||||
contact card qr code generator EUR 50 0% 0% Hoch 89 "1,68" "4,94"
|
|
||||||
qr printing EUR 500 0% 0% Mittel 46 "0,74" "7,53"
|
|
||||||
qr for business EUR 5000 0% 0% Hoch 97 "2,57" "7,95"
|
|
||||||
permanent qr code generator free EUR 500 0% 900% Hoch 85 "1,03" "3,57"
|
|
||||||
free menu qr code EUR 50 0% 0% Hoch 97 "1,94" "4,53"
|
|
||||||
generate qr code for a url EUR 50 0% 0% Hoch 94 "2,17" "5,95"
|
|
||||||
manage qr codes EUR 500 0% -90% Niedrig 30 "2,11" "18,77"
|
|
||||||
qr generator custom EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
|
||||||
qr code to phone number EUR 50 0% 0% Hoch 75 "0,74" "3,16"
|
|
||||||
customer qr code EUR 50 0% 0% Mittel 53 "1,80" "3,72"
|
|
||||||
website url qr code generator EUR 50 0% 0% Hoch 77 "1,65" "4,23"
|
|
||||||
square barcode generator EUR 500 0% 0% Mittel 45 "0,84" "8,47"
|
|
||||||
website to create qr code EUR 50 0% 0% Hoch 87 "1,89" "4,62"
|
|
||||||
scan barcode generator EUR 500 0% 0% Niedrig 27 "0,84" "5,11"
|
|
||||||
obtain a qr code EUR 50000 -90% 0% Hoch 82 "0,89" "3,63"
|
|
||||||
qr code generator for my website EUR 500 0% 0% Hoch 87 "4,10" "10,77"
|
|
||||||
generate your qr code EUR 500 0% 0% Hoch 69 "1,35" "5,19"
|
|
||||||
generate qr code from url free EUR 50 0% 0% Hoch 84 "0,38" "3,93"
|
|
||||||
create qr code for menu free EUR 50 0% ∞ Hoch 86 "2,03" "5,10"
|
|
||||||
qr business EUR 5000 0% 0% Hoch 97 "2,57" "7,95"
|
|
||||||
qr code generator site EUR 50000 0% 0% Niedrig 17 "1,88" "6,16"
|
|
||||||
code create EUR 50 -90% 0% Niedrig 3 "0,62" "6,98"
|
|
||||||
dynamic codes EUR 500 0% 0% Niedrig 12 "0,78" "4,21"
|
|
||||||
qr code analytics free EUR 50 0% 0% Hoch 86 "1,32" "5,57"
|
|
||||||
free url to qr code generator EUR 50 0% 0% Hoch 91 "0,94" "3,65"
|
|
||||||
personalized qr code generator EUR 50 0% 0% Hoch 90 "1,75" "6,74"
|
|
||||||
custom logo qr code generator EUR 500 0% 0% Hoch 68 "2,73" "7,71"
|
|
||||||
restaurant qr EUR 500 0% 0% Hoch 80 "1,61" "11,55"
|
|
||||||
generate facebook qr code EUR 5000 -90% 0% Niedrig 19 "1,26" "4,10"
|
|
||||||
qr contact card generator EUR 500 0% 0% Mittel 46 "2,35" "6,17"
|
|
||||||
free qr generator code EUR 50 0% 0% Hoch 90 "1,69" "4,10"
|
|
||||||
dynamic url qr code EUR 50 0% 0% Hoch 82 "5,45" "18,45"
|
|
||||||
free qr code analytics EUR 50 0% 0% Hoch 86 "1,32" "5,57"
|
|
||||||
generate qr code for phone number EUR 50 0% 0% Hoch 79 "0,76" "3,37"
|
|
||||||
free qr code generator and tracking EUR 500 -90% 0% Hoch 80 "2,43" "9,97"
|
|
||||||
generate unique qr code EUR 500 0% 0% Mittel 57 "1,51" "4,32"
|
|
||||||
youtube qr code generator free EUR 50 0% 0% Hoch 96 "1,21" "3,63"
|
|
||||||
customize qr code generator EUR 50 0% 0% Hoch 83 "1,16" "6,79"
|
|
||||||
make a qr scan code EUR 50 0% 0% Hoch 88 "1,27" "3,71"
|
|
||||||
create a qr code for location EUR 50 0% 0% Hoch 100 "1,91" "3,60"
|
|
||||||
url to qr generator EUR 50 0% 0% Hoch 89 "0,20" "9,36"
|
|
||||||
business qr EUR 5000 0% 0% Hoch 97 "2,57" "7,95"
|
|
||||||
edit a qr code EUR 500 0% 0% Mittel 37 "0,90" "6,55"
|
|
||||||
generate qr codes in bulk EUR 50 0% 0% Hoch 100 "2,07" "8,02"
|
|
||||||
qr code generator freeware EUR 500000 -90% 0% Hoch 86 "1,24" "3,64"
|
|
||||||
automated qr code generator EUR 50 0% 0% Hoch 86 "0,93" "3,18"
|
|
||||||
create qr code to email EUR 50 0% 0% Hoch 82 "0,86" "4,06"
|
|
||||||
free png qr code generator EUR 50 0% 0% Hoch 88 "1,55" "5,87"
|
|
||||||
free qr code generator for url EUR 50 0% 0% Hoch 89 "1,51" "4,10"
|
|
||||||
create a qr code from website EUR 50 0% 0% Hoch 77 "2,58" "6,51"
|
|
||||||
qr code qr EUR 5000 0% 0% Niedrig 0 "1,03" "3,01"
|
|
||||||
make qr code generator free EUR 500 0% 0% Hoch 83 "1,51" "3,63"
|
|
||||||
qr code generator custom logo EUR 500 0% 0% Hoch 68 "2,73" "7,71"
|
|
||||||
make your qr code free EUR 50 0% 0% Hoch 79 "1,79" "4,10"
|
|
||||||
free qr business cards EUR 500 -90% 0% Hoch 97 "1,92" "4,84"
|
|
||||||
create qr barcode free EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
|
||||||
qr code generator barcode EUR 50 0% 0% Mittel 43 "0,97" "4,06"
|
|
||||||
free sms qr code generator EUR 50 0% 0% Niedrig 32 "0,81" "3,03"
|
|
||||||
difference between dynamic and static qr code EUR 50 0% 0% Hoch 70 "0,23" "5,71"
|
|
||||||
qr code generator free for phone number EUR 50 0% ∞ Hoch 100 "0,37" "3,63"
|
|
||||||
creating scan codes EUR 50000 0% 0% Hoch 84 "1,68" "4,83"
|
|
||||||
create scan code free EUR 500 0% 0% Hoch 87 "1,28" "4,10"
|
|
||||||
free qr code generator for vcard EUR 500 0% 0% Hoch 81 "1,30" "4,00"
|
|
||||||
qr code generator for contact card EUR 500 0% 0% Mittel 46 "2,35" "6,17"
|
|
||||||
qr contact code generator EUR 500 0% 0% Hoch 75 "1,32" "4,73"
|
|
||||||
create own qr code free EUR 50 0% 0% Hoch 85 "0,87" "4,15"
|
|
||||||
qr to code EUR 50 0% 0% Niedrig 21 "1,00" "3,78"
|
|
||||||
free branded qr code generator EUR 50 0% 0% Hoch 100 "1,81" "3,52"
|
|
||||||
qr code free tracking EUR 500 0% 0% Hoch 73 "1,62" "9,39"
|
|
||||||
free qr code generator unlimited EUR 50 0% 0% Hoch 85 "1,08" "3,27"
|
|
||||||
create your own free qr code EUR 500 0% 0% Hoch 86 "1,43" "4,11"
|
|
||||||
customize a qr code EUR 5000 0% 0% Mittel 57 "1,83" "6,10"
|
|
||||||
modify qr code EUR 500 0% 0% Mittel 37 "0,90" "6,55"
|
|
||||||
create youtube qr code EUR 50 0% 0% Hoch 71 "0,86" "3,17"
|
|
||||||
vcard qr free EUR 50 0% 0% Hoch 95 "1,56" "3,63"
|
|
||||||
build qr codes EUR 50 -90% 0% Hoch 89 "0,95" "4,10"
|
|
||||||
create qr code free from url EUR 500 0% 900% Hoch 89 "1,74" "4,85"
|
|
||||||
barcode and qr code generator EUR 50 0% 0% Mittel 43 "0,97" "4,06"
|
|
||||||
make free qr EUR 50 0% 0% Hoch 89 "0,85" "4,10"
|
|
||||||
digital restaurant menu qr code EUR 50 0% 0% Hoch 95 "1,68" "5,15"
|
|
||||||
qr code generator for EUR 50 0% 0% Hoch 96 "0,93" "3,00"
|
|
||||||
make qr barcode EUR 50000 0% 0% Hoch 72 "0,71" "3,34"
|
|
||||||
generate a qr code from a url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
generator free EUR 500 0% 0% Niedrig 16 "0,82" "5,79"
|
|
||||||
qr campaign EUR 50 0% -90% Niedrig 19 "1,45" "10,66"
|
|
||||||
free qr code editor EUR 50 0% 0% Hoch 75 "1,32" "3,18"
|
|
||||||
square codes EUR 5000 0% 0% Niedrig 5 "0,83" "3,42"
|
|
||||||
free qr code for my business EUR 50 0% 0% Hoch 90 "2,15" "4,10"
|
|
||||||
generate qr code in bulk EUR 50 0% 0% Hoch 100 "2,07" "8,02"
|
|
||||||
create custom qr codes free EUR 50 0% 0% Hoch 100 "2,23" "6,61"
|
|
||||||
smart qr codes EUR 500 900% 900% Mittel 36 "1,13" "6,17"
|
|
||||||
create qr code for url free EUR 50 0% 0% Hoch 90 "1,51" "4,86"
|
|
||||||
qr code with brand logo EUR 50 0% 0% Hoch 88 "2,39" "4,41"
|
|
||||||
barcode to qr code generator EUR 50 0% 0% Niedrig 27 "1,89" "8,80"
|
|
||||||
i qr code EUR 50 0% 0% Niedrig 11 "1,25" "3,95"
|
|
||||||
qr code generator with logo for free EUR 500 0% 0% Hoch 80 "1,31" "4,47"
|
|
||||||
qr code generator no tracking EUR 50 0% 0% Hoch 76 "0,96" "3,93"
|
|
||||||
qr code unlimited EUR 500 0% 0% Mittel 66 "1,00" "6,13"
|
|
||||||
csv qr code generator EUR 50 0% 0% Mittel 65 "1,02" "5,95"
|
|
||||||
design qr code free EUR 50 0% 0% Hoch 97 "1,45" "4,05"
|
|
||||||
qr code generator card EUR 50 0% 0% Hoch 75 "1,26" "3,70"
|
|
||||||
qr design generator EUR 500 0% 0% Niedrig 32 "0,95" "3,36"
|
|
||||||
qr code permanent generator EUR 50 0% 0% Hoch 74 "0,56" "5,09"
|
|
||||||
free generate qr code for url EUR 50 0% 0% Mittel 48 "1,27" "3,19"
|
|
||||||
qr code i EUR 50 0% 0% Niedrig 11 "1,25" "3,95"
|
|
||||||
every qr code EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
|
||||||
view qr code EUR 50 0% 0% Niedrig 13 "0,65" "3,23"
|
|
||||||
code creation EUR 50 -90% 0% Niedrig 12 "2,14" "5,68"
|
|
||||||
create own qr code generator EUR 50 0% -90% Hoch 79 "1,21" "3,71"
|
|
||||||
credit card qr code EUR 50 0% 0% Niedrig 7 "2,64" "9,81"
|
|
||||||
digital qr codes EUR 500 -90% 0% Mittel 63 "1,15" "4,49"
|
|
||||||
qr generator for url EUR 50 0% 0% Hoch 91 "1,10" "7,17"
|
|
||||||
qr code bulk generator free EUR 50 0% 0% Hoch 95 "0,74" "3,18"
|
|
||||||
dynamic barcode EUR 50 0% 0% Niedrig 33 "1,44" "5,12"
|
|
||||||
event qr EUR 500 0% 0% Mittel 43 "1,48" "7,61"
|
|
||||||
custom code generator EUR 50 0% 0% Niedrig 32 "1,79" "8,74"
|
|
||||||
qr codes marketing EUR 500 0% 0% Niedrig 33 "1,61" "10,10"
|
|
||||||
recommended qr code generator EUR 5000 0% 0% Hoch 78 "1,63" "8,36"
|
|
||||||
create a scan qr code EUR 50 0% 0% Hoch 96 "2,22" "4,68"
|
|
||||||
qr code with vcard EUR 5000 0% 0% Hoch 74 "0,97" "3,63"
|
|
||||||
make a qr code for text EUR 50 0% 0% Hoch 71 "1,28" "3,34"
|
|
||||||
logo qr generator EUR 5000 0% 0% Mittel 64 "1,51" "5,12"
|
|
||||||
contact card qr generator EUR 50 0% 0% Hoch 82 "1,46" "3,63"
|
|
||||||
qr barcode free EUR 50000 0% 0% Hoch 86 "1,25" "3,63"
|
|
||||||
qr code scan device EUR 500 0% -90% Hoch 100 "0,37" "3,50"
|
|
||||||
all qr codes EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
|
||||||
cost for qr code EUR 500 0% 0% Mittel 58 "0,99" "5,66"
|
|
||||||
free qr tracking EUR 50 0% 0% Hoch 86 "1,49" "8,90"
|
|
||||||
generator for free EUR 500 0% 0% Niedrig 16 "0,82" "5,79"
|
|
||||||
create paypal qr code EUR 50 0% 0% Mittel 36 "1,79" "4,70"
|
|
||||||
generate qr code of url EUR 5000 0% -90% Hoch 84 "1,43" "4,85"
|
|
||||||
generate paypal qr code EUR 50 0% 0% Niedrig 30 "1,54" "4,76"
|
|
||||||
email qr code free EUR 50 0% 0% Hoch 68 "0,50" "3,48"
|
|
||||||
cost of qr codes EUR 500 0% 0% Mittel 58 "0,99" "5,66"
|
|
||||||
the best qr code generator free EUR 50 0% 0% Hoch 82 "1,00" "3,63"
|
|
||||||
qr code brand EUR 500 0% 0% Niedrig 4 "1,55" "8,10"
|
|
||||||
qr all EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
|
||||||
create qr reader EUR 50 0% 0% Hoch 82 "1,28" "3,60"
|
|
||||||
generate barcode qr EUR 50 0% 0% Mittel 43 "0,97" "4,06"
|
|
||||||
create barcode url EUR 50 0% 0% Hoch 77 "0,83" "3,43"
|
|
||||||
logos in qr codes EUR 5000 0% 0% Hoch 86 "1,20" "4,78"
|
|
||||||
create a paypal qr code EUR 50 0% 0% Mittel 45 "0,95" "5,00"
|
|
||||||
qr code save EUR 50 0% 0% Niedrig 19 "0,83" "3,47"
|
|
||||||
make a qr scanner EUR 500 0% 0% Mittel 56 "0,99" "4,22"
|
|
||||||
qr code freeware EUR 5000 0% 0% Hoch 84 "0,94" "3,33"
|
|
||||||
barcode scan code EUR 500 0% 0% Hoch 97 "0,81" "3,80"
|
|
||||||
qr code digital EUR 50 0% 0% Mittel 45 "0,59" "6,18"
|
|
||||||
qr code from vcard EUR 5000 0% 0% Hoch 74 "0,97" "3,63"
|
|
||||||
qr generator vcard free EUR 500 0% 0% Hoch 81 "1,30" "4,00"
|
|
||||||
make cool qr code EUR 50 0% 0% Hoch 83 "1,45" "3,96"
|
|
||||||
all qr EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
|
||||||
qr do EUR 50 0% 0% Niedrig 15 "0,85" "4,10"
|
|
||||||
qr events EUR 500 0% 0% Mittel 43 "1,48" "7,61"
|
|
||||||
call qr EUR 50 0% 0% Mittel 37 "1,00" "4,12"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Keyword Stats 2026-01-22 at 11_03_27
|
|
||||||
1. Januar 2025 - 31. Dezember 2025
|
|
||||||
Keyword Currency Avg. monthly searches Änderung über drei Monate Änderung im Jahresvergleich Competition Competition (indexed value) Top of page bid (low range) Top of page bid (high range) Ad impression share Organic impression share Organic average position In account? In plan? Searches: Jan 2025 Searches: Feb 2025 Searches: Mar 2025 Searches: Apr 2025 Searches: May 2025 Searches: Jun 2025 Searches: Jul 2025 Searches: Aug 2025 Searches: Sep 2025 Searches: Oct 2025 Searches: Nov 2025 Searches: Dec 2025
|
|
||||||
custom qr code generator EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
|
||||||
qr barcode EUR 50000 0% 900% Niedrig 3 "0,42" "1,93"
|
|
||||||
bulk qr code generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
|
||||||
bar code generator EUR 500000 0% 0% Niedrig 3 "0,67" "2,58"
|
|
||||||
qr code generator for business EUR 500 0% 900% Niedrig 17 "1,81" "7,01"
|
|
||||||
this is a qr code EUR 5000 0% 0% Niedrig 9 "0,47" "1,33"
|
|
||||||
get a qr code for business EUR 50 0% 0% Niedrig 20
|
|
||||||
a qr code EUR 500 0% 0% Niedrig 23 "0,49" "2,01"
|
|
||||||
qr dynamic code generator EUR 50 -100% -100% Unbekannt
|
|
||||||
facebook qr code generator EUR 5000 -90% 0% Niedrig 19 "1,26" "4,10"
|
|
||||||
whatsapp qr code generator EUR 500 0% 0% Niedrig 19 "0,51" "1,89"
|
|
||||||
qr tracking EUR 500 0% 0% Niedrig 11 "1,66" "12,51"
|
|
||||||
custom qr generator EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
|
||||||
create qr code with tracking EUR 50 0% 0% Niedrig 18 "3,65" "7,01"
|
|
||||||
qr code generator sign in EUR 500 -90% -90% Niedrig 6
|
|
||||||
make custom qr code EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
|
||||||
qr code qr code EUR 500 -90% -90% Niedrig 27 "0,26" "1,28"
|
|
||||||
free barcode EUR 5000 0% 0% Niedrig 6 "0,64" "3,16"
|
|
||||||
youtube qr code EUR 5000 0% 0% Niedrig 16 "0,52" "2,21"
|
|
||||||
paypal qr code generator EUR 500 0% 0% Niedrig 19 "0,66" "2,58"
|
|
||||||
code qr code EUR 500 0% 0% Niedrig 18 "0,29" "1,03"
|
|
||||||
qr code to text EUR 500 0% 0% Niedrig 4 "0,50" "2,20"
|
|
||||||
code to qr code EUR 50 0% 0% Niedrig 30 "0,43" "1,51"
|
|
||||||
qr code location generator EUR 50 0% 0% Niedrig 17
|
|
||||||
restaurant menu qr code generator EUR 50 0% 0% Niedrig 0
|
|
||||||
bulk qr code EUR 500 0% 0% Niedrig 13 "0,07" "2,85"
|
|
||||||
qr code instagram generator EUR 50 0% 0% Niedrig 29 "0,71" "1,75"
|
|
||||||
qr codes pro EUR 500 0% 0% Niedrig 28 "1,23" "8,93"
|
|
||||||
facebook qr code generator free EUR 500 0% 0% Niedrig 15 "1,27" "3,63"
|
|
||||||
generate website qr code EUR 50000 0% 0% Niedrig 17 "1,88" "6,16"
|
|
||||||
paypal qr code EUR 5000 0% 0% Niedrig 9 "1,03" "6,79"
|
|
||||||
unique qr codes EUR 500 0% 0% Niedrig 30 "1,21" "5,02"
|
|
||||||
the qr code EUR 500 0% 0% Niedrig 23 "0,66" "2,21"
|
|
||||||
scan location qr code EUR 50 0% 0% Niedrig 24
|
|
||||||
cool qr codes EUR 500 0% 0% Niedrig 25 "1,16" "3,82"
|
|
||||||
qr code for EUR 500 0% 0% Niedrig 12 "1,04" "3,53"
|
|
||||||
to create qr code EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code for phone number EUR 500 0% 0% Niedrig 28 "0,70" "2,56"
|
|
||||||
qr wifi EUR 500 0% 0% Niedrig 2 "0,12" "1,00"
|
|
||||||
with qr code EUR 50 0% 0% Niedrig 22 "1,00" "3,93"
|
|
||||||
free bulk qr code generator EUR 500 900% 900% Niedrig 27 "0,91" "2,85"
|
|
||||||
cost of qr code generator EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code analytics tracking EUR 50 0% 0% Niedrig 9
|
|
||||||
free qr code for restaurant menu EUR 50 0% -100% Unbekannt
|
|
||||||
qr code time tracking EUR 50 0% 0% Niedrig 0
|
|
||||||
qr code with text EUR 500 0% 0% Niedrig 15 "0,13" "1,40"
|
|
||||||
free qr code menu EUR 50 0% 0% Niedrig 29 "1,57" "4,96"
|
|
||||||
qr code generator for restaurant menu EUR 500 0% 0% Niedrig 5
|
|
||||||
difference between static and dynamic qr code EUR 50 0% 0% Niedrig 24 "0,32" "10,47"
|
|
||||||
bulk qr generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
|
||||||
barcode code generator EUR 500000 0% 0% Niedrig 3 "0,67" "2,58"
|
|
||||||
generate dynamic qr code free EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code contact generator free EUR 50 0% -100% Unbekannt
|
|
||||||
scan qr code for location EUR 50 0% 0% Niedrig 17
|
|
||||||
ai qr code EUR 500 0% 0% Niedrig 14 "0,76" "4,66"
|
|
||||||
buy dynamic qr code EUR 50 ∞ 0% Niedrig 25 "1,87" "12,81"
|
|
||||||
sms qr code EUR 500 0% 0% Niedrig 17 "0,60" "3,35"
|
|
||||||
dynamic code generator EUR 50 0% 0% Niedrig 17 "3,82" "15,62"
|
|
||||||
qr code restaurant menu free EUR 50 0% -100% Unbekannt
|
|
||||||
vcard qr code generator with logo EUR 50 -100% -100% Unbekannt
|
|
||||||
bulk qr code generator free EUR 500 900% 900% Niedrig 27 "0,91" "2,85"
|
|
||||||
sms qr code generator EUR 500 0% 0% Niedrig 18 "0,66" "3,11"
|
|
||||||
phone qr code EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
|
||||||
create your dynamic qr code EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code as a business card EUR 50 -100% -100% Unbekannt
|
|
||||||
generate free barcode EUR 50000 0% 0% Niedrig 5 "0,67" "2,52"
|
|
||||||
qr code barcode generator EUR 500 0% 0% Niedrig 15 "0,58" "1,51"
|
|
||||||
bulk qr code generator with logo EUR 50 0% 0% Niedrig 0
|
|
||||||
qr restaurant EUR 500 0% 0% Niedrig 7
|
|
||||||
sign up qr code EUR 50 0% 0% Niedrig 23 "1,81" "5,80"
|
|
||||||
create qr code menu free EUR 50 -100% -100% Unbekannt
|
|
||||||
bulk qr code generator excel EUR 50 0% 0% Niedrig 21
|
|
||||||
qr code options EUR 50 0% 900% Niedrig 15 "1,10" "2,91"
|
|
||||||
qr bulk generator EUR 5000 0% 0% Niedrig 13 "0,69" "3,63"
|
|
||||||
qr code s EUR 500 0% 0% Niedrig 27 "0,57" "4,93"
|
|
||||||
qr code to qr code EUR 50 ∞ ∞ Niedrig 14
|
|
||||||
log in qr code generator EUR 5000 0% 0% Niedrig 15 "0,67" "3,08"
|
|
||||||
create whatsapp qr code EUR 500 0% 0% Niedrig 19 "0,51" "1,89"
|
|
||||||
qr code in EUR 50 0% 0% Niedrig 22 "0,31" "1,88"
|
|
||||||
twitter qr code generator EUR 50 0% 0% Niedrig 4
|
|
||||||
care code EUR 500 0% 0% Niedrig 2
|
|
||||||
create qr code gratuit EUR 50 -100% -100% Unbekannt
|
|
||||||
restaurants with qr codes EUR 50 0% 0% Niedrig 11
|
|
||||||
master qr EUR 50 0% ∞ Niedrig 0
|
|
||||||
manage qr codes EUR 500 0% -90% Niedrig 30 "2,11" "18,77"
|
|
||||||
qr generator custom EUR 5000 0% 0% Niedrig 14 "1,91" "6,43"
|
|
||||||
qr code generator it EUR 50 0% 0% Niedrig 11
|
|
||||||
bulk qr EUR 500 0% 0% Niedrig 13 "0,07" "2,85"
|
|
||||||
master qr code EUR 50 0% 0% Niedrig 2
|
|
||||||
qr code generator in bulk EUR 50 0% 0% Niedrig 7
|
|
||||||
scan barcode generator EUR 500 0% 0% Niedrig 27 "0,84" "5,11"
|
|
||||||
static qr and dynamic qr EUR 50 -100% -100% Unbekannt
|
|
||||||
barcode make EUR 5000 900% 900% Niedrig 15 "0,64" "2,78"
|
|
||||||
qr code generator site EUR 50000 0% 0% Niedrig 17 "1,88" "6,16"
|
|
||||||
code create EUR 50 -90% 0% Niedrig 3 "0,62" "6,98"
|
|
||||||
dynamic codes EUR 500 0% 0% Niedrig 12 "0,78" "4,21"
|
|
||||||
scan qr code for restaurant menu EUR 50 0% 0% Unbekannt
|
|
||||||
qr code tracking analytics EUR 50 0% 0% Niedrig 0
|
|
||||||
generate facebook qr code EUR 5000 -90% 0% Niedrig 19 "1,26" "4,10"
|
|
||||||
create qr code location EUR 50 -100% 0% Unbekannt
|
|
||||||
qr code of location EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code with EUR 50 0% 0% Niedrig 7
|
|
||||||
code to qr EUR 50 0% 0% Niedrig 30 "0,43" "1,51"
|
|
||||||
make your own qr code for business EUR 50 0% -100% Unbekannt
|
|
||||||
scan your qr code EUR 50 0% 0% Niedrig 27 "0,56" "2,47"
|
|
||||||
for qr code generator EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code what is qr EUR 5000 0% 0% Niedrig 15 "0,05" "0,64"
|
|
||||||
qr code with location EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code qr EUR 5000 0% 0% Niedrig 0 "1,03" "3,01"
|
|
||||||
qr code generator bulk free EUR 50 0% 0% Niedrig 24
|
|
||||||
qr code and EUR 500 0% 0% Niedrig 18 "0,29" "1,03"
|
|
||||||
qr number EUR 500 0% 900% Niedrig 5 "0,07" "0,94"
|
|
||||||
credit card qr code generator EUR 50 0% -100% Unbekannt
|
|
||||||
about qr code generator EUR 50 0% 0% Unbekannt
|
|
||||||
whatsapp qr generator EUR 500 0% 0% Niedrig 19 "0,51" "1,89"
|
|
||||||
generate to qr code EUR 50 -100% -100% Unbekannt
|
|
||||||
qr codes work EUR 500 0% 0% Niedrig 11 "0,63" "2,06"
|
|
||||||
qr to code EUR 50 0% 0% Niedrig 21 "1,00" "3,78"
|
|
||||||
in qr code EUR 50 0% 0% Niedrig 22 "0,31" "1,88"
|
|
||||||
create qr business card free EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code it EUR 50 0% 0% Niedrig 29 "1,21" "2,25"
|
|
||||||
qr marketing EUR 50 0% 0% Niedrig 3
|
|
||||||
it qr code EUR 50 0% 0% Niedrig 5
|
|
||||||
and qr code EUR 500 0% 0% Niedrig 18 "0,29" "1,03"
|
|
||||||
qr code is EUR 50 0% 0% Niedrig 17 "0,09" "1,83"
|
|
||||||
bulk code generator EUR 50 0% 0% Niedrig 5
|
|
||||||
qr code calculator EUR 50 0% 0% Niedrig 23
|
|
||||||
qr bulk EUR 500 0% 0% Niedrig 13 "0,07" "2,85"
|
|
||||||
generator qr barcode EUR 50 -100% -100% Unbekannt
|
|
||||||
generator free EUR 500 0% 0% Niedrig 16 "0,82" "5,79"
|
|
||||||
code generator code EUR 50 ∞ 0% Niedrig 14
|
|
||||||
qr number generator EUR 50 0% 0% Niedrig 14
|
|
||||||
qr code generator bulk create EUR 50 -100% -100% Unbekannt
|
|
||||||
qr campaign EUR 50 0% -90% Niedrig 19 "1,45" "10,66"
|
|
||||||
square codes EUR 5000 0% 0% Niedrig 5 "0,83" "3,42"
|
|
||||||
generator create EUR 500 0% 0% Niedrig 1
|
|
||||||
code for qr code EUR 50 ∞ 0% Niedrig 14
|
|
||||||
qr bulk code generator EUR 50 0% 0% Niedrig 11
|
|
||||||
qr code generator with location EUR 50 -100% 0% Unbekannt
|
|
||||||
qr code scanner qr code generator EUR 50 ∞ 0% Niedrig 29
|
|
||||||
youtube qr EUR 5000 0% 0% Niedrig 16 "0,52" "2,21"
|
|
||||||
qr code generator custom design EUR 50 -100% -100% Unbekannt
|
|
||||||
your code generator EUR 50 0% -100% Unbekannt
|
|
||||||
free dynamic code generator EUR 50 0% -100% Unbekannt
|
|
||||||
qr scan code free EUR 50 0% 0% Niedrig 0
|
|
||||||
edit qr EUR 50 0% 0% Niedrig 2
|
|
||||||
qr code scan qr code EUR 50 0% 0% Niedrig 25
|
|
||||||
sozdat qr code EUR 50 -100% 0% Unbekannt
|
|
||||||
barcode to qr code generator EUR 50 0% 0% Niedrig 27 "1,89" "8,80"
|
|
||||||
qr codes that work EUR 500 0% 0% Niedrig 11 "0,63" "2,06"
|
|
||||||
i qr code EUR 50 0% 0% Niedrig 11 "1,25" "3,95"
|
|
||||||
free barcode code generator EUR 50000 0% 0% Niedrig 5 "0,67" "2,52"
|
|
||||||
work qr code EUR 500 0% 0% Niedrig 11 "0,63" "2,06"
|
|
||||||
qr code on device EUR 50 0% 0% Niedrig 1
|
|
||||||
barcode code generator free EUR 50 0% -90% Niedrig 6 "0,20" "2,34"
|
|
||||||
device qr code EUR 50 0% 0% Niedrig 22 "0,58" "2,52"
|
|
||||||
code barcode EUR 500 0% 900% Niedrig 10 "0,19" "1,55"
|
|
||||||
all qr code generator EUR 50 0% 0% Unbekannt
|
|
||||||
advanced qr code EUR 50 0% 0% Niedrig 2
|
|
||||||
qr generator bulk EUR 50 0% 0% Niedrig 14
|
|
||||||
information qr code generator EUR 50 -100% -100% Unbekannt
|
|
||||||
device qr EUR 50 0% -100% Unbekannt
|
|
||||||
type qr code EUR 50 0% 0% Niedrig 9 "0,52" "1,66"
|
|
||||||
qr edit EUR 50 0% 0% Niedrig 21
|
|
||||||
no qr codes EUR 500 0% 0% Niedrig 3 "0,26" "1,28"
|
|
||||||
qr code na EUR 50 0% 0% Unbekannt
|
|
||||||
qr code & EUR 50 0% ∞ Niedrig 0
|
|
||||||
qr code a EUR 500 0% 0% Niedrig 23 "0,49" "2,01"
|
|
||||||
qr generator location EUR 50 ∞ 0% Niedrig 0
|
|
||||||
qr code an EUR 50 0% 0% Niedrig 19
|
|
||||||
qr code on EUR 50 0% 0% Niedrig 12
|
|
||||||
qr code i EUR 50 0% 0% Niedrig 11 "1,25" "3,95"
|
|
||||||
qr code of EUR 50 0% 0% Niedrig 9 "0,50" "2,07"
|
|
||||||
qr code on this phone EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
|
||||||
your qr EUR 50 0% 0% Niedrig 20
|
|
||||||
qr code what is EUR 5000 0% 0% Niedrig 15 "0,05" "0,64"
|
|
||||||
qr code generator vcard business card EUR 50 -100% -100% Unbekannt
|
|
||||||
every qr code EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
|
||||||
at qr code EUR 50 0% 0% Niedrig 4
|
|
||||||
qr code what is it EUR 50 0% 0% Niedrig 17 "0,05" "0,44"
|
|
||||||
qr code where is it EUR 50 0% 0% Niedrig 0
|
|
||||||
view qr code EUR 50 0% 0% Niedrig 13 "0,65" "3,23"
|
|
||||||
code creation EUR 50 -90% 0% Niedrig 12 "2,14" "5,68"
|
|
||||||
qr code from EUR 50 0% 0% Niedrig 14 "0,58" "1,45"
|
|
||||||
write qr code EUR 50 0% 0% Niedrig 0
|
|
||||||
credit card qr code EUR 50 0% 0% Niedrig 7 "2,64" "9,81"
|
|
||||||
qr code about EUR 50 0% 0% Niedrig 18
|
|
||||||
www com qr code EUR 50 0% 0% Unbekannt
|
|
||||||
this qr code EUR 50 0% 0% Niedrig 8
|
|
||||||
by qr code EUR 50 0% 0% Niedrig 19
|
|
||||||
qr code to location EUR 50 -100% -100% Unbekannt
|
|
||||||
location qr generator EUR 50 0% 0% Niedrig 0
|
|
||||||
business card qr code generator with logo EUR 50 0% 0% Niedrig 0
|
|
||||||
qr work EUR 50 900% 900% Niedrig 1
|
|
||||||
generate bulk qr code from excel EUR 50 ∞ ∞ Niedrig 14
|
|
||||||
any qr code EUR 500 0% 0% Niedrig 0
|
|
||||||
generate qr code bulk EUR 50 -100% 0% Unbekannt
|
|
||||||
qr code qr scanner EUR 500 0% 0% Niedrig 0
|
|
||||||
get qr code for location EUR 50 -100% -100% Unbekannt
|
|
||||||
scan code for EUR 50 0% 0% Niedrig 19
|
|
||||||
generator barcode qr EUR 50 0% -100% Unbekannt
|
|
||||||
free qr code for location EUR 50 -100% 0% Unbekannt
|
|
||||||
open a qr code EUR 500 0% 0% Niedrig 23 "0,41" "1,51"
|
|
||||||
phone qr EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
|
||||||
barcode to qr generator EUR 50 -100% -100% Unbekannt
|
|
||||||
static code generator EUR 50 0% 0% Niedrig 26
|
|
||||||
location qr code creation EUR 50 0% 0% Unbekannt
|
|
||||||
location scan qr code EUR 50 ∞ ∞ Niedrig 0
|
|
||||||
create location qr code free EUR 50 -100% 0% Unbekannt
|
|
||||||
generator qr code vcard EUR 50 0% 0% Unbekannt
|
|
||||||
bulk generator EUR 50 0% 0% Niedrig 4 "0,27" "1,33"
|
|
||||||
qr code of this device EUR 50 0% ∞ Niedrig 20
|
|
||||||
qr code generator location free EUR 50 0% 0% Niedrig 0
|
|
||||||
qr location code EUR 50 0% -100% Unbekannt
|
|
||||||
account qr code EUR 50 0% 0% Niedrig 13
|
|
||||||
qr code to code EUR 50 0% 0% Niedrig 0
|
|
||||||
free qr scan generator EUR 50 0% 0% Niedrig 26 "0,89" "1,51"
|
|
||||||
dynamic qr code generator with logo EUR 50 0% 0% Niedrig 0
|
|
||||||
qr code to barcode generator EUR 50 0% 0% Niedrig 5
|
|
||||||
qr code of a location EUR 50 0% -100% Unbekannt
|
|
||||||
the code generator EUR 50 0% 0% Niedrig 27
|
|
||||||
create qr code of location EUR 50 0% -100% Unbekannt
|
|
||||||
bulk qr barcode generator EUR 50 0% 0% Niedrig 3
|
|
||||||
qr hunt EUR 50 0% 0% Niedrig 4
|
|
||||||
bulk qr generator free EUR 50 -100% -100% Unbekannt
|
|
||||||
dynamic qr code display EUR 50 900% 900% Niedrig 6
|
|
||||||
location qr code free EUR 50 0% -100% Unbekannt
|
|
||||||
location qr scanner EUR 50 -100% 0% Unbekannt
|
|
||||||
qr quotes EUR 500 0% 0% Niedrig 5 "0,61" "2,17"
|
|
||||||
qr c9de EUR 50 -90% 0% Niedrig 2
|
|
||||||
you are code generator EUR 50 0% -100% Unbekannt
|
|
||||||
make a qr code for location EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code generator from csv EUR 50 0% 0% Niedrig 0
|
|
||||||
generate qr code from barcode EUR 50 0% 0% Niedrig 29
|
|
||||||
make generator EUR 500 0% 0% Niedrig 9 "0,46" "2,62"
|
|
||||||
all qr codes EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
|
||||||
bulk qr code generator with logo free EUR 50 0% ∞ Unbekannt
|
|
||||||
whatsapp qr code business card EUR 50 -100% 0% Unbekannt
|
|
||||||
qr code with phone EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
|
||||||
generator for free EUR 500 0% 0% Niedrig 16 "0,82" "5,79"
|
|
||||||
generate url from qr code EUR 500 -90% 0% Niedrig 4 "2,05" "2,69"
|
|
||||||
barcode and qr codes EUR 50000 0% 900% Niedrig 3 "0,42" "1,93"
|
|
||||||
qr code menu restaurant free EUR 50 0% 0% Unbekannt
|
|
||||||
qr code features EUR 500 0% 900% Niedrig 2 "0,33" "1,29"
|
|
||||||
generate qr code for location free EUR 50 0% -100% Unbekannt
|
|
||||||
location in qr code EUR 50 0% 0% Unbekannt
|
|
||||||
generate qr code by url EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code required EUR 50 0% 0% Niedrig 11 "0,94" "2,61"
|
|
||||||
qr code from phone EUR 50 0% 0% Niedrig 11 "0,49" "1,03"
|
|
||||||
location qr code scan EUR 50 -100% -100% Unbekannt
|
|
||||||
no qr EUR 500 0% 0% Niedrig 3 "0,26" "1,28"
|
|
||||||
qr code scan for location EUR 50 0% -100% Unbekannt
|
|
||||||
generate url to qr code EUR 50 -100% -100% Unbekannt
|
|
||||||
get code from qr code EUR 50 0% 0% Niedrig 10
|
|
||||||
scan the qr code for location EUR 50 ∞ ∞ Niedrig 0
|
|
||||||
kod generator EUR 50 0% 0% Niedrig 0
|
|
||||||
qr code in text EUR 50 0% 0% Niedrig 2
|
|
||||||
restaurant menu scan code EUR 50 0% 0% Niedrig 0
|
|
||||||
location qr code free generator EUR 50 0% 0% Unbekannt
|
|
||||||
url from qr code EUR 50 0% 0% Niedrig 4
|
|
||||||
scan tracking EUR 50 900% 0% Niedrig 24 "0,31" "2,42"
|
|
||||||
qr code of this phone EUR 50 0% 0% Niedrig 0
|
|
||||||
qr code brand EUR 500 0% 0% Niedrig 4 "1,55" "8,10"
|
|
||||||
qr code in phone EUR 500 0% 0% Niedrig 28 "0,45" "1,61"
|
|
||||||
www the qr code generator scan EUR 50 -100% 0% Unbekannt
|
|
||||||
create qr code from barcode EUR 50 0% 0% Niedrig 29
|
|
||||||
qr all EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
|
||||||
bulk qr code barcode generator EUR 50 ∞ 0% Niedrig 0
|
|
||||||
qr scan location EUR 50 0% 0% Niedrig 0
|
|
||||||
qr code no EUR 500 0% 0% Niedrig 3 "0,26" "1,28"
|
|
||||||
qr code that works EUR 500 0% 0% Niedrig 11 "0,63" "2,06"
|
|
||||||
qr code randomizer EUR 5000 0% 0% Niedrig 13 "0,60" "1,71"
|
|
||||||
qr code marketing campaigns EUR 50 0% 0% Niedrig 3
|
|
||||||
qr code generator create your qr code for free EUR 50 -100% -100% Unbekannt
|
|
||||||
account qr EUR 50 0% ∞ Niedrig 0
|
|
||||||
qr code for credit card EUR 50 -100% -100% Unbekannt
|
|
||||||
qr cost EUR 50 0% 0% Niedrig 5
|
|
||||||
qr code on the phone EUR 50 0% 0% Niedrig 13
|
|
||||||
scan qr code scan qr code EUR 50 0% 0% Niedrig 25
|
|
||||||
qr kodlari EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code scan code EUR 50 0% 0% Niedrig 17
|
|
||||||
scan by qr code EUR 50 0% 0% Niedrig 0
|
|
||||||
3 qr code EUR 50 0% 0% Niedrig 0
|
|
||||||
scan qr code location EUR 50 0% 0% Niedrig 17
|
|
||||||
qr scanner in EUR 50 0% 0% Niedrig 18
|
|
||||||
create qr code to whatsapp EUR 50 -100% -100% Unbekannt
|
|
||||||
qr code on email EUR 50 0% 0% Niedrig 25
|
|
||||||
create qr code for whatsapp free EUR 50 0% -100% Unbekannt
|
|
||||||
make qr code of location EUR 50 -100% 0% Unbekannt
|
|
||||||
qr code save EUR 50 0% 0% Niedrig 19 "0,83" "3,47"
|
|
||||||
no to qr code EUR 50 0% -100% Unbekannt
|
|
||||||
call qr code EUR 50000 0% 0% Niedrig 0 "0,57" "2,59"
|
|
||||||
free barcode code EUR 500 0% 0% Niedrig 18 "0,65" "2,84"
|
|
||||||
qr code for scan EUR 50 0% 0% Niedrig 14 "0,52" "1,56"
|
|
||||||
bar code s EUR 50 0% 0% Niedrig 24 "1,26" "1,72"
|
|
||||||
qr code as text EUR 500 0% 0% Niedrig 15 "0,13" "1,40"
|
|
||||||
qr view EUR 50 0% 0% Niedrig 3
|
|
||||||
qr code generator device EUR 50 0% 0% Niedrig 8
|
|
||||||
qr scanner for EUR 50 0% 0% Niedrig 14 "0,52" "1,56"
|
|
||||||
qr code generator full EUR 50 0% 0% Unbekannt
|
|
||||||
qr code generator for visiting card free EUR 50 0% 0% Unbekannt
|
|
||||||
qr coe3 EUR 50 -100% -100% Unbekannt
|
|
||||||
qr text code EUR 50 0% 0% Niedrig 29
|
|
||||||
scan qr free EUR 50 0% 0% Niedrig 25
|
|
||||||
cole generator EUR 50 0% 0% Niedrig 24
|
|
||||||
into qr code EUR 50 0% 0% Niedrig 22 "0,31" "1,88"
|
|
||||||
qr code how does it work EUR 50 0% 0% Niedrig 25 "0,10" "0,52"
|
|
||||||
any qr EUR 500 0% 0% Niedrig 0
|
|
||||||
menu card qr code generator EUR 50 0% 0% Unbekannt
|
|
||||||
url to code generator EUR 50 ∞ 0% Niedrig 14
|
|
||||||
website qr scanner EUR 500 0% 0% Niedrig 6 "0,03" "1,77"
|
|
||||||
all in qr code EUR 50 0% 0% Unbekannt
|
|
||||||
all qr EUR 50 0% 0% Niedrig 5 "0,50" "6,33"
|
|
||||||
qr scanner location EUR 50 0% 0% Niedrig 0
|
|
||||||
qr how to use EUR 50 0% 0% Niedrig 0
|
|
||||||
qr scanner for location EUR 50 -100% 0% Unbekannt
|
|
||||||
scan qr code for EUR 50 0% 0% Niedrig 11
|
|
||||||
scan the qr code for EUR 50 0% 0% Niedrig 0
|
|
||||||
scan this qr code for EUR 50 ∞ ∞ Niedrig 29
|
|
||||||
the qr barcode generator EUR 50 -100% -100% Unbekannt
|
|
||||||
trackable codes 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 code login generator EUR 50 0% 0% Niedrig 16
|
|
||||||
|
|
||||||
BIN
lint_out.txt
@@ -1,25 +0,0 @@
|
|||||||
# QRMaster Content Research (Powered by Apify)
|
|
||||||
|
|
||||||
## 💆 Spas & Wellness Centers
|
|
||||||
- **Core Use Cases:**
|
|
||||||
- **Seamless Booking:** QR codes on posters or reception desks for instant appointment scheduling.
|
|
||||||
- **Digital Guest Intake:** Contactless check-in via QR-linked intake forms to enhance safety and privacy.
|
|
||||||
- **Menu of Services:** Dynamic digital menus allowing for easy service selection without physical pamphlets.
|
|
||||||
- **Reviews & Feedback:** "Scan to Rate" prompts on mirrors or checkout to boost TripAdvisor and Google ratings.
|
|
||||||
|
|
||||||
## 💄 Beauty Salons
|
|
||||||
- **Core Use Cases:**
|
|
||||||
- **Portfolio Showcasing:** QR codes at styling stations leading to "before and after" galleries on social media.
|
|
||||||
- **Loyalty Program:** Instant sign-up for digital reward systems upon scan.
|
|
||||||
- **Social Sharing:** "Scan to Tag Us" with integrated Instagram/TikTok handles to drive organic reach.
|
|
||||||
- **Product Upsell:** QR codes on retail display shelves linking to product tutorials and educational content.
|
|
||||||
|
|
||||||
## 💈 Barbershops
|
|
||||||
- **Core Use Cases:**
|
|
||||||
- **Smart Waitlists:** Scan to join the queue from outside the shop, reducing crowded waiting areas.
|
|
||||||
- **Tip-to-Tap Integration:** Simplified tipping through QR leading directly to payment apps.
|
|
||||||
- **Aftercare Sales:** Selling hair waxes or beard oils through QR codes on receipts for easy re-ordering.
|
|
||||||
- **Community Building:** Linking to local events or barbershop blogs to foster neighborhood presence.
|
|
||||||
|
|
||||||
---
|
|
||||||
*Next Steps: Deep-diving into the remaining 40 industries to generate specific marketing content.*
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
FILENAME: spas.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for spas and wellness centers landing page. Luxurious spa reception desk with white orchids, candles, rolled white towels, soft warm lighting, calming neutral palette, minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: beauty-salons.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for beauty salons landing page. Stylish modern beauty salon interior with large illuminated mirror station, elegant product display, soft warm lighting, minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: barbershops.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for barbershops landing page. Classic modern barbershop with leather barber chair, chrome tools on shelf, clean black and white tiled floor, warm lighting. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: nail-salons.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for nail salons landing page. Elegant nail salon station with colorful nail polish bottles neatly arranged, manicure tools, soft pastel lighting, minimal clean composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: tattoo-studios.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for tattoo studios landing page. Stylish tattoo studio interior with clean black bench, framed flash art on white walls, professional lighting, minimal edgy aesthetic. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: pharmacies.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for pharmacies landing page. Clean modern pharmacy interior with white shelving, organized product displays, soft clinical lighting, minimal professional composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: clothing-stores.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for clothing stores landing page. Elegant boutique clothing store with curated rack of garments, clean minimal interior, warm spotlighting, fitting room in background. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: car-dealerships.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for car dealerships landing page. Sleek modern car showroom with a luxury vehicle on a polished floor, dramatic overhead lighting, minimal glass and chrome architecture. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: florists.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for florists and flower shops landing page. Beautiful artisan florist shop with abundant colorful floral arrangements, rustic wooden counter, soft natural light. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: pet-stores.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for pet stores and groomers landing page. Bright modern pet store interior with organized shelving, cute dog being groomed in foreground, warm friendly lighting. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: electronics-stores.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for electronics stores landing page. Sleek modern electronics retail interior with backlit product displays, tablets and devices on clean white shelving, cool blue accent lighting. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: jewelry-stores.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for jewelry stores landing page. Luxurious jewelry store interior with glass display counter, diamond rings and necklaces elegantly arranged, warm spotlight lighting, dark rich background. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: hardware-stores.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for hardware and DIY stores landing page. Bright hardware store aisle with well-organized tools on shelving, clean modern signage, warm industrial lighting. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: bookstores.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for bookstores landing page. Cozy independent bookstore interior with floor-to-ceiling wooden shelves of books, warm reading lamp light, inviting atmosphere. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: universities.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for universities and colleges landing page. Impressive university campus building facade with students walking, classical architecture, blue sky, minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: schools.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for schools landing page. Bright modern school corridor with lockers, natural light from windows, students walking, clean minimal educational environment. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: museums.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for museums and exhibitions landing page. Grand museum hall with high ceilings, dramatic artifact display, dramatic lighting on exhibits, minimal modern architecture. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: libraries.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for public libraries landing page. Stunning modern library interior with towering bookshelves, warm reading area, soft natural light from skylights, minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: theaters.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for theaters and performing arts landing page. Elegant theater auditorium interior with red velvet seats, stage lit with warm spotlight, ornate architecture, dramatic ambiance. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: cinemas.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for cinemas and movie theaters landing page. Modern cinema foyer with glowing box office, dramatic neon lighting accents, minimal sleek design, movie poster frames blurred in background. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: churches.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for churches and places of worship landing page. Beautiful church interior with stained glass windows casting colored light, wooden pews, peaceful minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: art-galleries.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for art galleries landing page. Clean white-walled contemporary art gallery with large paintings on walls, polished concrete floor, dramatic track lighting on artwork. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: stadiums.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for stadiums and sports venues landing page. Aerial view of a large modern stadium at golden hour, dramatic scale, vibrant field, minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
!
|
|
||||||
FILENAME: wedding-planners.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for wedding planners landing page. Elegant wedding reception venue setup with floral centerpieces, candlelight, white tablecloths, soft bokeh in background, romantic minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: photographers.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for photography studios landing page. Professional photography studio with camera on tripod, softbox lighting setup, white seamless backdrop, minimal clean setup. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: trade-shows.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for trade shows and exhibitions landing page. Large exhibition hall with branded stands, people networking, dramatic overhead lighting, minimal busy-but-clean composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: law-firms.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for law firms landing page. Prestigious law firm office with dark wood shelving, legal books, leather chairs, clean executive desk, soft warm lighting. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: accountants.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for accounting firms landing page. Clean modern accountancy office with sleek desk, laptop, neat files, large window with city view, minimal professional composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: insurance-agencies.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for insurance agencies landing page. Professional insurance agency office with friendly advisor desk, clean modern interior, trust-inspiring warm neutral tones, minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: travel-agencies.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for travel agencies landing page. Vibrant travel agency window display with destination cards, tropical beach and mountain imagery, warm inviting lighting, minimal composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: coworking-spaces.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for coworking spaces landing page. Bright modern coworking office with open desk plan, natural light, plants, people working on laptops, minimal Scandinavian aesthetic. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: property-management.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for property management landing page. Modern apartment building lobby with clean reception desk, architectural lighting, minimal contemporary interior design. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: airports.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for airports and travel hubs landing page. Dramatic modern airport terminal interior with high glass ceilings, people with luggage walking, departure boards, minimal futuristic composition. Professional, no text. Premium aesthetic.
|
|
||||||
|
|
||||||
FILENAME: dentists.png
|
|
||||||
PROMPT: Clean, modern hero image for a QR code for dental practices landing page. Clean modern dental practice waiting room with white interior, comfortable seating, reception desk, plants, calming warm lighting. Professional, no text. Premium aesthetic.
|
|
||||||
@@ -1,457 +0,0 @@
|
|||||||
# Programmatic SEO Plan: 1000 Pages for QR Master
|
|
||||||
|
|
||||||
Date: 2026-03-31
|
|
||||||
Owner: QR Master
|
|
||||||
Scope: Organic growth plan for roughly 1000 indexable SEO pages
|
|
||||||
|
|
||||||
## Executive Decision
|
|
||||||
|
|
||||||
Do not build `1000 blog posts`.
|
|
||||||
|
|
||||||
For QR Master, the better model is:
|
|
||||||
|
|
||||||
- `80-120` editorial blog posts
|
|
||||||
- `150-250` commercial tool pages
|
|
||||||
- `200-300` use-case and workflow pages
|
|
||||||
- `100-150` industry pages
|
|
||||||
- `150-250` comparison and alternative pages
|
|
||||||
- `150-250` support, glossary, template, and problem-solution pages
|
|
||||||
|
|
||||||
That gets you to roughly `1000` pages without creating a thin-content footprint.
|
|
||||||
|
|
||||||
## Why 1000 blog posts is the wrong move
|
|
||||||
|
|
||||||
QR Master already has a stronger pSEO base than a typical blog-first site:
|
|
||||||
|
|
||||||
- `22` blog posts in [src/lib/blog-data.ts](C:\Users\a931627\Documents\QRMASTER\src\lib\blog-data.ts)
|
|
||||||
- `20` tool pages in [src/app/sitemap.ts](C:\Users\a931627\Documents\QRMASTER\src\app\sitemap.ts)
|
|
||||||
- `9` use-case pages in [src/lib/growth-pages.ts](C:\Users\a931627\Documents\QRMASTER\src\lib\growth-pages.ts)
|
|
||||||
- `8` industry pages in [src/lib/industry-pages.ts](C:\Users\a931627\Documents\QRMASTER\src\lib\industry-pages.ts)
|
|
||||||
- static generation already exists for blog, use-cases, learn hubs, and industry pages
|
|
||||||
|
|
||||||
The current architecture is already optimized for scalable landing pages, not for managing 1000 long-form editorial articles in one monolithic blog dataset.
|
|
||||||
|
|
||||||
There is also a search-quality risk. Google explicitly warns against:
|
|
||||||
|
|
||||||
- scaled content abuse
|
|
||||||
- doorway-style pages
|
|
||||||
- large volumes of low-value, near-duplicate content
|
|
||||||
|
|
||||||
Relevant guidance:
|
|
||||||
|
|
||||||
- Google spam policies: https://developers.google.com/search/docs/advanced/guidelines/auto-gen-content
|
|
||||||
- Google people-first content guidance: https://developers.google.com/search/docs/fundamentals/creating-helpful-content
|
|
||||||
- Google generative AI content guidance: https://developers.google.com/search/docs/fundamentals/using-gen-ai-content
|
|
||||||
|
|
||||||
## What live SERPs suggest right now
|
|
||||||
|
|
||||||
Spot checks on 2026-03-31 indicate that high-intent QR queries are mostly served by dedicated landing pages, generators, and comparison pages, not by generic blog posts.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- restaurant menu QR: GustoQR, Menulizer, QRMake, Jampos, WebsitesQR
|
|
||||||
- WiFi QR: WiQRCode, Q-WiFi
|
|
||||||
- dynamic QR: GeckoQR, QRFlow, QRelix
|
|
||||||
- bulk QR: QRMass, TofuQR, BulkQRBarcode
|
|
||||||
|
|
||||||
This means the highest-value SEO surface for QR Master is:
|
|
||||||
|
|
||||||
- generator pages
|
|
||||||
- workflow pages
|
|
||||||
- industry pages
|
|
||||||
- comparison pages
|
|
||||||
- practical support content tied to specific jobs
|
|
||||||
|
|
||||||
Not 1000 generic “what is” articles.
|
|
||||||
|
|
||||||
## Recommended 1000-page mix
|
|
||||||
|
|
||||||
### 1. Tool intent pages: 180 pages
|
|
||||||
|
|
||||||
Goal: capture users searching for a specific QR type, format, or action.
|
|
||||||
|
|
||||||
Current base:
|
|
||||||
|
|
||||||
- `/tools/url-qr-code`
|
|
||||||
- `/tools/wifi-qr-code`
|
|
||||||
- `/tools/vcard-qr-code`
|
|
||||||
- `/tools/instagram-qr-code`
|
|
||||||
- etc.
|
|
||||||
|
|
||||||
Expand with sub-intents around each tool:
|
|
||||||
|
|
||||||
- format intent: `svg`, `png`, `pdf`, `eps`
|
|
||||||
- output intent: `for print`, `for stickers`, `for packaging`
|
|
||||||
- job intent: `for flyers`, `for tables`, `for business cards`
|
|
||||||
- modifier intent: `free`, `custom`, `dynamic`, `trackable`
|
|
||||||
|
|
||||||
Example cluster:
|
|
||||||
|
|
||||||
- `/tools/wifi-qr-code`
|
|
||||||
- `/tools/wifi-qr-code/for-restaurants`
|
|
||||||
- `/tools/wifi-qr-code/for-hotels`
|
|
||||||
- `/tools/wifi-qr-code/for-airbnb`
|
|
||||||
- `/tools/wifi-qr-code/print-size`
|
|
||||||
- `/tools/wifi-qr-code/svg`
|
|
||||||
- `/tools/wifi-qr-code/how-to`
|
|
||||||
- `/tools/wifi-qr-code/troubleshooting`
|
|
||||||
|
|
||||||
Requirement:
|
|
||||||
|
|
||||||
- every page must change the workflow, examples, FAQ, CTA, and placement guidance
|
|
||||||
- not just the H1
|
|
||||||
|
|
||||||
### 2. Use-case and workflow pages: 260 pages
|
|
||||||
|
|
||||||
Goal: map product capabilities to specific offline-to-online jobs.
|
|
||||||
|
|
||||||
Current base:
|
|
||||||
|
|
||||||
- `/use-cases/restaurant-menu-qr-codes`
|
|
||||||
- `/use-cases/business-card-qr-codes`
|
|
||||||
- `/use-cases/event-qr-codes`
|
|
||||||
- etc.
|
|
||||||
|
|
||||||
Expand by combining:
|
|
||||||
|
|
||||||
- surface: table tent, flyer, poster, window, packaging, receipt, label, badge
|
|
||||||
- workflow: menu, reviews, payment, lead capture, coupon, check-in, support, manual, onboarding
|
|
||||||
- intent: editable, trackable, branded, bulk, privacy-safe
|
|
||||||
|
|
||||||
Example patterns:
|
|
||||||
|
|
||||||
- `/use-cases/qr-codes-for-product-packaging`
|
|
||||||
- `/use-cases/qr-codes-for-brochures`
|
|
||||||
- `/use-cases/qr-codes-for-trade-show-booths`
|
|
||||||
- `/use-cases/qr-codes-for-real-estate-flyers`
|
|
||||||
- `/use-cases/qr-codes-for-table-ordering`
|
|
||||||
- `/use-cases/qr-codes-for-manuals-and-inserts`
|
|
||||||
- `/use-cases/qr-codes-for-review-collection`
|
|
||||||
|
|
||||||
### 3. Industry pages: 140 pages
|
|
||||||
|
|
||||||
Goal: capture commercial intent by vertical.
|
|
||||||
|
|
||||||
Current base:
|
|
||||||
|
|
||||||
- restaurants
|
|
||||||
- cafes
|
|
||||||
- hotels
|
|
||||||
- real estate
|
|
||||||
- gyms
|
|
||||||
- doctors and dentists
|
|
||||||
- retail
|
|
||||||
- events
|
|
||||||
|
|
||||||
Best expansion model:
|
|
||||||
|
|
||||||
- industry hub
|
|
||||||
- industry plus workflow
|
|
||||||
- industry plus operational pain point
|
|
||||||
|
|
||||||
Example patterns:
|
|
||||||
|
|
||||||
- `/qr-code-for/restaurants`
|
|
||||||
- `/qr-code-for/restaurants/menu-updates`
|
|
||||||
- `/qr-code-for/restaurants/table-ordering`
|
|
||||||
- `/qr-code-for/restaurants/review-collection`
|
|
||||||
- `/qr-code-for/hotels/guest-wifi`
|
|
||||||
- `/qr-code-for/retail/packaging`
|
|
||||||
- `/qr-code-for/events/check-in`
|
|
||||||
|
|
||||||
Important:
|
|
||||||
|
|
||||||
- keep the base industry pages
|
|
||||||
- add second-level workflow pages only where search intent and product fit are strong
|
|
||||||
|
|
||||||
### 4. Comparison and alternative pages: 180 pages
|
|
||||||
|
|
||||||
Goal: capture bottom-funnel evaluation traffic.
|
|
||||||
|
|
||||||
Patterns:
|
|
||||||
|
|
||||||
- `[competitor] alternative`
|
|
||||||
- `[competitor] vs QR Master`
|
|
||||||
- `dynamic vs static`
|
|
||||||
- `free vs paid`
|
|
||||||
- `[feature A] vs [feature B]`
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- `/compare/qr-code-generator-com-alternative`
|
|
||||||
- `/compare/beaconstac-alternative`
|
|
||||||
- `/compare/flowcode-alternative`
|
|
||||||
- `/compare/qr-code-monkey-vs-qr-master`
|
|
||||||
- `/compare/dynamic-vs-static-qr-codes`
|
|
||||||
- `/compare/free-vs-paid-qr-code-generator`
|
|
||||||
|
|
||||||
These pages should include:
|
|
||||||
|
|
||||||
- actual comparison tables
|
|
||||||
- pricing logic
|
|
||||||
- use-case fit
|
|
||||||
- privacy and GDPR angle
|
|
||||||
- migration guidance
|
|
||||||
|
|
||||||
### 5. Support, glossary, and problem-solution pages: 140 pages
|
|
||||||
|
|
||||||
Goal: capture informational searches with strong product adjacency.
|
|
||||||
|
|
||||||
Patterns:
|
|
||||||
|
|
||||||
- how to
|
|
||||||
- troubleshooting
|
|
||||||
- best practices
|
|
||||||
- definitions
|
|
||||||
- safety and compliance
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- `/guide/qr-code-print-size`
|
|
||||||
- `/guide/why-my-qr-code-is-not-scanning`
|
|
||||||
- `/guide/how-to-track-qr-code-scans`
|
|
||||||
- `/guide/qr-code-gdpr`
|
|
||||||
- `/guide/qr-code-phishing`
|
|
||||||
- `/guide/how-to-create-a-vcard-qr-code`
|
|
||||||
- `/guide/how-to-use-utm-with-qr-codes`
|
|
||||||
|
|
||||||
These are not filler pages. They should support tool, use-case, and comparison clusters.
|
|
||||||
|
|
||||||
### 6. Editorial blog: 100 pages
|
|
||||||
|
|
||||||
Goal: publish expert-led, citation-worthy content.
|
|
||||||
|
|
||||||
Use blog posts for:
|
|
||||||
|
|
||||||
- original research
|
|
||||||
- industry benchmarks
|
|
||||||
- deep tutorials
|
|
||||||
- opinionated comparisons
|
|
||||||
- campaign strategy examples
|
|
||||||
- security explainers
|
|
||||||
|
|
||||||
Do not use the blog for every long-tail keyword variation.
|
|
||||||
|
|
||||||
## URL architecture
|
|
||||||
|
|
||||||
Stay with subfolders. Do not use subdomains.
|
|
||||||
|
|
||||||
Recommended structure:
|
|
||||||
|
|
||||||
- `/tools/[tool]`
|
|
||||||
- `/tools/[tool]/[modifier-or-job]`
|
|
||||||
- `/use-cases/[slug]`
|
|
||||||
- `/qr-code-for/[industry]`
|
|
||||||
- `/qr-code-for/[industry]/[workflow]`
|
|
||||||
- `/compare/[slug]`
|
|
||||||
- `/guide/[slug]`
|
|
||||||
- `/blog/[slug]`
|
|
||||||
- `/learn/[pillar]`
|
|
||||||
|
|
||||||
This keeps topical authority consolidated under one domain and matches the existing app structure.
|
|
||||||
|
|
||||||
## What makes each page unique
|
|
||||||
|
|
||||||
Every indexable page should contain at least 3 of these:
|
|
||||||
|
|
||||||
- unique quick answer
|
|
||||||
- workflow-specific steps
|
|
||||||
- vertical-specific FAQ
|
|
||||||
- placement guidance
|
|
||||||
- examples tied to the page context
|
|
||||||
- comparison logic
|
|
||||||
- recommended tool stack
|
|
||||||
- CTA matched to that exact query
|
|
||||||
- internal links to adjacent nodes
|
|
||||||
- proprietary insights from QR Master product usage once available
|
|
||||||
|
|
||||||
If a page only swaps:
|
|
||||||
|
|
||||||
- city
|
|
||||||
- industry
|
|
||||||
- social platform name
|
|
||||||
- file format
|
|
||||||
|
|
||||||
then it is not ready to index.
|
|
||||||
|
|
||||||
## The best programmatic page families for QR Master
|
|
||||||
|
|
||||||
### Highest priority
|
|
||||||
|
|
||||||
1. Tool x job-to-be-done
|
|
||||||
2. Industry x workflow
|
|
||||||
3. Use-case x printed surface
|
|
||||||
4. Comparison and alternative pages
|
|
||||||
5. Tracking, analytics, and GDPR support pages
|
|
||||||
|
|
||||||
### Medium priority
|
|
||||||
|
|
||||||
1. Glossary pages
|
|
||||||
2. Generator template pages
|
|
||||||
3. Print specification pages
|
|
||||||
4. Security and trust pages
|
|
||||||
|
|
||||||
### Low priority
|
|
||||||
|
|
||||||
1. city pages
|
|
||||||
2. country pages
|
|
||||||
3. “near me” pages
|
|
||||||
4. large-scale locale combinations
|
|
||||||
|
|
||||||
Those are the most likely to drift into doorway territory for this product.
|
|
||||||
|
|
||||||
## Data model recommendation
|
|
||||||
|
|
||||||
Do not keep scaling everything inside one giant `blog-data.ts`.
|
|
||||||
|
|
||||||
Instead, split content into typed datasets:
|
|
||||||
|
|
||||||
- `src/lib/tool-pages.ts`
|
|
||||||
- `src/lib/use-case-pages.ts`
|
|
||||||
- `src/lib/industry-pages.ts`
|
|
||||||
- `src/lib/comparison-pages.ts`
|
|
||||||
- `src/lib/guide-pages.ts`
|
|
||||||
- `src/lib/blog-data.ts`
|
|
||||||
|
|
||||||
Each record should support:
|
|
||||||
|
|
||||||
- slug
|
|
||||||
- query intent
|
|
||||||
- primary CTA
|
|
||||||
- secondary CTA
|
|
||||||
- unique answer block
|
|
||||||
- unique workflow steps
|
|
||||||
- unique FAQ
|
|
||||||
- related links
|
|
||||||
- schema fields
|
|
||||||
- publish and update metadata
|
|
||||||
|
|
||||||
## Internal linking model
|
|
||||||
|
|
||||||
Every page should sit inside a clear cluster.
|
|
||||||
|
|
||||||
Example cluster:
|
|
||||||
|
|
||||||
- tool: `/tools/wifi-qr-code`
|
|
||||||
- industry: `/qr-code-for/hotels`
|
|
||||||
- workflow: `/qr-code-for/hotels/guest-wifi`
|
|
||||||
- guide: `/guide/how-to-create-a-wifi-qr-code`
|
|
||||||
- comparison: `/compare/free-vs-paid-qr-code-generator`
|
|
||||||
- commercial: `/dynamic-qr-code-generator`
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
|
|
||||||
- hub links down to spoke pages
|
|
||||||
- spoke links back to hub
|
|
||||||
- spoke links sideways to 2-4 adjacent pages
|
|
||||||
- editorial blog links into commercial and programmatic pages
|
|
||||||
- every page should have breadcrumb schema
|
|
||||||
|
|
||||||
## Indexation policy
|
|
||||||
|
|
||||||
Do not index everything on day one.
|
|
||||||
|
|
||||||
Recommended rollout:
|
|
||||||
|
|
||||||
- launch first `100-150` strongest pages
|
|
||||||
- measure impressions, clicks, engagement, and conversions
|
|
||||||
- only expand page families that show traction
|
|
||||||
- noindex weak template variants until they have enough differentiated content
|
|
||||||
|
|
||||||
This matters because a bad 1000-page rollout can lower perceived site quality faster than it grows traffic.
|
|
||||||
|
|
||||||
## 90-day rollout
|
|
||||||
|
|
||||||
### Phase 1: Foundation
|
|
||||||
|
|
||||||
- keep current blog, tool, use-case, and industry system
|
|
||||||
- create new page-family schemas for `comparison` and `guide`
|
|
||||||
- move content datasets out of oversized single files where needed
|
|
||||||
- update sitemap generation to support multiple page families
|
|
||||||
|
|
||||||
### Phase 2: First 150 pages
|
|
||||||
|
|
||||||
- publish 40 tool-adjacent pages
|
|
||||||
- publish 40 industry-workflow pages
|
|
||||||
- publish 30 use-case surface pages
|
|
||||||
- publish 20 comparison pages
|
|
||||||
- publish 20 support and guide pages
|
|
||||||
|
|
||||||
### Phase 3: Measure
|
|
||||||
|
|
||||||
Track:
|
|
||||||
|
|
||||||
- indexation rate
|
|
||||||
- impressions per page family
|
|
||||||
- click-through rate
|
|
||||||
- assisted signups
|
|
||||||
- free-to-paid influence
|
|
||||||
- pages with zero impressions after 60 days
|
|
||||||
|
|
||||||
### Phase 4: Scale winners
|
|
||||||
|
|
||||||
Double down only on page families that show:
|
|
||||||
|
|
||||||
- meaningful impressions
|
|
||||||
- rankings entering top 20
|
|
||||||
- conversion assistance
|
|
||||||
- internal-link engagement
|
|
||||||
|
|
||||||
## Suggested page count by family
|
|
||||||
|
|
||||||
| Family | Target |
|
|
||||||
|---|---:|
|
|
||||||
| Tool base pages | 20 |
|
|
||||||
| Tool modifiers and job pages | 160 |
|
|
||||||
| Use-case pages | 120 |
|
|
||||||
| Use-case surface and workflow expansions | 140 |
|
|
||||||
| Industry hubs | 20 |
|
|
||||||
| Industry workflow pages | 120 |
|
|
||||||
| Comparison pages | 180 |
|
|
||||||
| Guides and troubleshooting | 140 |
|
|
||||||
| Editorial blog | 100 |
|
|
||||||
| Hubs and utility pages | 20 |
|
|
||||||
| Total | 1000 |
|
|
||||||
|
|
||||||
## What not to do
|
|
||||||
|
|
||||||
Avoid:
|
|
||||||
|
|
||||||
- 1000 AI-written blog posts targeting slight keyword variations
|
|
||||||
- city pages without local operations or local proof
|
|
||||||
- dozens of pages that all funnel to the same generic generator with no new value
|
|
||||||
- near-duplicate intros with only one variable changed
|
|
||||||
- indexable pages with no cluster context or internal links
|
|
||||||
|
|
||||||
## Best next step for this codebase
|
|
||||||
|
|
||||||
If the goal is execution, the best sequence is:
|
|
||||||
|
|
||||||
1. add `comparison` and `guide` page families
|
|
||||||
2. restructure page content into typed datasets
|
|
||||||
3. ship the first `50-100` high-fit pages
|
|
||||||
4. measure what gets indexed and clicked
|
|
||||||
5. then scale toward `300`, `500`, and finally `1000`
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
|
|
||||||
Live search spot checks on 2026-03-31:
|
|
||||||
|
|
||||||
- https://www.gustoqr.com/
|
|
||||||
- https://www.menulizer.com/
|
|
||||||
- https://qrmake.io/menu-qr-code
|
|
||||||
- https://www.jampos.app/qr-generator
|
|
||||||
- https://www.websitesqr.com/menu-qr-code
|
|
||||||
- https://wiqrcode.com/
|
|
||||||
- https://www.q-wifi.com/
|
|
||||||
- https://www.geckoqr.com/
|
|
||||||
- https://qrflow.co/
|
|
||||||
- https://qrmass.com/
|
|
||||||
- https://tofu-qr.com/qr-generator/bulk/
|
|
||||||
- https://bulkqrbarcode.com/
|
|
||||||
|
|
||||||
Google guidance:
|
|
||||||
|
|
||||||
- https://developers.google.com/search/docs/advanced/guidelines/auto-gen-content
|
|
||||||
- https://developers.google.com/search/docs/fundamentals/creating-helpful-content
|
|
||||||
- https://developers.google.com/search/docs/fundamentals/using-gen-ai-content
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
# Programmatic SEO Top 50 Backlog
|
|
||||||
|
|
||||||
Date: 2026-03-31
|
|
||||||
Project: QR Master
|
|
||||||
Purpose: Prioritized first-wave backlog for the new `comparison` and `guide` families plus adjacent high-fit commercial clusters.
|
|
||||||
|
|
||||||
## Scoring Model
|
|
||||||
|
|
||||||
Each URL idea is scored from `0-100`.
|
|
||||||
|
|
||||||
Weights:
|
|
||||||
|
|
||||||
- Commercial intent: `30`
|
|
||||||
- Product fit: `25`
|
|
||||||
- Differentiation potential: `15`
|
|
||||||
- Cluster leverage: `10`
|
|
||||||
- SERP winability: `10`
|
|
||||||
- Production effort: `10`
|
|
||||||
|
|
||||||
Interpretation:
|
|
||||||
|
|
||||||
- `90+`: ship immediately
|
|
||||||
- `85-89`: first-wave priority
|
|
||||||
- `80-84`: second-wave after initial measurement
|
|
||||||
- `<80`: hold until stronger cluster support exists
|
|
||||||
|
|
||||||
## Top 50
|
|
||||||
|
|
||||||
| Rank | URL | Family | Score | Primary CTA |
|
|
||||||
|---|---|---|---:|---|
|
|
||||||
| 1 | `/compare/dynamic-vs-static-qr-codes` | comparison | 93 | `/dynamic-qr-code-generator` |
|
|
||||||
| 2 | `/compare/free-vs-paid-qr-code-generator` | comparison | 92 | `/pricing` |
|
|
||||||
| 3 | `/guide/how-to-track-qr-code-scans` | guide-problem | 91 | `/qr-code-tracking` |
|
|
||||||
| 4 | `/guide/why-my-qr-code-is-not-scanning` | guide-problem | 91 | `/custom-qr-code-generator` |
|
|
||||||
| 5 | `/guide/qr-code-print-size` | guide-problem | 90 | `/custom-qr-code-generator` |
|
|
||||||
| 6 | `/compare/qr-code-monkey-vs-qr-master` | comparison | 90 | `/dynamic-qr-code-generator` |
|
|
||||||
| 7 | `/compare/flowcode-alternative` | comparison | 89 | `/qr-code-tracking` |
|
|
||||||
| 8 | `/compare/beaconstac-alternative` | comparison | 89 | `/dynamic-qr-code-generator` |
|
|
||||||
| 9 | `/guide/how-to-use-utm-with-qr-codes` | guide-problem | 89 | `/qr-code-tracking` |
|
|
||||||
| 10 | `/guide/dynamic-qr-code-best-practices` | guide-strategic | 88 | `/dynamic-qr-code-generator` |
|
|
||||||
| 11 | `/guide/qr-code-gdpr` | guide-strategic | 88 | `/qr-code-tracking` |
|
|
||||||
| 12 | `/compare/bulk-qr-generator-vs-single-qr-generator` | comparison | 88 | `/bulk-qr-code-generator` |
|
|
||||||
| 13 | `/qr-code-for/restaurants/menu-updates` | industry-workflow | 88 | `/dynamic-qr-code-generator` |
|
|
||||||
| 14 | `/qr-code-for/restaurants/review-collection` | industry-workflow | 87 | `/qr-code-tracking` |
|
|
||||||
| 15 | `/tools/wifi-qr-code/for-hotels` | tool-job | 87 | `/tools/wifi-qr-code` |
|
|
||||||
| 16 | `/guide/how-to-create-a-vcard-qr-code` | guide-problem | 87 | `/tools/vcard-qr-code` |
|
|
||||||
| 17 | `/qr-code-for/hotels/guest-wifi` | industry-workflow | 87 | `/tools/wifi-qr-code` |
|
|
||||||
| 18 | `/use-cases/qr-codes-for-product-packaging` | use-case | 87 | `/bulk-qr-code-generator` |
|
|
||||||
| 19 | `/qr-code-for/retail/packaging` | industry-workflow | 86 | `/bulk-qr-code-generator` |
|
|
||||||
| 20 | `/tools/url-qr-code/for-flyers` | tool-job | 86 | `/tools/url-qr-code` |
|
|
||||||
| 21 | `/guide/qr-code-security-best-practices` | guide-strategic | 86 | `/dynamic-qr-code-generator` |
|
|
||||||
| 22 | `/compare/qr-code-generator-com-alternative` | comparison | 86 | `/dynamic-qr-code-generator` |
|
|
||||||
| 23 | `/compare/bitly-qr-code-generator-alternative` | comparison | 86 | `/qr-code-tracking` |
|
|
||||||
| 24 | `/qr-code-for/events/check-in` | industry-workflow | 86 | `/tools/event-qr-code` |
|
|
||||||
| 25 | `/use-cases/qr-codes-for-review-collection` | use-case | 86 | `/qr-code-tracking` |
|
|
||||||
| 26 | `/tools/wifi-qr-code/for-restaurants` | tool-job | 85 | `/tools/wifi-qr-code` |
|
|
||||||
| 27 | `/tools/vcard-qr-code/for-business-cards` | tool-job | 85 | `/tools/vcard-qr-code` |
|
|
||||||
| 28 | `/guide/how-to-create-a-wifi-qr-code` | guide-problem | 85 | `/tools/wifi-qr-code` |
|
|
||||||
| 29 | `/guide/how-to-update-a-qr-code-after-printing` | guide-problem | 85 | `/dynamic-qr-code-generator` |
|
|
||||||
| 30 | `/compare/canva-qr-code-vs-qr-master` | comparison | 85 | `/custom-qr-code-generator` |
|
|
||||||
| 31 | `/qr-code-for/real-estate/open-house-flyers` | industry-workflow | 85 | `/qr-code-tracking` |
|
|
||||||
| 32 | `/use-cases/qr-codes-for-brochures` | use-case | 85 | `/dynamic-qr-code-generator` |
|
|
||||||
| 33 | `/tools/url-qr-code/for-packaging` | tool-job | 85 | `/tools/url-qr-code` |
|
|
||||||
| 34 | `/compare/uniqode-alternative` | comparison | 84 | `/qr-code-tracking` |
|
|
||||||
| 35 | `/compare/adobe-express-qr-code-vs-qr-master` | comparison | 84 | `/custom-qr-code-generator` |
|
|
||||||
| 36 | `/tools/whatsapp-qr-code/for-customer-support` | tool-job | 84 | `/tools/whatsapp-qr-code` |
|
|
||||||
| 37 | `/qr-code-for/cafes/loyalty-signups` | industry-workflow | 84 | `/tools/url-qr-code` |
|
|
||||||
| 38 | `/qr-code-for/events/booth-lead-capture` | industry-workflow | 84 | `/qr-code-tracking` |
|
|
||||||
| 39 | `/guide/qr-code-landing-page-best-practices` | guide-strategic | 84 | `/dynamic-qr-code-generator` |
|
|
||||||
| 40 | `/use-cases/qr-codes-for-table-ordering` | use-case | 84 | `/dynamic-qr-code-generator` |
|
|
||||||
| 41 | `/tools/pdf-qr-code/for-menus` | tool-job | 83 | `/dynamic-qr-code-generator` |
|
|
||||||
| 42 | `/guide/qr-code-analytics-for-offline-campaigns` | guide-strategic | 83 | `/qr-code-analytics` |
|
|
||||||
| 43 | `/compare/linktree-vs-qr-master-for-offline-campaigns` | comparison | 83 | `/qr-code-for-marketing-campaigns` |
|
|
||||||
| 44 | `/qr-code-for/doctors-dentists/intake-forms` | industry-workflow | 83 | `/tools/url-qr-code` |
|
|
||||||
| 45 | `/tools/paypal-qr-code/for-invoices` | tool-job | 82 | `/tools/paypal-qr-code` |
|
|
||||||
| 46 | `/guide/how-to-test-a-qr-code-before-printing` | guide-problem | 82 | `/custom-qr-code-generator` |
|
|
||||||
| 47 | `/use-cases/qr-codes-for-manuals-and-inserts` | use-case | 82 | `/bulk-qr-code-generator` |
|
|
||||||
| 48 | `/qr-code-for/retail/window-shopping` | industry-workflow | 81 | `/qr-code-for-marketing-campaigns` |
|
|
||||||
| 49 | `/tools/email-qr-code/for-event-follow-up` | tool-job | 81 | `/tools/email-qr-code` |
|
|
||||||
| 50 | `/guide/qr-code-branding-best-practices` | guide-strategic | 80 | `/custom-qr-code-generator` |
|
|
||||||
|
|
||||||
## Shipment Mix For First 50
|
|
||||||
|
|
||||||
Recommended composition:
|
|
||||||
|
|
||||||
- `15` comparison pages
|
|
||||||
- `15` guide pages
|
|
||||||
- `10` tool-job pages
|
|
||||||
- `10` industry-workflow or use-case pages
|
|
||||||
|
|
||||||
This keeps the first wave biased toward:
|
|
||||||
|
|
||||||
- bottom-funnel demand
|
|
||||||
- strong CTA fit
|
|
||||||
- cluster leverage into existing product pages
|
|
||||||
|
|
||||||
## First 10 To Ship
|
|
||||||
|
|
||||||
1. `/compare/dynamic-vs-static-qr-codes`
|
|
||||||
2. `/compare/free-vs-paid-qr-code-generator`
|
|
||||||
3. `/guide/how-to-track-qr-code-scans`
|
|
||||||
4. `/guide/why-my-qr-code-is-not-scanning`
|
|
||||||
5. `/guide/qr-code-print-size`
|
|
||||||
6. `/compare/qr-code-monkey-vs-qr-master`
|
|
||||||
7. `/compare/flowcode-alternative`
|
|
||||||
8. `/compare/beaconstac-alternative`
|
|
||||||
9. `/guide/how-to-use-utm-with-qr-codes`
|
|
||||||
10. `/guide/dynamic-qr-code-best-practices`
|
|
||||||
|
|
||||||
## Cluster Notes
|
|
||||||
|
|
||||||
### Cluster A: Dynamic QR buying decision
|
|
||||||
|
|
||||||
- `/compare/dynamic-vs-static-qr-codes`
|
|
||||||
- `/compare/free-vs-paid-qr-code-generator`
|
|
||||||
- `/guide/dynamic-qr-code-best-practices`
|
|
||||||
- `/guide/how-to-update-a-qr-code-after-printing`
|
|
||||||
- `/dynamic-qr-code-generator`
|
|
||||||
|
|
||||||
### Cluster B: Tracking and analytics
|
|
||||||
|
|
||||||
- `/guide/how-to-track-qr-code-scans`
|
|
||||||
- `/guide/how-to-use-utm-with-qr-codes`
|
|
||||||
- `/guide/qr-code-analytics-for-offline-campaigns`
|
|
||||||
- `/qr-code-tracking`
|
|
||||||
- `/qr-code-analytics`
|
|
||||||
|
|
||||||
### Cluster C: Print reliability
|
|
||||||
|
|
||||||
- `/guide/why-my-qr-code-is-not-scanning`
|
|
||||||
- `/guide/qr-code-print-size`
|
|
||||||
- `/guide/how-to-test-a-qr-code-before-printing`
|
|
||||||
- `/custom-qr-code-generator`
|
|
||||||
|
|
||||||
### Cluster D: Commercial alternatives
|
|
||||||
|
|
||||||
- `/compare/qr-code-monkey-vs-qr-master`
|
|
||||||
- `/compare/flowcode-alternative`
|
|
||||||
- `/compare/beaconstac-alternative`
|
|
||||||
- `/compare/qr-code-generator-com-alternative`
|
|
||||||
- `/compare/uniqode-alternative`
|
|
||||||
|
|
||||||
## Default Indexing Recommendation
|
|
||||||
|
|
||||||
Default to `index` only when:
|
|
||||||
|
|
||||||
- the page has a strong cluster position
|
|
||||||
- the CTA is specific and commercially coherent
|
|
||||||
- the page has distinct FAQs, workflow logic, and related links
|
|
||||||
|
|
||||||
Default to `hold` or `noindex` when:
|
|
||||||
|
|
||||||
- the page is still too close to an existing sibling
|
|
||||||
- the page has weak differentiation
|
|
||||||
- the cluster around it is not yet live
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
PRODID:-//QR Master//Reddit Full Calendar//EN
|
|
||||||
CALSCALE:GREGORIAN
|
|
||||||
METHOD:PUBLISH
|
|
||||||
X-WR-CALNAME:QR Master Reddit Full
|
|
||||||
X-WR-TIMEZONE:Europe/Berlin
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260316-library@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260316T100000Z
|
|
||||||
DTEND:20260316T103000Z
|
|
||||||
SUMMARY:Reddit reply library - all 15 templates
|
|
||||||
DESCRIPTION:1) If someone asks what tool you use:\nIf you need the destination to stay editable later\, that’s exactly the problem I built around.\nI built QR Master for this\, so obvious bias:\nhttps://www.qrmaster.net/dynamic-qr-code-generator\nIf you want\, I can also just explain the setup without linking anything.\n\n2) Direct link ask:\nSure. I’m building it\, so bias disclosed:\nhttps://www.qrmaster.net/dynamic-qr-code-generator\nMain use case is when something is already printed and the destination changes later.\n\n3) Restaurant/menu:\nIf the menu changes even semi-regularly\, I wouldn’t print a static QR.\nhttps://www.qrmaster.net/use-cases/restaurant-menu-qr-codes\n\n4) Flyer/print:\nThe problem usually isn’t generating the QR. It’s what happens when the page changes after print.\nhttps://www.qrmaster.net/use-cases/flyer-qr-codes\n\n5) Events:\nFor events\, I’d mainly optimize for can we update this later without reprinting everything?\nhttps://www.qrmaster.net/use-cases/event-qr-codes\n\n6) Packaging/bulk:\nIf you’re dealing with lots of codes\, bulk workflow matters way more than people expect.\nhttps://www.qrmaster.net/bulk-qr-code-generator\n\n7) Analytics:\nYou probably want some scan visibility\, but only the useful stuff.\nhttps://www.qrmaster.net/qr-code-tracking\n\n8) Privacy:\nIf privacy matters\, I’d look very closely at how the tracking is handled.\nhttps://www.qrmaster.net/privacy\n\n9) Agencies:\nYeah\, agencies are actually one of the more interesting use cases.\nhttps://www.qrmaster.net/qr-code-for-marketing-campaigns\n\n10) Alternatives:\nIf you need editability after print\, that’s where tools start to differ.\nhttps://www.qrmaster.net/dynamic-qr-code-generator\n\n11) Skeptical reply:\nFair skepticism. I built it\, so I’m obviously biased:\nhttps://www.qrmaster.net/dynamic-qr-code-generator\n\n12) How set up:\nUse a dynamic destination\, keep the landing page mobile-first\, and make sure you can update it after print.\nhttps://www.qrmaster.net/dynamic-qr-code-generator\n\n13) Recommendations:\nIf the destination might change later\, I’d use a dynamic QR setup.\nhttps://www.qrmaster.net/dynamic-qr-code-generator\n\n14) Feedback thread:\nI’m building in this space\, so this is partly self-interested\, but yes\, this is a real problem.\nhttps://www.qrmaster.net/dynamic-qr-code-generator\n\n15) Direct relevance:\nI built a tool for this exact issue\, so obvious bias:\nhttps://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260317-post2@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260317T120000Z
|
|
||||||
DTEND:20260317T124500Z
|
|
||||||
SUMMARY:r/startups - Post 2
|
|
||||||
DESCRIPTION:Title: One URL change can ruin 500 flyers. That pain is more real than I expected.\n\nI underestimated how annoying printed mistakes are.\n\nA lot of software problems are reversible.\nPrint problems aren’t.\n\nIf a landing page changes after flyers\, posters\, inserts\, or menus are already out there\, someone has to:\n- live with a broken flow\n- reprint everything\n- or patch it manually in a messy way\n\nThat sounds minor until you talk to people actually running campaigns or local businesses.\n\nWhat small operational problem ended up being much more expensive than it looked at first?\n\nUse replies: 2\, 4\, 11\, 12\, 15
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260319-post1@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260319T120000Z
|
|
||||||
DTEND:20260319T124500Z
|
|
||||||
SUMMARY:r/SaaS - Post 1
|
|
||||||
DESCRIPTION:Title: I thought QR code software was about generation. The real pain starts after print.\n\nI used to think the value was make a QR code fast.\n\nIt’s not.\n\nThe painful part starts after something is already printed:\n- the menu changes\n- the event page changes\n- the campaign URL changes\n- someone notices a typo too late\n\nOne small change can turn a stack of flyers into trash.\n\nThat shifted how I think about the whole category.\nThe QR itself is easy.\nThe expensive part is everything around it.\n\nAnyone else building in a category where the simple feature isn’t actually where the value is?\n\nUse replies: 1\, 7\, 10\, 11\, 15
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260324-post3@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260324T123000Z
|
|
||||||
DTEND:20260324T131500Z
|
|
||||||
SUMMARY:r/smallbusiness - Post 3
|
|
||||||
DESCRIPTION:Title: Small businesses usually don’t need more marketing. They need fewer expensive mistakes.\n\nOne thing I keep noticing:\n\nA lot of owners don’t care about having a fancy tool.\nThey care about not wasting money.\n\nWith QR codes\, the common mistakes seem to be:\n- printing static codes for things that change often\n- sending people to ugly mobile pages\n- having no idea whether anyone scanned anything\n- letting one outdated link stay live for weeks\n\nFeels like a lot of marketing problems are actually ops problems.\n\nWhat’s one small process change that saved your business money this year?\n\nUse replies: 3\, 4\, 10\, 12\, 13
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260326-post4@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260326T120000Z
|
|
||||||
DTEND:20260326T124500Z
|
|
||||||
SUMMARY:r/EntrepreneurRideAlong - Post 4
|
|
||||||
DESCRIPTION:Title: Building in a boring category taught me that boring problems are usually expensive.\n\nI’m building around QR codes\, which sounds incredibly boring on paper.\n\nBut boring problems are often the ones people pay to avoid.\n\nIn this case\, it’s stuff like:\n- reprinting menus\n- fixing outdated flyers\n- updating event info after posters are already out\n- managing lots of QR destinations across campaigns\n\nNobody is emotionally excited about QR codes.\nThey’re emotionally excited about not dealing with preventable mess.\n\nAnyone else building something unsexy that turned out to have very real pain behind it?\n\nUse replies: 2\, 4\, 9\, 11\, 15
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260327-post5@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260327T133000Z
|
|
||||||
DTEND:20260327T141500Z
|
|
||||||
SUMMARY:r/SideProject - Post 5
|
|
||||||
DESCRIPTION:Title: The weird part about building a QR product is that the technical problem isn’t the interesting one.\n\nGenerating a QR image is trivial.\n\nWhat turned out to be more interesting:\n- what happens after print\n- whether someone can change the destination later\n- what analytics are actually useful\n- how privacy concerns show up once tracking enters the conversation\n- how bulk workflows matter way more than expected\n\nIt’s one of those products that looks dumb-simple from the outside and much more operational once you talk to users.\n\nWhat kind of side project looked simple until real use cases started showing up?\n\nUse replies: 1\, 6\, 7\, 8\, 15
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260331-post7@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260331T120000Z
|
|
||||||
DTEND:20260331T124500Z
|
|
||||||
SUMMARY:r/AlphaandBetaTesters - Post 7
|
|
||||||
DESCRIPTION:Title: Looking for feedback from anyone who has used QR codes in restaurants\, events\, print\, or packaging.\n\nI’m trying to learn from people who use QR codes in the real world\, not just in theory.\n\nEspecially if you’ve used them for:\n- menus\n- flyers\n- product packaging\n- event materials\n- WiFi / contact sharing\n- agency campaigns\n\nThings I’m curious about:\n- what changes most often after something is printed?\n- what’s annoying about current tools?\n- do you actually care about scan analytics?\n- does privacy / GDPR affect vendor choice at all?\n\nI’m happy to share what I’m building if useful\, but mostly looking for honest feedback from people who’ve dealt with this firsthand.\n\nUse replies: 3\, 5\, 6\, 8\, 14
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260402-post8@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260402T120000Z
|
|
||||||
DTEND:20260402T124500Z
|
|
||||||
SUMMARY:r/RoastMyStartup - Post 8
|
|
||||||
DESCRIPTION:Title: Roast my positioning: is avoid reprints and broken QR campaigns a strong enough problem?\n\nI’m working on a product around dynamic QR codes.\n\nThe positioning I’m testing is less make QR codes and more avoid reprints\, outdated links\, and messy campaign management.\n\nTarget users are mostly:\n- small businesses\n- restaurants\n- marketers\n- agencies\n- event / packaging use cases\n\nThe questions I’d love roasted:\n- does the pain feel real enough?\n- does this sound too niche?\n- what part sounds generic or weak?\n- what would make you ignore this instantly?\n\nHappy to share the product if the sub is okay with it.\n\nUse replies: 2\, 10\, 11\, 14\, 15
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260403-post6@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260403T123000Z
|
|
||||||
DTEND:20260403T131500Z
|
|
||||||
SUMMARY:r/ProductMgmt - Post 6
|
|
||||||
DESCRIPTION:Title: Users say they want a QR generator. What they actually want is damage control.\n\nA PM lesson I didn’t expect:\n\nPeople describe the need as I need a QR code.\nBut what they actually care about is something like:\nI need this thing to not break once it’s already printed.\n\nThat changes what feels important.\n\nGenerate code sounds like the core feature.\nBut retention/value probably sits closer to:\n- edit later\n- track scans\n- handle multiple codes\n- avoid privacy headaches\n- manage existing campaigns cleanly\n\nHave you seen that mismatch in your own product?\nWhat users ask for first vs. what actually matters later?\n\nUse replies: 1\, 7\, 8\, 10\, 12
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260407-post9@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260407T120000Z
|
|
||||||
DTEND:20260407T124500Z
|
|
||||||
SUMMARY:r/startups - Post 9
|
|
||||||
DESCRIPTION:Title: I’m starting to think edit after print is a stronger product promise than track scans.\n\nInteresting thing from early positioning:\n\nI assumed analytics would be the hero feature.\nBut I can change the destination later seems to click faster.\n\nMakes sense in hindsight.\nTracking is nice.\nAvoiding expensive mistakes is urgent.\n\nSo now I’m wondering if the better message is:\n- first promise control\n- then introduce analytics\n- then layer in bulk / workflow / privacy\n\nIf you’ve sold into small businesses or marketers:\nwhat kind of promise gets attention faster\, insight or control?\n\nUse replies: 1\, 7\, 9\, 10\, 15
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-full-20260409-post10@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260409T123000Z
|
|
||||||
DTEND:20260409T131500Z
|
|
||||||
SUMMARY:r/smallbusiness - Post 10
|
|
||||||
DESCRIPTION:Title: What looks like a tiny print detail can quietly waste a lot of money.\n\nI keep coming back to this:\n\nA broken link on a website is annoying.\nA broken link on printed material is expensive.\n\nBecause now the problem is sitting in:\n- stores\n- restaurants\n- posters\n- packaging\n- tables\n- flyers already handed out\n\nFeels like one of those things that sounds tiny until you count the friction and replacement cost.\n\nWhat’s a small detail in your business that causes way more downstream cost than people assume?\n\nUse replies: 3\, 4\, 6\, 13\, 15
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
||||||
@@ -1,443 +0,0 @@
|
|||||||
# QR Master Reddit Calendar - Full 4 Weeks
|
|
||||||
|
|
||||||
Times are in Europe/Berlin.
|
|
||||||
|
|
||||||
## Link Map
|
|
||||||
|
|
||||||
- Core dynamic angle: `https://www.qrmaster.net/dynamic-qr-code-generator`
|
|
||||||
- Reprint / waste angle: `https://www.qrmaster.net/reprint-calculator`
|
|
||||||
- Restaurant / menu: `https://www.qrmaster.net/use-cases/restaurant-menu-qr-codes`
|
|
||||||
- Flyer / print campaigns: `https://www.qrmaster.net/use-cases/flyer-qr-codes`
|
|
||||||
- Event use case: `https://www.qrmaster.net/use-cases/event-qr-codes`
|
|
||||||
- Bulk / packaging: `https://www.qrmaster.net/bulk-qr-code-generator`
|
|
||||||
- Packaging use case: `https://www.qrmaster.net/use-cases/packaging-qr-codes`
|
|
||||||
- Tracking / analytics: `https://www.qrmaster.net/qr-code-tracking`
|
|
||||||
- Privacy: `https://www.qrmaster.net/privacy`
|
|
||||||
- Campaign workflows: `https://www.qrmaster.net/qr-code-for-marketing-campaigns`
|
|
||||||
- Main site: `https://www.qrmaster.net/`
|
|
||||||
|
|
||||||
## Reply Library
|
|
||||||
|
|
||||||
### 1. If someone asks what tool you use
|
|
||||||
|
|
||||||
```text
|
|
||||||
If you need the destination to stay editable later, that’s exactly the problem I built around.
|
|
||||||
|
|
||||||
I built QR Master for this, so obvious bias:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
|
|
||||||
If you want, I can also just explain the setup without linking anything.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. If someone asks for the link directly
|
|
||||||
|
|
||||||
```text
|
|
||||||
Sure. I’m building it, so bias disclosed:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
|
|
||||||
Main use case is when something is already printed and the destination changes later.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Restaurant / menu threads
|
|
||||||
|
|
||||||
```text
|
|
||||||
If the menu changes even semi-regularly, I wouldn’t print a static QR.
|
|
||||||
|
|
||||||
I built a tool for exactly that use case, so obvious bias here:
|
|
||||||
https://www.qrmaster.net/use-cases/restaurant-menu-qr-codes
|
|
||||||
|
|
||||||
Even without using mine, I’d still recommend a dynamic destination and a very mobile-friendly menu page.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Flyer / print campaign threads
|
|
||||||
|
|
||||||
```text
|
|
||||||
The problem usually isn’t generating the QR. It’s what happens when the page changes after print.
|
|
||||||
|
|
||||||
I built something specifically for that:
|
|
||||||
https://www.qrmaster.net/use-cases/flyer-qr-codes
|
|
||||||
|
|
||||||
If helpful, I can outline the print-safe setup here too.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Event use case
|
|
||||||
|
|
||||||
```text
|
|
||||||
For events, I’d mainly optimize for “can we update this later without reprinting everything?”
|
|
||||||
|
|
||||||
I built QR Master around that exact headache, so bias here:
|
|
||||||
https://www.qrmaster.net/use-cases/event-qr-codes
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Packaging / bulk
|
|
||||||
|
|
||||||
```text
|
|
||||||
If you’re dealing with lots of codes, bulk workflow matters way more than people expect.
|
|
||||||
|
|
||||||
I built this with that in mind:
|
|
||||||
https://www.qrmaster.net/bulk-qr-code-generator
|
|
||||||
|
|
||||||
Happy to share what a clean CSV / batch setup usually looks like.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. Analytics questions
|
|
||||||
|
|
||||||
```text
|
|
||||||
You probably want some scan visibility, but only the useful stuff.
|
|
||||||
|
|
||||||
I built QR Master with that angle in mind, so obvious bias:
|
|
||||||
https://www.qrmaster.net/qr-code-tracking
|
|
||||||
|
|
||||||
Personally I think basic scan data + clean attribution matters more than a flashy dashboard.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8. Privacy / GDPR
|
|
||||||
|
|
||||||
```text
|
|
||||||
If privacy matters, I’d look very closely at how the tracking is handled.
|
|
||||||
|
|
||||||
I’m building QR Master, so not pretending to be neutral:
|
|
||||||
https://www.qrmaster.net/privacy
|
|
||||||
|
|
||||||
That said, I’d ask the same questions to any vendor.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9. If someone says this would be useful for agencies
|
|
||||||
|
|
||||||
```text
|
|
||||||
Yeah, agencies are actually one of the more interesting use cases.
|
|
||||||
|
|
||||||
I built this partly with that workflow in mind:
|
|
||||||
https://www.qrmaster.net/qr-code-for-marketing-campaigns
|
|
||||||
|
|
||||||
Managing multiple destinations gets messy fast otherwise.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 10. If someone asks about alternatives
|
|
||||||
|
|
||||||
```text
|
|
||||||
Depends what you care about.
|
|
||||||
|
|
||||||
If you just need a basic static code, almost anything works.
|
|
||||||
If you need editability after print, that’s where tools start to differ.
|
|
||||||
|
|
||||||
I built QR Master for that second case:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
```
|
|
||||||
|
|
||||||
### 11. If someone is skeptical
|
|
||||||
|
|
||||||
```text
|
|
||||||
Fair skepticism.
|
|
||||||
|
|
||||||
I built it, so I’m obviously biased:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
|
|
||||||
If you’d rather keep it link-free, I’m happy to just explain the tradeoffs here.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 12. If someone asks how you would set this up
|
|
||||||
|
|
||||||
```text
|
|
||||||
Short version:
|
|
||||||
use a dynamic destination, keep the landing page mobile-first, and make sure you can update it after print.
|
|
||||||
|
|
||||||
I built a tool for exactly that flow:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
```
|
|
||||||
|
|
||||||
### 13. If someone asks for recommendations
|
|
||||||
|
|
||||||
```text
|
|
||||||
If the destination might change later, I’d use a dynamic QR setup.
|
|
||||||
|
|
||||||
I built one for that use case, so take this with bias:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
|
|
||||||
If not mine, I’d still choose something that makes post-print edits easy.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 14. Feedback threads
|
|
||||||
|
|
||||||
```text
|
|
||||||
I’m building in this space, so this is partly self-interested, but yes, this is a real problem.
|
|
||||||
|
|
||||||
Here’s what I’m working on if useful:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
|
|
||||||
Would genuinely love blunt feedback more than polite praise.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 15. Direct relevance / no hard sell
|
|
||||||
|
|
||||||
```text
|
|
||||||
I built a tool for this exact issue, so obvious bias:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
|
|
||||||
No hard sell. Just seemed directly relevant to what you asked.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Schedule
|
|
||||||
|
|
||||||
### 2026-03-17 Tuesday, 13:00
|
|
||||||
|
|
||||||
- Subreddit: `r/startups`
|
|
||||||
- Post #2
|
|
||||||
- Title: `One URL change can ruin 500 flyers. That pain is more real than I expected.`
|
|
||||||
- Use replies: `2`, `4`, `11`, `12`, `15`
|
|
||||||
|
|
||||||
```text
|
|
||||||
I underestimated how annoying printed mistakes are.
|
|
||||||
|
|
||||||
A lot of software problems are reversible.
|
|
||||||
Print problems aren’t.
|
|
||||||
|
|
||||||
If a landing page changes after flyers, posters, inserts, or menus are already out there, someone has to:
|
|
||||||
- live with a broken flow
|
|
||||||
- reprint everything
|
|
||||||
- or patch it manually in a messy way
|
|
||||||
|
|
||||||
That sounds minor until you talk to people actually running campaigns or local businesses.
|
|
||||||
|
|
||||||
What small operational problem ended up being much more expensive than it looked at first?
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-03-19 Thursday, 13:00
|
|
||||||
|
|
||||||
- Subreddit: `r/SaaS`
|
|
||||||
- Post #1
|
|
||||||
- Title: `I thought QR code software was about generation. The real pain starts after print.`
|
|
||||||
- Use replies: `1`, `7`, `10`, `11`, `15`
|
|
||||||
|
|
||||||
```text
|
|
||||||
I used to think the value was “make a QR code fast.”
|
|
||||||
|
|
||||||
It’s not.
|
|
||||||
|
|
||||||
The painful part starts after something is already printed:
|
|
||||||
- the menu changes
|
|
||||||
- the event page changes
|
|
||||||
- the campaign URL changes
|
|
||||||
- someone notices a typo too late
|
|
||||||
|
|
||||||
One small change can turn a stack of flyers into trash.
|
|
||||||
|
|
||||||
That shifted how I think about the whole category.
|
|
||||||
The QR itself is easy.
|
|
||||||
The expensive part is everything around it.
|
|
||||||
|
|
||||||
Anyone else building in a category where the “simple feature” isn’t actually where the value is?
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-03-24 Tuesday, 13:30
|
|
||||||
|
|
||||||
- Subreddit: `r/smallbusiness`
|
|
||||||
- Post #3
|
|
||||||
- Title: `Small businesses usually don’t need “more marketing.” They need fewer expensive mistakes.`
|
|
||||||
- Use replies: `3`, `4`, `10`, `12`, `13`
|
|
||||||
|
|
||||||
```text
|
|
||||||
One thing I keep noticing:
|
|
||||||
|
|
||||||
A lot of owners don’t care about having a fancy tool.
|
|
||||||
They care about not wasting money.
|
|
||||||
|
|
||||||
With QR codes, the common mistakes seem to be:
|
|
||||||
- printing static codes for things that change often
|
|
||||||
- sending people to ugly mobile pages
|
|
||||||
- having no idea whether anyone scanned anything
|
|
||||||
- letting one outdated link stay live for weeks
|
|
||||||
|
|
||||||
Feels like a lot of “marketing problems” are actually ops problems.
|
|
||||||
|
|
||||||
What’s one small process change that saved your business money this year?
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-03-26 Thursday, 13:00
|
|
||||||
|
|
||||||
- Subreddit: `r/EntrepreneurRideAlong`
|
|
||||||
- Post #4
|
|
||||||
- Use replies: `2`, `4`, `9`, `11`, `15`
|
|
||||||
|
|
||||||
```text
|
|
||||||
Building in a boring category taught me that boring problems are usually expensive
|
|
||||||
|
|
||||||
I’m building around QR codes, which sounds incredibly boring on paper.
|
|
||||||
|
|
||||||
But boring problems are often the ones people pay to avoid.
|
|
||||||
|
|
||||||
In this case, it’s stuff like:
|
|
||||||
- reprinting menus
|
|
||||||
- fixing outdated flyers
|
|
||||||
- updating event info after posters are already out
|
|
||||||
- managing lots of QR destinations across campaigns
|
|
||||||
|
|
||||||
Nobody is emotionally excited about QR codes.
|
|
||||||
They’re emotionally excited about not dealing with preventable mess.
|
|
||||||
|
|
||||||
Anyone else building something “unsexy” that turned out to have very real pain behind it?
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-03-27 Friday, 14:30
|
|
||||||
|
|
||||||
- Subreddit: `r/SideProject`
|
|
||||||
- Post #5
|
|
||||||
- Title: `The weird part about building a QR product is that the technical problem isn’t the interesting one`
|
|
||||||
- Use replies: `1`, `6`, `7`, `8`, `15`
|
|
||||||
|
|
||||||
```text
|
|
||||||
Generating a QR image is trivial.
|
|
||||||
|
|
||||||
What turned out to be more interesting:
|
|
||||||
- what happens after print
|
|
||||||
- whether someone can change the destination later
|
|
||||||
- what analytics are actually useful
|
|
||||||
- how privacy concerns show up once tracking enters the conversation
|
|
||||||
- how bulk workflows matter way more than expected
|
|
||||||
|
|
||||||
It’s one of those products that looks dumb-simple from the outside and much more operational once you talk to users.
|
|
||||||
|
|
||||||
What kind of side project looked simple until real use cases started showing up?
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-03-31 Tuesday, 14:00
|
|
||||||
|
|
||||||
- Subreddit: `r/AlphaandBetaTesters`
|
|
||||||
- Post #7
|
|
||||||
- Use replies: `3`, `5`, `6`, `8`, `14`
|
|
||||||
|
|
||||||
```text
|
|
||||||
Looking for feedback from anyone who has used QR codes in restaurants, events, print, or packaging
|
|
||||||
|
|
||||||
I’m trying to learn from people who use QR codes in the real world, not just in theory.
|
|
||||||
|
|
||||||
Especially if you’ve used them for:
|
|
||||||
- menus
|
|
||||||
- flyers
|
|
||||||
- product packaging
|
|
||||||
- event materials
|
|
||||||
- WiFi / contact sharing
|
|
||||||
- agency campaigns
|
|
||||||
|
|
||||||
Things I’m curious about:
|
|
||||||
- what changes most often after something is printed?
|
|
||||||
- what’s annoying about current tools?
|
|
||||||
- do you actually care about scan analytics?
|
|
||||||
- does privacy / GDPR affect vendor choice at all?
|
|
||||||
|
|
||||||
I’m happy to share what I’m building if useful, but mostly looking for honest feedback from people who’ve dealt with this firsthand.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-04-02 Thursday, 14:00
|
|
||||||
|
|
||||||
- Subreddit: `r/RoastMyStartup`
|
|
||||||
- Post #8
|
|
||||||
- Use replies: `2`, `10`, `11`, `14`, `15`
|
|
||||||
|
|
||||||
```text
|
|
||||||
Roast my positioning: is “avoid reprints and broken QR campaigns” a strong enough problem?
|
|
||||||
|
|
||||||
I’m working on a product around dynamic QR codes.
|
|
||||||
|
|
||||||
The positioning I’m testing is less “make QR codes” and more:
|
|
||||||
“avoid reprints, outdated links, and messy campaign management.”
|
|
||||||
|
|
||||||
Target users are mostly:
|
|
||||||
- small businesses
|
|
||||||
- restaurants
|
|
||||||
- marketers
|
|
||||||
- agencies
|
|
||||||
- event / packaging use cases
|
|
||||||
|
|
||||||
The questions I’d love roasted:
|
|
||||||
- does the pain feel real enough?
|
|
||||||
- does this sound too niche?
|
|
||||||
- what part sounds generic or weak?
|
|
||||||
- what would make you ignore this instantly?
|
|
||||||
|
|
||||||
Happy to share the product if the sub is okay with it.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-04-03 Friday, 14:30
|
|
||||||
|
|
||||||
- Subreddit: `r/ProductMgmt`
|
|
||||||
- Post #6
|
|
||||||
- Use replies: `1`, `7`, `8`, `10`, `12`
|
|
||||||
|
|
||||||
```text
|
|
||||||
Users say they want a QR generator. What they actually want is damage control.
|
|
||||||
|
|
||||||
A PM lesson I didn’t expect:
|
|
||||||
|
|
||||||
People describe the need as “I need a QR code.”
|
|
||||||
But what they actually care about is something like:
|
|
||||||
“I need this thing to not break once it’s already printed.”
|
|
||||||
|
|
||||||
That changes what feels important.
|
|
||||||
|
|
||||||
“Generate code” sounds like the core feature.
|
|
||||||
But retention/value probably sits closer to:
|
|
||||||
- edit later
|
|
||||||
- track scans
|
|
||||||
- handle multiple codes
|
|
||||||
- avoid privacy headaches
|
|
||||||
- manage existing campaigns cleanly
|
|
||||||
|
|
||||||
Have you seen that mismatch in your own product?
|
|
||||||
What users ask for first vs. what actually matters later?
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-04-07 Tuesday, 14:00
|
|
||||||
|
|
||||||
- Subreddit: `r/startups`
|
|
||||||
- Post #9
|
|
||||||
- Use replies: `1`, `7`, `9`, `10`, `15`
|
|
||||||
|
|
||||||
```text
|
|
||||||
I’m starting to think “edit after print” is a stronger product promise than “track scans”
|
|
||||||
|
|
||||||
Interesting thing from early positioning:
|
|
||||||
|
|
||||||
I assumed analytics would be the hero feature.
|
|
||||||
But “I can change the destination later” seems to click faster.
|
|
||||||
|
|
||||||
Makes sense in hindsight.
|
|
||||||
Tracking is nice.
|
|
||||||
Avoiding expensive mistakes is urgent.
|
|
||||||
|
|
||||||
So now I’m wondering if the better message is:
|
|
||||||
- first promise control
|
|
||||||
- then introduce analytics
|
|
||||||
- then layer in bulk / workflow / privacy
|
|
||||||
|
|
||||||
If you’ve sold into small businesses or marketers:
|
|
||||||
what kind of promise gets attention faster, insight or control?
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2026-04-09 Thursday, 14:30
|
|
||||||
|
|
||||||
- Subreddit: `r/smallbusiness`
|
|
||||||
- Post #10
|
|
||||||
- Use replies: `3`, `4`, `6`, `13`, `15`
|
|
||||||
|
|
||||||
```text
|
|
||||||
What looks like a tiny print detail can quietly waste a lot of money
|
|
||||||
|
|
||||||
I keep coming back to this:
|
|
||||||
|
|
||||||
A broken link on a website is annoying.
|
|
||||||
A broken link on printed material is expensive.
|
|
||||||
|
|
||||||
Because now the problem is sitting in:
|
|
||||||
- stores
|
|
||||||
- restaurants
|
|
||||||
- posters
|
|
||||||
- packaging
|
|
||||||
- tables
|
|
||||||
- flyers already handed out
|
|
||||||
|
|
||||||
Feels like one of those things that sounds tiny until you count the friction and replacement cost.
|
|
||||||
|
|
||||||
What’s a “small detail” in your business that causes way more downstream cost than people assume?
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
PRODID:-//QR Master//Reddit 4 Week Calendar Universal//EN
|
|
||||||
CALSCALE:GREGORIAN
|
|
||||||
METHOD:PUBLISH
|
|
||||||
X-WR-CALNAME:QR Master Reddit Plan
|
|
||||||
X-WR-TIMEZONE:Europe/Berlin
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260316-comments-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260316T133000Z
|
|
||||||
DTEND:20260316T141500Z
|
|
||||||
SUMMARY:Reddit comment block - r/startups + r/SaaS
|
|
||||||
DESCRIPTION:Warm up account and build relevant karma. No links unless asked directly.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260317-startups-post-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260317T120000Z
|
|
||||||
DTEND:20260317T124500Z
|
|
||||||
SUMMARY:Reddit post - r/startups
|
|
||||||
DESCRIPTION:Post: One URL change can ruin 500 flyers. Link only if asked. Use https://www.qrmaster.net/reprint-calculator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260318-startups-replies-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260318T133000Z
|
|
||||||
DTEND:20260318T141500Z
|
|
||||||
SUMMARY:Reddit replies - r/startups
|
|
||||||
DESCRIPTION:Reply to all serious comments from Tuesday. Keep link-free unless asked directly.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260319-saas-post-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260319T120000Z
|
|
||||||
DTEND:20260319T124500Z
|
|
||||||
SUMMARY:Reddit post - r/SaaS
|
|
||||||
DESCRIPTION:Post: The real pain starts after print. Link only if asked. Use https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260320-saas-comments-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260320T133000Z
|
|
||||||
DTEND:20260320T141500Z
|
|
||||||
SUMMARY:Reddit comment block - r/SaaS
|
|
||||||
DESCRIPTION:Extend the Thursday discussion. Tracking link if needed: https://www.qrmaster.net/qr-code-tracking
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260323-smallbiz-sideproject-comments-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260323T133000Z
|
|
||||||
DTEND:20260323T141500Z
|
|
||||||
SUMMARY:Reddit comment block - r/smallbusiness + r/SideProject
|
|
||||||
DESCRIPTION:Warm both subs before posting this week. No links unless asked directly.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260324-smallbusiness-post-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260324T123000Z
|
|
||||||
DTEND:20260324T131500Z
|
|
||||||
SUMMARY:Reddit post - r/smallbusiness
|
|
||||||
DESCRIPTION:Post: Most small businesses do not need more tools. Default link if asked: https://www.qrmaster.net/reprint-calculator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260325-smallbusiness-replies-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260325T133000Z
|
|
||||||
DTEND:20260325T141500Z
|
|
||||||
SUMMARY:Reddit replies - r/smallbusiness
|
|
||||||
DESCRIPTION:Answer practical questions from Tuesday. Drop links only when the use case is obvious.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260326-sideproject-post-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260326T120000Z
|
|
||||||
DTEND:20260326T124500Z
|
|
||||||
SUMMARY:Reddit post - r/SideProject
|
|
||||||
DESCRIPTION:Post: The technical problem is not the interesting one. Core link: https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260327-sideproject-comments-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260327T133000Z
|
|
||||||
DTEND:20260327T141500Z
|
|
||||||
SUMMARY:Reddit comment block - r/SideProject
|
|
||||||
DESCRIPTION:Follow up on Thursday. Bulk link if needed: https://www.qrmaster.net/bulk-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260330-feedback-roast-comments-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260330T133000Z
|
|
||||||
DTEND:20260330T141500Z
|
|
||||||
SUMMARY:Reddit comment block - feedback week warm-up
|
|
||||||
DESCRIPTION:Warm up r/AlphaandBetaTesters and r/RoastMyStartup. No links today.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260331-alpha-beta-post-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260331T120000Z
|
|
||||||
DTEND:20260331T124500Z
|
|
||||||
SUMMARY:Reddit post - r/AlphaandBetaTesters
|
|
||||||
DESCRIPTION:Feedback request. Put the link in the first comment. Use https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260401-alpha-beta-replies-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260401T133000Z
|
|
||||||
DTEND:20260401T141500Z
|
|
||||||
SUMMARY:Reddit replies - r/AlphaandBetaTesters
|
|
||||||
DESCRIPTION:Answer all serious feedback. Privacy proof only if asked: https://www.qrmaster.net/privacy
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260402-roast-post-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260402T120000Z
|
|
||||||
DTEND:20260402T124500Z
|
|
||||||
SUMMARY:Reddit post - r/RoastMyStartup
|
|
||||||
DESCRIPTION:Roast my positioning. Direct site link is okay here: https://www.qrmaster.net/
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260403-objection-review-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260403T133000Z
|
|
||||||
DTEND:20260403T141500Z
|
|
||||||
SUMMARY:Reddit objection review
|
|
||||||
DESCRIPTION:Summarize week 3 objections: pricing, niche, privacy, free generator comparison, ICP clarity.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260406-saas-comments-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260406T133000Z
|
|
||||||
DTEND:20260406T141500Z
|
|
||||||
SUMMARY:Reddit comment block - r/SaaS
|
|
||||||
DESCRIPTION:Re-enter with objection-informed comments before the next post. No links unless asked.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260407-saas-post-2-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260407T120000Z
|
|
||||||
DTEND:20260407T124500Z
|
|
||||||
SUMMARY:Reddit post - r/SaaS follow-up
|
|
||||||
DESCRIPTION:Post: edit later vs track scans. Default link if asked: https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260408-saas-replies-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260408T133000Z
|
|
||||||
DTEND:20260408T141500Z
|
|
||||||
SUMMARY:Reddit replies - r/SaaS
|
|
||||||
DESCRIPTION:Work the Tuesday thread hard for comments, not just upvotes.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260409-promo-post-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260409T123000Z
|
|
||||||
DTEND:20260409T131500Z
|
|
||||||
SUMMARY:Reddit promo post
|
|
||||||
DESCRIPTION:Post in r/Plugyourproduct or r/startups_promotion. Direct link okay: https://www.qrmaster.net/
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260410-followup-universal@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260410T133000Z
|
|
||||||
DTEND:20260410T141500Z
|
|
||||||
SUMMARY:Reddit follow-up block
|
|
||||||
DESCRIPTION:Answer all promo-thread comments publicly. No DMs, no pressure.
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
BEGIN:VCALENDAR
|
|
||||||
VERSION:2.0
|
|
||||||
PRODID:-//QR Master//Reddit 4 Week Calendar//EN
|
|
||||||
CALSCALE:GREGORIAN
|
|
||||||
METHOD:PUBLISH
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260316-comments@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260316T143000
|
|
||||||
DTEND:20260316T151500
|
|
||||||
SUMMARY:Reddit comment block - r/startups + r/SaaS
|
|
||||||
DESCRIPTION:Goal: warm up account and build relevant karma. No links unless asked directly.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260317-startups-post@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260317T130000
|
|
||||||
DTEND:20260317T134500
|
|
||||||
SUMMARY:Reddit post - r/startups
|
|
||||||
DESCRIPTION:Title: One URL change can ruin 500 flyers. Link only if asked. Use https://www.qrmaster.net/reprint-calculator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260318-startups-replies@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260318T143000
|
|
||||||
DTEND:20260318T151500
|
|
||||||
SUMMARY:Reddit replies - r/startups
|
|
||||||
DESCRIPTION:Reply to all serious comments from Tuesday. Keep link-free unless asked directly.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260319-saas-post@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260319T130000
|
|
||||||
DTEND:20260319T134500
|
|
||||||
SUMMARY:Reddit post - r/SaaS
|
|
||||||
DESCRIPTION:Title: The real pain starts after print. Link only if asked. Use https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260320-saas-comments@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260320T143000
|
|
||||||
DTEND:20260320T151500
|
|
||||||
SUMMARY:Reddit comment block - r/SaaS
|
|
||||||
DESCRIPTION:Extend the Thursday discussion. If asked about tracking, use https://www.qrmaster.net/qr-code-tracking
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260323-smallbiz-sideproject-comments@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260323T143000
|
|
||||||
DTEND:20260323T151500
|
|
||||||
SUMMARY:Reddit comment block - r/smallbusiness + r/SideProject
|
|
||||||
DESCRIPTION:Warm both subs before posting this week. No links unless asked directly.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260324-smallbusiness-post@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260324T133000
|
|
||||||
DTEND:20260324T141500
|
|
||||||
SUMMARY:Reddit post - r/smallbusiness
|
|
||||||
DESCRIPTION:Title: Most small businesses don't need more tools. Default link if asked: https://www.qrmaster.net/reprint-calculator Restaurant link: https://www.qrmaster.net/use-cases/restaurant-menu-qr-codes
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260325-smallbusiness-replies@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260325T143000
|
|
||||||
DTEND:20260325T151500
|
|
||||||
SUMMARY:Reddit replies - r/smallbusiness
|
|
||||||
DESCRIPTION:Answer practical questions from Tuesday. Drop links only when the use case is obvious.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260326-sideproject-post@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260326T130000
|
|
||||||
DTEND:20260326T134500
|
|
||||||
SUMMARY:Reddit post - r/SideProject
|
|
||||||
DESCRIPTION:Title: The technical problem isn't the interesting one. Core link if asked: https://www.qrmaster.net/dynamic-qr-code-generator Bulk link: https://www.qrmaster.net/bulk-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260327-sideproject-comments@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260327T143000
|
|
||||||
DTEND:20260327T151500
|
|
||||||
SUMMARY:Reddit comment block - r/SideProject
|
|
||||||
DESCRIPTION:Follow up on the Thursday thread. Use bulk link if people ask about scale or packaging.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260330-feedback-roast-comments@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260330T153000
|
|
||||||
DTEND:20260330T161500
|
|
||||||
SUMMARY:Reddit comment block - feedback week warm-up
|
|
||||||
DESCRIPTION:Warm up r/AlphaandBetaTesters and r/RoastMyStartup. No links today.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260331-alpha-beta-post@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260331T140000
|
|
||||||
DTEND:20260331T144500
|
|
||||||
SUMMARY:Reddit post - r/AlphaandBetaTesters
|
|
||||||
DESCRIPTION:Feedback request. Put the link in the first comment, not the post body. Use https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260401-alpha-beta-replies@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260401T153000
|
|
||||||
DTEND:20260401T161500
|
|
||||||
SUMMARY:Reddit replies - r/AlphaandBetaTesters
|
|
||||||
DESCRIPTION:Answer all serious feedback. Privacy proof only if asked: https://www.qrmaster.net/privacy
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260402-roast-post@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260402T140000
|
|
||||||
DTEND:20260402T144500
|
|
||||||
SUMMARY:Reddit post - r/RoastMyStartup
|
|
||||||
DESCRIPTION:Roast my positioning. Direct site link is okay here: https://www.qrmaster.net/
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260403-objection-review@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260403T153000
|
|
||||||
DTEND:20260403T161500
|
|
||||||
SUMMARY:Reddit objection review
|
|
||||||
DESCRIPTION:Summarize the week-3 objections: pricing, niche, privacy, free generator comparison, ICP clarity.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260406-saas-comments@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260406T153000
|
|
||||||
DTEND:20260406T161500
|
|
||||||
SUMMARY:Reddit comment block - r/SaaS
|
|
||||||
DESCRIPTION:Re-enter with objection-informed comments before the next post. No links unless asked.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260407-saas-post-2@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260407T140000
|
|
||||||
DTEND:20260407T144500
|
|
||||||
SUMMARY:Reddit post - r/SaaS follow-up
|
|
||||||
DESCRIPTION:Title: edit later vs track scans. Default link if asked: https://www.qrmaster.net/dynamic-qr-code-generator Measurement angle: https://www.qrmaster.net/qr-code-for-marketing-campaigns
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260408-saas-replies@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260408T153000
|
|
||||||
DTEND:20260408T161500
|
|
||||||
SUMMARY:Reddit replies - r/SaaS
|
|
||||||
DESCRIPTION:Work the Tuesday thread hard for comments, not just upvotes.
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260409-promo-post@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260409T143000
|
|
||||||
DTEND:20260409T151500
|
|
||||||
SUMMARY:Reddit promo post - r/Plugyourproduct or r/startups_promotion
|
|
||||||
DESCRIPTION:Direct link in post is okay. Use https://www.qrmaster.net/ Optional focused link: https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
END:VEVENT
|
|
||||||
BEGIN:VEVENT
|
|
||||||
UID:reddit-20260410-followup@qrmaster.net
|
|
||||||
DTSTAMP:20260312T120000Z
|
|
||||||
DTSTART:20260410T153000
|
|
||||||
DTEND:20260410T161500
|
|
||||||
SUMMARY:Reddit follow-up block
|
|
||||||
DESCRIPTION:Answer all promo-thread comments publicly. No DMs, no pressure, keep it in-thread.
|
|
||||||
END:VEVENT
|
|
||||||
END:VCALENDAR
|
|
||||||
@@ -1,457 +0,0 @@
|
|||||||
# Reddit 4-Week Calendar for QR Master
|
|
||||||
|
|
||||||
Times below are in Europe/Berlin local time.
|
|
||||||
Use clean URLs only. Do not add UTM parameters to public Reddit links.
|
|
||||||
|
|
||||||
## Link Map
|
|
||||||
|
|
||||||
- Reprint / cost angle: `https://www.qrmaster.net/reprint-calculator`
|
|
||||||
- Dynamic after print: `https://www.qrmaster.net/dynamic-qr-code-generator`
|
|
||||||
- Restaurant / menu angle: `https://www.qrmaster.net/use-cases/restaurant-menu-qr-codes`
|
|
||||||
- Flyer / print attribution: `https://www.qrmaster.net/use-cases/flyer-qr-codes`
|
|
||||||
- Campaign measurement: `https://www.qrmaster.net/qr-code-for-marketing-campaigns`
|
|
||||||
- Tracking / analytics: `https://www.qrmaster.net/qr-code-tracking`
|
|
||||||
- Bulk / packaging: `https://www.qrmaster.net/bulk-qr-code-generator`
|
|
||||||
- Packaging use case: `https://www.qrmaster.net/use-cases/packaging-qr-codes`
|
|
||||||
- Privacy proof only: `https://www.qrmaster.net/privacy`
|
|
||||||
- Full site feedback / promo-only subs: `https://www.qrmaster.net/`
|
|
||||||
|
|
||||||
## 2026-03-16 Monday, 14:30
|
|
||||||
|
|
||||||
- Type: Comment block
|
|
||||||
- Subreddits: `r/startups`, `r/SaaS`
|
|
||||||
- Goal: Warm up account and build relevant karma
|
|
||||||
- Rule: No links unless someone explicitly asks
|
|
||||||
- Comment prompt ideas:
|
|
||||||
- "The expensive part starts after print, not at QR generation."
|
|
||||||
- "A lot of SMB tools sound boring until one mistake turns printed material into waste."
|
|
||||||
- "I care less about the QR itself and more about what happens when the destination changes later."
|
|
||||||
|
|
||||||
## 2026-03-17 Tuesday, 13:00
|
|
||||||
|
|
||||||
- Type: Main post
|
|
||||||
- Subreddit: `r/startups`
|
|
||||||
- Title: `One URL change can ruin 500 flyers. That pain is more real than I expected.`
|
|
||||||
- Body:
|
|
||||||
|
|
||||||
```text
|
|
||||||
I underestimated how annoying printed mistakes are.
|
|
||||||
|
|
||||||
A lot of software problems are reversible.
|
|
||||||
Print problems aren’t.
|
|
||||||
|
|
||||||
If a landing page changes after flyers, posters, inserts, or menus are already out there, someone has to:
|
|
||||||
- live with a broken flow
|
|
||||||
- reprint everything
|
|
||||||
- or patch it manually in a messy way
|
|
||||||
|
|
||||||
That sounds minor until you talk to people actually running campaigns or local businesses.
|
|
||||||
|
|
||||||
What small operational problem ended up being much more expensive than it looked at first?
|
|
||||||
```
|
|
||||||
|
|
||||||
- Link if asked: `https://www.qrmaster.net/reprint-calculator`
|
|
||||||
- Possible replies:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Yeah, that’s the part I underestimated too. The QR itself is easy. The expensive part is when the destination changes after print.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
I built around exactly that issue, so obvious bias here:
|
|
||||||
https://www.qrmaster.net/reprint-calculator
|
|
||||||
|
|
||||||
If links are annoying in this thread, I can just explain the workflow here.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
Static is fine if the URL is truly permanent. The pain starts when someone assumes “permanent” and the campaign changes two weeks later.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-03-18 Wednesday, 14:30
|
|
||||||
|
|
||||||
- Type: Reply block
|
|
||||||
- Subreddits: `r/startups`
|
|
||||||
- Goal: Reply to every serious comment from the Tuesday post
|
|
||||||
- Link rule: Only if asked directly
|
|
||||||
- Safe reply template:
|
|
||||||
|
|
||||||
```text
|
|
||||||
That’s fair. I’m building in this space, so obvious bias if I share the product. Happy to keep it link-free and just explain the setup.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-03-19 Thursday, 13:00
|
|
||||||
|
|
||||||
- Type: Main post
|
|
||||||
- Subreddit: `r/SaaS`
|
|
||||||
- Title: `I thought QR code software was about generation. The real pain starts after print.`
|
|
||||||
- Body:
|
|
||||||
|
|
||||||
```text
|
|
||||||
I used to think the value was “make a QR code fast.”
|
|
||||||
|
|
||||||
It’s not.
|
|
||||||
|
|
||||||
The painful part starts after something is already printed:
|
|
||||||
- the menu changes
|
|
||||||
- the event page changes
|
|
||||||
- the campaign URL changes
|
|
||||||
- someone notices a typo too late
|
|
||||||
|
|
||||||
One small change can turn a stack of flyers into trash.
|
|
||||||
|
|
||||||
That shifted how I think about the whole category.
|
|
||||||
The QR itself is easy.
|
|
||||||
The expensive part is everything around it.
|
|
||||||
|
|
||||||
Anyone else building in a category where the “simple feature” isn’t actually where the value is?
|
|
||||||
```
|
|
||||||
|
|
||||||
- Link if asked: `https://www.qrmaster.net/dynamic-qr-code-generator`
|
|
||||||
- Possible replies:
|
|
||||||
|
|
||||||
```text
|
|
||||||
That’s exactly how I see it now too. “Generate” sounds like the product, but “edit after print” is where the value starts.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
I built a tool for that exact use case, so obvious founder bias:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
I thought analytics would be the hook. In practice, “don’t make me reprint stuff” lands faster.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-03-20 Friday, 14:30
|
|
||||||
|
|
||||||
- Type: Comment block
|
|
||||||
- Subreddits: `r/SaaS`
|
|
||||||
- Goal: Extend the Thursday discussion without posting a new link
|
|
||||||
- If someone asks about tracking: `https://www.qrmaster.net/qr-code-tracking`
|
|
||||||
- Safe tracking reply:
|
|
||||||
|
|
||||||
```text
|
|
||||||
If the goal is proof instead of guesswork, tracking matters. I’m building in that space too, so obvious bias:
|
|
||||||
https://www.qrmaster.net/qr-code-tracking
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-03-23 Monday, 14:30
|
|
||||||
|
|
||||||
- Type: Comment block
|
|
||||||
- Subreddits: `r/smallbusiness`, `r/SideProject`
|
|
||||||
- Goal: Warm both subs before posting this week
|
|
||||||
- Rule: No link unless asked directly
|
|
||||||
|
|
||||||
## 2026-03-24 Tuesday, 13:30
|
|
||||||
|
|
||||||
- Type: Main post
|
|
||||||
- Subreddit: `r/smallbusiness`
|
|
||||||
- Title: `Most small businesses don’t need more tools. They need fewer preventable mistakes.`
|
|
||||||
- Body:
|
|
||||||
|
|
||||||
```text
|
|
||||||
I keep seeing the same pattern:
|
|
||||||
|
|
||||||
Owners usually don’t want “more software.”
|
|
||||||
They want fewer headaches.
|
|
||||||
|
|
||||||
With QR codes, the common headaches seem to be:
|
|
||||||
- printing a code that can’t be updated later
|
|
||||||
- linking to a bad mobile page
|
|
||||||
- not knowing if anyone scanned it
|
|
||||||
- having to redo materials because one URL changed
|
|
||||||
|
|
||||||
That feels less like a marketing problem and more like an operations problem.
|
|
||||||
|
|
||||||
What low-effort process change saved you time or money recently?
|
|
||||||
```
|
|
||||||
|
|
||||||
- Default link if asked: `https://www.qrmaster.net/reprint-calculator`
|
|
||||||
- Restaurant/menu link if relevant: `https://www.qrmaster.net/use-cases/restaurant-menu-qr-codes`
|
|
||||||
- Possible replies:
|
|
||||||
|
|
||||||
```text
|
|
||||||
That’s basically how I think about it now too. Most owners don’t want a “QR platform.” They want to avoid paying twice for the same print run.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
If the menu changes regularly, I wouldn’t print a static QR. I built around exactly that use case, so bias disclosed:
|
|
||||||
https://www.qrmaster.net/use-cases/restaurant-menu-qr-codes
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
For a more general cost angle, this is the cleanest page to share:
|
|
||||||
https://www.qrmaster.net/reprint-calculator
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-03-25 Wednesday, 14:30
|
|
||||||
|
|
||||||
- Type: Reply block
|
|
||||||
- Subreddits: `r/smallbusiness`
|
|
||||||
- Goal: Answer every practical question from the Tuesday post
|
|
||||||
- Rule: Only drop a link when the use case is obvious
|
|
||||||
|
|
||||||
## 2026-03-26 Thursday, 13:00
|
|
||||||
|
|
||||||
- Type: Main post
|
|
||||||
- Subreddit: `r/SideProject`
|
|
||||||
- Title: `The weird part about building a QR product is that the technical problem isn’t the interesting one`
|
|
||||||
- Body:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Generating a QR image is trivial.
|
|
||||||
|
|
||||||
What turned out to be more interesting:
|
|
||||||
- what happens after print
|
|
||||||
- whether someone can change the destination later
|
|
||||||
- what analytics are actually useful
|
|
||||||
- how privacy concerns show up once tracking enters the conversation
|
|
||||||
- how bulk workflows matter way more than expected
|
|
||||||
|
|
||||||
It’s one of those products that looks dumb-simple from the outside and much more operational once you talk to users.
|
|
||||||
|
|
||||||
What kind of side project looked simple until real use cases started showing up?
|
|
||||||
```
|
|
||||||
|
|
||||||
- Link if asked: `https://www.qrmaster.net/dynamic-qr-code-generator`
|
|
||||||
- Bulk link if someone asks about scale: `https://www.qrmaster.net/bulk-qr-code-generator`
|
|
||||||
- Possible replies:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Exactly. The QR itself is not the product. The post-print control is.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
I built around that exact issue, so obvious bias:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
If the interesting part for you is scale, the bulk side is here:
|
|
||||||
https://www.qrmaster.net/bulk-qr-code-generator
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-03-27 Friday, 14:30
|
|
||||||
|
|
||||||
- Type: Comment block
|
|
||||||
- Subreddits: `r/SideProject`
|
|
||||||
- Goal: Follow up on the Thursday thread and answer bulk/packaging questions
|
|
||||||
- Primary link if relevant: `https://www.qrmaster.net/bulk-qr-code-generator`
|
|
||||||
|
|
||||||
## 2026-03-30 Monday, 15:30
|
|
||||||
|
|
||||||
- Type: Comment block
|
|
||||||
- Subreddits: `r/AlphaandBetaTesters`, `r/RoastMyStartup`
|
|
||||||
- Goal: Warm up both communities before feedback posts
|
|
||||||
- Rule: No links today
|
|
||||||
|
|
||||||
## 2026-03-31 Tuesday, 14:00
|
|
||||||
|
|
||||||
- Type: Feedback post
|
|
||||||
- Subreddit: `r/AlphaandBetaTesters`
|
|
||||||
- Title: `Looking for feedback from anyone who has used QR codes in restaurants, events, print, or packaging`
|
|
||||||
- Body:
|
|
||||||
|
|
||||||
```text
|
|
||||||
I’m trying to learn from people who use QR codes in the real world, not just in theory.
|
|
||||||
|
|
||||||
Especially if you’ve used them for:
|
|
||||||
- menus
|
|
||||||
- flyers
|
|
||||||
- product packaging
|
|
||||||
- event materials
|
|
||||||
- WiFi / contact sharing
|
|
||||||
- agency campaigns
|
|
||||||
|
|
||||||
Things I’m curious about:
|
|
||||||
- what changes most often after something is printed?
|
|
||||||
- what’s annoying about current tools?
|
|
||||||
- do you actually care about scan analytics?
|
|
||||||
- does privacy / GDPR affect vendor choice at all?
|
|
||||||
|
|
||||||
I’m happy to share what I’m building if useful, but mostly looking for honest feedback from people who’ve dealt with this firsthand.
|
|
||||||
```
|
|
||||||
|
|
||||||
- Link placement: first comment, not the post body
|
|
||||||
- First comment link: `https://www.qrmaster.net/dynamic-qr-code-generator`
|
|
||||||
- Possible replies:
|
|
||||||
|
|
||||||
```text
|
|
||||||
This is the product I’m testing the messaging on, so obvious bias:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
That’s useful. The thing I keep hearing too is that the problem starts once something is already printed.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
If the privacy side is the bigger concern, I can share how I’m handling that specifically instead of pitching the product.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-04-01 Wednesday, 15:30
|
|
||||||
|
|
||||||
- Type: Reply block
|
|
||||||
- Subreddits: `r/AlphaandBetaTesters`
|
|
||||||
- Goal: Answer all serious feedback and record objections
|
|
||||||
- Privacy proof link only if asked: `https://www.qrmaster.net/privacy`
|
|
||||||
|
|
||||||
## 2026-04-02 Thursday, 14:00
|
|
||||||
|
|
||||||
- Type: Roast post
|
|
||||||
- Subreddit: `r/RoastMyStartup`
|
|
||||||
- Title: `Roast my positioning: is “avoid reprints and broken QR campaigns” a strong enough problem?`
|
|
||||||
- Body:
|
|
||||||
|
|
||||||
```text
|
|
||||||
I’m working on a product around dynamic QR codes.
|
|
||||||
|
|
||||||
The positioning I’m testing is less “make QR codes” and more:
|
|
||||||
“avoid reprints, outdated links, and messy campaign management.”
|
|
||||||
|
|
||||||
Target users are mostly:
|
|
||||||
- small businesses
|
|
||||||
- restaurants
|
|
||||||
- marketers
|
|
||||||
- agencies
|
|
||||||
- event / packaging use cases
|
|
||||||
|
|
||||||
The questions I’d love roasted:
|
|
||||||
- does the pain feel real enough?
|
|
||||||
- does this sound too niche?
|
|
||||||
- what part sounds generic or weak?
|
|
||||||
- what would make you ignore this instantly?
|
|
||||||
|
|
||||||
Happy to share the product if the sub is okay with it.
|
|
||||||
```
|
|
||||||
|
|
||||||
- Link placement: direct link in post is okay
|
|
||||||
- Link: `https://www.qrmaster.net/`
|
|
||||||
- Possible replies:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Fair. The goal here is honestly sharper criticism, not a soft launch.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
That’s a good callout. If the pain still sounds too “small,” then the messaging isn’t strong enough yet.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
Yep, that’s the site:
|
|
||||||
https://www.qrmaster.net/
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-04-03 Friday, 15:30
|
|
||||||
|
|
||||||
- Type: Objection review
|
|
||||||
- Goal: Summarize the week-3 feedback into 3 to 5 objections
|
|
||||||
- Typical objection buckets:
|
|
||||||
- "why pay for QR codes?"
|
|
||||||
- "sounds niche"
|
|
||||||
- "privacy / GDPR?"
|
|
||||||
- "what’s different from free generators?"
|
|
||||||
- "who is this really for?"
|
|
||||||
|
|
||||||
## 2026-04-06 Monday, 15:30
|
|
||||||
|
|
||||||
- Type: Comment block
|
|
||||||
- Subreddits: `r/SaaS`
|
|
||||||
- Goal: Re-enter with objection-informed comments before the next post
|
|
||||||
- Rule: No links unless asked
|
|
||||||
|
|
||||||
## 2026-04-07 Tuesday, 14:00
|
|
||||||
|
|
||||||
- Type: Main post
|
|
||||||
- Subreddit: `r/SaaS`
|
|
||||||
- Title: `I’m starting to think “edit later” is a stronger product promise than “track scans”`
|
|
||||||
- Body:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Interesting thing from early positioning:
|
|
||||||
|
|
||||||
I assumed analytics would be the hero feature.
|
|
||||||
But “I can change the destination later” seems to click faster.
|
|
||||||
|
|
||||||
Makes sense in hindsight.
|
|
||||||
Tracking is nice.
|
|
||||||
Avoiding expensive mistakes is urgent.
|
|
||||||
|
|
||||||
So now I’m wondering if the better message is:
|
|
||||||
- first promise control
|
|
||||||
- then introduce analytics
|
|
||||||
- then layer in bulk / workflow / privacy
|
|
||||||
|
|
||||||
If you’ve sold into small businesses or marketers:
|
|
||||||
what kind of promise gets attention faster, insight or control?
|
|
||||||
```
|
|
||||||
|
|
||||||
- Default link if asked: `https://www.qrmaster.net/dynamic-qr-code-generator`
|
|
||||||
- If the thread becomes measurement-heavy: `https://www.qrmaster.net/qr-code-for-marketing-campaigns`
|
|
||||||
- Possible replies:
|
|
||||||
|
|
||||||
```text
|
|
||||||
That’s exactly the split I’m seeing too. “Insight” sounds nice, “control” feels urgent.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
I built around that exact use case, so obvious bias:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
If the attribution side is the interesting part for you, this is the more relevant page:
|
|
||||||
https://www.qrmaster.net/qr-code-for-marketing-campaigns
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-04-08 Wednesday, 15:30
|
|
||||||
|
|
||||||
- Type: Reply block
|
|
||||||
- Subreddits: `r/SaaS`
|
|
||||||
- Goal: Work the Tuesday thread hard for comments, not just upvotes
|
|
||||||
|
|
||||||
## 2026-04-09 Thursday, 14:30
|
|
||||||
|
|
||||||
- Type: Promo post
|
|
||||||
- Subreddit: `r/Plugyourproduct` or `r/startups_promotion`
|
|
||||||
- Title: `QR Master: editable QR codes for print campaigns, menus, packaging, and analytics`
|
|
||||||
- Body:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Built QR Master to solve a simple but expensive problem:
|
|
||||||
people print QR codes, then the destination changes later.
|
|
||||||
|
|
||||||
What it does:
|
|
||||||
- editable QR destinations after print
|
|
||||||
- scan tracking
|
|
||||||
- bulk workflows
|
|
||||||
- campaign-friendly use cases for menus, flyers, events, and packaging
|
|
||||||
|
|
||||||
Looking for honest feedback on the value prop and landing page clarity.
|
|
||||||
```
|
|
||||||
|
|
||||||
- Link placement: direct link in post
|
|
||||||
- Link: `https://www.qrmaster.net/`
|
|
||||||
- Possible replies:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Appreciate it. The core promise is really “don’t reprint just because the URL changed.”
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
If you want the most direct core page instead of the homepage, this is it:
|
|
||||||
https://www.qrmaster.net/dynamic-qr-code-generator
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
If you’re more interested in measurement than editability, this page is the better entry point:
|
|
||||||
https://www.qrmaster.net/qr-code-tracking
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2026-04-10 Friday, 15:30
|
|
||||||
|
|
||||||
- Type: Follow-up block
|
|
||||||
- Goal: Answer all promo-thread comments publicly and close the 4-week run
|
|
||||||
- Rule: No DMs, no pressure, keep every answer in-thread
|
|
||||||
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
# 30-Day X/Twitter Content Plan for QR Master
|
|
||||||
|
|
||||||
Use this as a `30-day X/Twitter content plan` for a founder-led QR Master account. It is written in English and optimized for reach first, with product relevance built in.
|
|
||||||
|
|
||||||
## Positioning for the Month
|
|
||||||
|
|
||||||
`Dynamic QR codes for measurable offline marketing, without creepy tracking.`
|
|
||||||
|
|
||||||
## Audience Focus
|
|
||||||
|
|
||||||
Primary audience for Days 1-15:
|
|
||||||
`Restaurants / hospitality`
|
|
||||||
|
|
||||||
Secondary audience for Days 16-30:
|
|
||||||
`Agencies / offline marketers / retail operators`
|
|
||||||
|
|
||||||
## CTA Rule for the Whole Month
|
|
||||||
|
|
||||||
- Most posts: `Reply with a keyword`, `follow for more`, or `DM me`
|
|
||||||
- Only light link usage
|
|
||||||
- Put direct product CTA mainly in replies, profile, and pinned post
|
|
||||||
|
|
||||||
## 30-Day Plan
|
|
||||||
|
|
||||||
### Day 1
|
|
||||||
|
|
||||||
**Post type:** Founder positioning post
|
|
||||||
**Hook:** `Most QR codes are dead the moment they get printed.`
|
|
||||||
**Angle:** Static QR codes create reprint costs and broken customer journeys.
|
|
||||||
**CTA:** `If you run offline marketing, follow this account. I'm breaking down how to fix it.`
|
|
||||||
|
|
||||||
### Day 2
|
|
||||||
|
|
||||||
**Post type:** Short insight post
|
|
||||||
**Hook:** `A restaurant menu should not require a reprint every time one dish changes.`
|
|
||||||
**Angle:** Dynamic QR codes for menus and specials.
|
|
||||||
**CTA:** `Reply "menu" if you want me to post the exact setup.`
|
|
||||||
|
|
||||||
### Day 3
|
|
||||||
|
|
||||||
**Post type:** Teardown
|
|
||||||
**Hook:** `3 mistakes I see on restaurant QR menus all the time:`
|
|
||||||
**Angle:** Bad placement, no fallback page, no analytics.
|
|
||||||
**CTA:** `Want me to roast your menu QR? Reply with a screenshot.`
|
|
||||||
|
|
||||||
### Day 4
|
|
||||||
|
|
||||||
**Post type:** Thread
|
|
||||||
**Hook:** `How restaurants can update menus without reprinting tables, flyers, or window signs:`
|
|
||||||
**Angle:** 5-step workflow using one dynamic QR.
|
|
||||||
**CTA:** `I can turn this into a checklist if people want it.`
|
|
||||||
|
|
||||||
### Day 5
|
|
||||||
|
|
||||||
**Post type:** Contrarian post
|
|
||||||
**Hook:** `Unpopular opinion: "free QR code generators" are expensive.`
|
|
||||||
**Angle:** Hidden cost is reprints, lost scans, no attribution.
|
|
||||||
**CTA:** `Agree or disagree?`
|
|
||||||
|
|
||||||
### Day 6
|
|
||||||
|
|
||||||
**Post type:** Demo video
|
|
||||||
**Hook:** `Change the destination after print. That's the whole game.`
|
|
||||||
**Angle:** Quick screen recording showing edit-after-print.
|
|
||||||
**CTA:** `DM me "edit" and I'll send the workflow.`
|
|
||||||
|
|
||||||
### Day 7
|
|
||||||
|
|
||||||
**Post type:** Founder story
|
|
||||||
**Hook:** `We started building QR Master because most QR tools felt like toys.`
|
|
||||||
**Angle:** Needed analytics, bulk creation, privacy-first tracking.
|
|
||||||
**CTA:** `What's one thing you hate about current QR tools?`
|
|
||||||
|
|
||||||
### Day 8
|
|
||||||
|
|
||||||
**Post type:** Pain-to-fix post
|
|
||||||
**Hook:** `If your flyer has a QR code but no tracking, you're guessing.`
|
|
||||||
**Angle:** Offline campaigns need measurable scans.
|
|
||||||
**CTA:** `Reply "track" if you want a simple attribution template.`
|
|
||||||
|
|
||||||
### Day 9
|
|
||||||
|
|
||||||
**Post type:** Restaurant-specific post
|
|
||||||
**Hook:** `Today's special changes. Your printed QR shouldn't.`
|
|
||||||
**Angle:** Daily menu operations.
|
|
||||||
**CTA:** `Restaurant owners: how often do you update menus?`
|
|
||||||
|
|
||||||
### Day 10
|
|
||||||
|
|
||||||
**Post type:** Roast / audit
|
|
||||||
**Hook:** `This QR code placement is killing conversions.`
|
|
||||||
**Angle:** Explain why low-visibility placements fail.
|
|
||||||
**CTA:** `Send me your flyer/menu/poster and I'll break it down.`
|
|
||||||
|
|
||||||
### Day 11
|
|
||||||
|
|
||||||
**Post type:** Thread
|
|
||||||
**Hook:** `5 QR code mistakes that make restaurant marketing look cheap:`
|
|
||||||
**Angle:** Visual clutter, dead links, bad landing pages, no tracking, wrong CTA.
|
|
||||||
**CTA:** `I'll post 5 fixes tomorrow if this gets traction.`
|
|
||||||
|
|
||||||
### Day 12
|
|
||||||
|
|
||||||
**Post type:** Build in public
|
|
||||||
**Hook:** `One thing founders underestimate: people don't want "a QR code." They want a workflow.`
|
|
||||||
**Angle:** Product insight from building.
|
|
||||||
**CTA:** `What simple tool became critical in your business?`
|
|
||||||
|
|
||||||
### Day 13
|
|
||||||
|
|
||||||
**Post type:** Short proof post
|
|
||||||
**Hook:** `One QR code. Multiple seasonal campaigns. Zero reprints.`
|
|
||||||
**Angle:** Reuse same printed asset with changing destination.
|
|
||||||
**CTA:** `This is one of the biggest underrated offline growth hacks.`
|
|
||||||
|
|
||||||
### Day 14
|
|
||||||
|
|
||||||
**Post type:** Demo video
|
|
||||||
**Hook:** `From printed table card to measurable scan funnel in under 30 seconds:`
|
|
||||||
**Angle:** Show QR creation + analytics preview.
|
|
||||||
**CTA:** `If you want more product breakdowns, follow.`
|
|
||||||
|
|
||||||
### Day 15
|
|
||||||
|
|
||||||
**Post type:** Summary / recap
|
|
||||||
**Hook:** `The biggest restaurant QR lesson so far:`
|
|
||||||
**Angle:** Most businesses don't need more print, they need more flexibility.
|
|
||||||
**CTA:** `Next week I'm switching to agencies and offline marketers.`
|
|
||||||
|
|
||||||
### Day 16
|
|
||||||
|
|
||||||
**Post type:** Agency-focused post
|
|
||||||
**Hook:** `If your agency runs flyer or poster campaigns without QR attribution, you're underreporting impact.`
|
|
||||||
**Angle:** Agencies need scan data to prove ROI.
|
|
||||||
**CTA:** `Reply "agency" if you want my offline attribution framework.`
|
|
||||||
|
|
||||||
### Day 17
|
|
||||||
|
|
||||||
**Post type:** Contrarian post
|
|
||||||
**Hook:** `The problem is not the QR code. The problem is the dead destination behind it.`
|
|
||||||
**Angle:** Static link is the failure point.
|
|
||||||
**CTA:** `This is where most campaigns quietly lose money.`
|
|
||||||
|
|
||||||
### Day 18
|
|
||||||
|
|
||||||
**Post type:** Thread
|
|
||||||
**Hook:** `How to make offline campaigns actually measurable:`
|
|
||||||
**Angle:** QR + UTM + landing page + analytics naming structure.
|
|
||||||
**CTA:** `I can turn this into a swipe file.`
|
|
||||||
|
|
||||||
### Day 19
|
|
||||||
|
|
||||||
**Post type:** Audit post
|
|
||||||
**Hook:** `3 reasons most poster QR campaigns don't convert:`
|
|
||||||
**Angle:** Weak CTA, poor mobile page, no tracking structure.
|
|
||||||
**CTA:** `Want a poster teardown series?`
|
|
||||||
|
|
||||||
### Day 20
|
|
||||||
|
|
||||||
**Post type:** Demo video
|
|
||||||
**Hook:** `Bulk-create hundreds of QR codes from a spreadsheet.`
|
|
||||||
**Angle:** Show CSV/Excel workflow for agencies or retail.
|
|
||||||
**CTA:** `DM me "bulk" if that would save your team time.`
|
|
||||||
|
|
||||||
### Day 21
|
|
||||||
|
|
||||||
**Post type:** Founder hot take
|
|
||||||
**Hook:** `"Just put a QR code on it" is bad marketing advice.`
|
|
||||||
**Angle:** QR is distribution, not strategy.
|
|
||||||
**CTA:** `What matters more: placement, offer, or landing page?`
|
|
||||||
|
|
||||||
### Day 22
|
|
||||||
|
|
||||||
**Post type:** Mini case format
|
|
||||||
**Hook:** `Campaign idea: one printed asset, three different destinations over 30 days.`
|
|
||||||
**Angle:** Explain how one QR can support multiple campaign phases.
|
|
||||||
**CTA:** `This is why dynamic matters more than design.`
|
|
||||||
|
|
||||||
### Day 23
|
|
||||||
|
|
||||||
**Post type:** Thread
|
|
||||||
**Hook:** `How I'd structure QR tracking for an agency campaign with flyers, packaging, and in-store signage:`
|
|
||||||
**Angle:** Naming conventions, attribution logic, reporting.
|
|
||||||
**CTA:** `If useful, I'll post the naming template.`
|
|
||||||
|
|
||||||
### Day 24
|
|
||||||
|
|
||||||
**Post type:** Privacy wedge post
|
|
||||||
**Hook:** `You can measure scans without turning people into surveillance data.`
|
|
||||||
**Angle:** Privacy-first analytics as a business advantage.
|
|
||||||
**CTA:** `Too many teams think analytics has to mean creepy.`
|
|
||||||
|
|
||||||
### Day 25
|
|
||||||
|
|
||||||
**Post type:** Teardown
|
|
||||||
**Hook:** `This flyer has a QR code. But it still won't tell you what worked.`
|
|
||||||
**Angle:** Missing attribution structure.
|
|
||||||
**CTA:** `Reply with "audit" and I'll post a fixed version.`
|
|
||||||
|
|
||||||
### Day 26
|
|
||||||
|
|
||||||
**Post type:** Retail / packaging post
|
|
||||||
**Hook:** `Packaging QR codes get interesting when you can change the destination later.`
|
|
||||||
**Angle:** Product updates, campaigns, support pages, seasonal promos.
|
|
||||||
**CTA:** `Retail operators: are you using QR for support, promo, or repeat purchase?`
|
|
||||||
|
|
||||||
### Day 27
|
|
||||||
|
|
||||||
**Post type:** Build in public
|
|
||||||
**Hook:** `One thing we keep seeing: people buy QR tools for "generation" and stay for "management."`
|
|
||||||
**Angle:** Product-market insight.
|
|
||||||
**CTA:** `That distinction matters more than most founders think.`
|
|
||||||
|
|
||||||
### Day 28
|
|
||||||
|
|
||||||
**Post type:** Demo video
|
|
||||||
**Hook:** `Here's what "measurable offline workflow" actually looks like in practice:`
|
|
||||||
**Angle:** Create, edit, track, compare placements.
|
|
||||||
**CTA:** `If this kind of content is useful, I'll make it a weekly series.`
|
|
||||||
|
|
||||||
### Day 29
|
|
||||||
|
|
||||||
**Post type:** Hero thread
|
|
||||||
**Hook:** `Most offline marketing teams don't have a traffic problem. They have a measurement problem.`
|
|
||||||
**Angle:** Big thesis thread connecting restaurants, agencies, retail, and dynamic QR logic.
|
|
||||||
**CTA:** `If you work in offline marketing, this is the framework.`
|
|
||||||
|
|
||||||
### Day 30
|
|
||||||
|
|
||||||
**Post type:** Month-end recap + soft CTA
|
|
||||||
**Hook:** `30 days of talking to people about QR workflows taught me this:`
|
|
||||||
**Angle:** Summarize 5 strongest lessons from the month.
|
|
||||||
**CTA:** `If you want, next I'll publish the full playbook: hooks, setup, and attribution templates.`
|
|
||||||
|
|
||||||
## Weekly Cadence
|
|
||||||
|
|
||||||
- `Mon`: strong opinion or positioning
|
|
||||||
- `Tue`: practical educational post
|
|
||||||
- `Wed`: teardown or audit
|
|
||||||
- `Thu`: thread
|
|
||||||
- `Fri`: product proof or demo
|
|
||||||
- `Sat`: founder insight / build in public
|
|
||||||
- `Sun`: recap or lighter conversation post
|
|
||||||
|
|
||||||
## Content Mix
|
|
||||||
|
|
||||||
- `8 threads`
|
|
||||||
- `6 teardown/audit posts`
|
|
||||||
- `5 demo videos`
|
|
||||||
- `6 short contrarian/value posts`
|
|
||||||
- `5 founder/build-in-public posts`
|
|
||||||
|
|
||||||
## Reply Strategy
|
|
||||||
|
|
||||||
Every day, add:
|
|
||||||
|
|
||||||
- `10-15 replies` to founders, marketers, restaurant-tech, local business, retail ops, and agency accounts
|
|
||||||
- Focus on posts about: offline marketing, menus, customer journeys, attribution, retail campaigns, print, local growth
|
|
||||||
- Use replies to seed your core themes:
|
|
||||||
- reprint cost
|
|
||||||
- edit after print
|
|
||||||
- measurable offline
|
|
||||||
- privacy-first analytics
|
|
||||||
- bulk workflows
|
|
||||||
|
|
||||||
## Optional Next Step
|
|
||||||
|
|
||||||
If needed, this can be expanded into:
|
|
||||||
|
|
||||||
1. fully written tweets for all 30 days
|
|
||||||
2. 8 full threads written out
|
|
||||||
3. a Notion-style content calendar with posting times and CTAs
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"project_name": "QRMaster Global Visibility",
|
|
||||||
"domain": "qrmaster.net",
|
|
||||||
"market_language": "en",
|
|
||||||
"offer": "a free dynamic QR cost calculator and professional management guide",
|
|
||||||
"keywords": [
|
|
||||||
{
|
|
||||||
"query": "dynamic qr code generator",
|
|
||||||
"intent": "commercial"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"query": "create dynamic qr code free",
|
|
||||||
"intent": "informational"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"query": "qr code for restaurant menu",
|
|
||||||
"intent": "commercial"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"query": "qr code marketing analytics",
|
|
||||||
"intent": "informational"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"query": "bulk qr code generator from csv",
|
|
||||||
"intent": "commercial"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"backlink": {
|
|
||||||
"seed_queries": [
|
|
||||||
"restaurant marketing ideas intitle:resources",
|
|
||||||
"digital menu setup guide intitle:links",
|
|
||||||
"retail marketing tools inurl:blog",
|
|
||||||
"qr code best practices 2026",
|
|
||||||
"hospitality tech trends intitle:guide",
|
|
||||||
"small business marketing resources inurl:links"
|
|
||||||
],
|
|
||||||
"outreach": {
|
|
||||||
"allow_send_emails": false,
|
|
||||||
"from_name": "QRMaster Outreach Team",
|
|
||||||
"templates": {
|
|
||||||
"en": {
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
42
next-sitemap.config.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/** @type {import('next-sitemap').IConfig} */
|
||||||
|
module.exports = {
|
||||||
|
siteUrl: 'https://www.qrmaster.net',
|
||||||
|
generateRobotsTxt: true,
|
||||||
|
robotsTxtOptions: {
|
||||||
|
policies: [
|
||||||
|
{
|
||||||
|
userAgent: '*',
|
||||||
|
allow: '/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transform: async (config, path) => {
|
||||||
|
// Custom priority and changefreq based on path
|
||||||
|
let priority = 0.7;
|
||||||
|
let changefreq = 'weekly';
|
||||||
|
|
||||||
|
if (path === '/') {
|
||||||
|
priority = 0.9;
|
||||||
|
changefreq = 'daily';
|
||||||
|
} else if (path === '/blog') {
|
||||||
|
priority = 0.7;
|
||||||
|
changefreq = 'daily';
|
||||||
|
} else if (path === '/pricing') {
|
||||||
|
priority = 0.8;
|
||||||
|
changefreq = 'weekly';
|
||||||
|
} else if (path === '/faq') {
|
||||||
|
priority = 0.6;
|
||||||
|
changefreq = 'weekly';
|
||||||
|
} else if (path.startsWith('/blog/')) {
|
||||||
|
priority = 0.6;
|
||||||
|
changefreq = 'weekly';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
loc: path,
|
||||||
|
changefreq,
|
||||||
|
priority,
|
||||||
|
lastmod: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
129
next.config.mjs
@@ -1,104 +1,25 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
skipTrailingSlashRedirect: true,
|
skipTrailingSlashRedirect: true,
|
||||||
images: {
|
images: {
|
||||||
unoptimized: false,
|
unoptimized: false,
|
||||||
domains: ['www.qrmaster.net', 'qrmaster.net', 'images.qrmaster.net'],
|
domains: ['www.qrmaster.net', 'qrmaster.net', 'images.qrmaster.net'],
|
||||||
formats: ['image/webp', 'image/avif'],
|
formats: ['image/webp', 'image/avif'],
|
||||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
||||||
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
serverComponentsExternalPackages: ['@prisma/client', 'bcryptjs'],
|
serverComponentsExternalPackages: ['@prisma/client', 'bcryptjs'],
|
||||||
},
|
},
|
||||||
// Allow build to succeed even with prerender errors
|
// Allow build to succeed even with prerender errors
|
||||||
// Pages with useSearchParams() will be rendered dynamically at runtime
|
// Pages with useSearchParams() will be rendered dynamically at runtime
|
||||||
staticPageGenerationTimeout: 120,
|
staticPageGenerationTimeout: 120,
|
||||||
onDemandEntries: {
|
onDemandEntries: {
|
||||||
maxInactiveAge: 25 * 1000,
|
maxInactiveAge: 25 * 1000,
|
||||||
pagesBufferLength: 2,
|
pagesBufferLength: 2,
|
||||||
},
|
},
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
async redirects() {
|
};
|
||||||
return [
|
|
||||||
{
|
export default nextConfig;
|
||||||
source: '/create-qr',
|
|
||||||
destination: '/dynamic-qr-code-generator',
|
|
||||||
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',
|
|
||||||
destination: '/tools/barcode-generator',
|
|
||||||
permanent: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: '/bar-code-generator',
|
|
||||||
destination: '/tools/barcode-generator',
|
|
||||||
permanent: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: '/qr-code-for/breweries-tap-rooms',
|
|
||||||
destination: '/qr-code-for/breweries',
|
|
||||||
permanent: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: '/qr-code-for/wineries-vineyards',
|
|
||||||
destination: '/qr-code-for/wineries',
|
|
||||||
permanent: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: '/qr-code-for/catering-businesses',
|
|
||||||
destination: '/qr-code-for/catering',
|
|
||||||
permanent: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: '/qr-code-for/spas-wellness-centers',
|
|
||||||
destination: '/qr-code-for/spas',
|
|
||||||
permanent: true,
|
|
||||||
},
|
|
||||||
// Additional likely old slugs from previous site version
|
|
||||||
{ source: '/qr-code-for/bars-pubs', destination: '/qr-code-for/bars', permanent: true },
|
|
||||||
{ source: '/qr-code-for/nightclubs-venues', destination: '/qr-code-for/nightclubs', permanent: true },
|
|
||||||
{ source: '/qr-code-for/dental-practices', destination: '/qr-code-for/dentists', permanent: true },
|
|
||||||
{ source: '/qr-code-for/pet-groomers', destination: '/qr-code-for/pet-grooming', permanent: true },
|
|
||||||
{ source: '/qr-code-for/clothing-apparel-stores', destination: '/qr-code-for/clothing-stores', permanent: true },
|
|
||||||
{ source: '/qr-code-for/florists-flower-shops', destination: '/qr-code-for/florists', permanent: true },
|
|
||||||
{ source: '/qr-code-for/pet-stores-groomers', destination: '/qr-code-for/pet-stores', permanent: true },
|
|
||||||
{ source: '/qr-code-for/hardware-diy-stores', destination: '/qr-code-for/hardware-stores', permanent: true },
|
|
||||||
{ source: '/qr-code-for/universities-colleges', destination: '/qr-code-for/universities', permanent: true },
|
|
||||||
{ source: '/qr-code-for/museums-exhibitions', destination: '/qr-code-for/museums', permanent: true },
|
|
||||||
{ source: '/qr-code-for/public-libraries', destination: '/qr-code-for/libraries', permanent: true },
|
|
||||||
{ source: '/qr-code-for/theaters-performing-arts', destination: '/qr-code-for/theaters', permanent: true },
|
|
||||||
{ source: '/qr-code-for/cinemas-movie-theaters', destination: '/qr-code-for/cinemas', permanent: true },
|
|
||||||
{ source: '/qr-code-for/churches-places-of-worship', destination: '/qr-code-for/churches', permanent: true },
|
|
||||||
{ source: '/qr-code-for/stadiums-sports-venues', destination: '/qr-code-for/stadiums', permanent: true },
|
|
||||||
{ source: '/qr-code-for/airports-travel-hubs', destination: '/qr-code-for/airports', permanent: true },
|
|
||||||
{ source: '/qr-code-for/photography-studios', destination: '/qr-code-for/photographers', permanent: true },
|
|
||||||
{ source: '/qr-code-for/trade-shows-exhibitions', destination: '/qr-code-for/trade-shows', permanent: true },
|
|
||||||
{ source: '/qr-code-for/accounting-firms', destination: '/qr-code-for/accountants', permanent: true },
|
|
||||||
];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default nextConfig;
|
|
||||||
|
|||||||
22572
package-lock.json
generated
190
package.json
@@ -1,104 +1,86 @@
|
|||||||
{
|
{
|
||||||
"name": "qr-master",
|
"name": "qr-master",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Create custom QR codes in seconds",
|
"description": "Create custom QR codes in seconds",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3050",
|
"dev": "next dev -p 3050",
|
||||||
"build": "node scripts/build.js",
|
"build": "prisma generate && next build",
|
||||||
"trigger:indexing": "tsx scripts/trigger-indexing.ts",
|
"start": "next start",
|
||||||
"submit:indexnow": "tsx scripts/submit-indexnow.ts",
|
"lint": "next lint",
|
||||||
"start": "next start",
|
"db:generate": "prisma generate",
|
||||||
"lint": "next lint",
|
"db:migrate": "prisma migrate dev",
|
||||||
"indexnow": "tsx scripts/submit-indexnow.ts",
|
"db:deploy": "prisma migrate deploy",
|
||||||
"db:generate": "prisma generate",
|
"db:seed": "tsx prisma/seed.ts",
|
||||||
"db:migrate": "prisma migrate dev",
|
"db:studio": "prisma studio",
|
||||||
"db:deploy": "prisma migrate deploy",
|
"postinstall": "prisma generate",
|
||||||
"db:seed": "tsx prisma/seed.ts",
|
"docker:dev": "docker compose -f docker-compose.dev.yml up -d",
|
||||||
"db:studio": "prisma studio",
|
"docker:dev:stop": "docker compose -f docker-compose.dev.yml down",
|
||||||
"postinstall": "prisma generate",
|
"docker:dev:clean": "docker compose -f docker-compose.dev.yml down --remove-orphans && docker container prune -f",
|
||||||
"docker:dev": "docker compose -f docker-compose.dev.yml up -d",
|
"docker:prod": "docker compose up -d --build",
|
||||||
"docker:dev:stop": "docker compose -f docker-compose.dev.yml down",
|
"docker:stop": "docker compose down",
|
||||||
"docker:dev:clean": "docker compose -f docker-compose.dev.yml down --remove-orphans && docker container prune -f",
|
"docker:logs": "docker compose logs -f",
|
||||||
"docker:prod": "docker compose up -d --build",
|
"docker:db": "docker compose exec db psql -U postgres -d qrmaster",
|
||||||
"docker:stop": "docker compose down",
|
"docker:redis": "docker compose exec redis redis-cli",
|
||||||
"docker:logs": "docker compose logs -f",
|
"docker:backup": "docker compose exec db pg_dump -U postgres qrmaster > backup_$(date +%Y%m%d).sql"
|
||||||
"docker:db": "docker compose exec db psql -U postgres -d qrmaster",
|
},
|
||||||
"docker:redis": "docker compose exec redis redis-cli",
|
"dependencies": {
|
||||||
"docker:backup": "docker compose exec db pg_dump -U postgres qrmaster > backup_$(date +%Y%m%d).sql"
|
"@auth/prisma-adapter": "^2.11.1",
|
||||||
},
|
"@edge-runtime/cookies": "^6.0.0",
|
||||||
"dependencies": {
|
"@prisma/client": "^5.7.0",
|
||||||
"@auth/prisma-adapter": "^2.11.1",
|
"@stripe/stripe-js": "^8.0.0",
|
||||||
"@aws-sdk/client-s3": "^3.972.0",
|
"@types/d3-scale": "^4.0.9",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.972.0",
|
"bcryptjs": "^2.4.3",
|
||||||
"@edge-runtime/cookies": "^6.0.0",
|
"chart.js": "^4.4.0",
|
||||||
"@prisma/client": "^5.7.0",
|
"clsx": "^2.0.0",
|
||||||
"@stripe/stripe-js": "^8.0.0",
|
"d3-scale": "^4.0.2",
|
||||||
"@types/d3-scale": "^4.0.9",
|
"dayjs": "^1.11.10",
|
||||||
"@types/nodemailer": "^7.0.11",
|
"exceljs": "^4.4.0",
|
||||||
"axios": "^1.13.2",
|
"file-saver": "^2.0.5",
|
||||||
"bcryptjs": "^2.4.3",
|
"i18next": "^23.7.6",
|
||||||
"chart.js": "^4.4.0",
|
"ioredis": "^5.3.2",
|
||||||
"clsx": "^2.0.0",
|
"jszip": "^3.10.1",
|
||||||
"copy-image-clipboard": "^2.1.2",
|
"lucide-react": "^0.562.0",
|
||||||
"d3-scale": "^4.0.2",
|
"next": "^14.2.35",
|
||||||
"dayjs": "^1.11.10",
|
"next-auth": "^4.24.5",
|
||||||
"dotenv": "^17.2.3",
|
"papaparse": "^5.4.1",
|
||||||
"exceljs": "^4.4.0",
|
"posthog-js": "^1.276.0",
|
||||||
"file-saver": "^2.0.5",
|
"qr-code-styling": "^1.9.2",
|
||||||
"framer-motion": "^12.24.10",
|
"qrcode": "^1.5.3",
|
||||||
"googleapis": "^170.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
"html-to-image": "^1.11.13",
|
"react": "^18.2.0",
|
||||||
"i18next": "^23.7.6",
|
"react-chartjs-2": "^5.2.0",
|
||||||
"ioredis": "^5.3.2",
|
"react-dom": "^18.2.0",
|
||||||
"jspdf": "^4.0.0",
|
"react-dropzone": "^14.2.3",
|
||||||
"jszip": "^3.10.1",
|
"react-i18next": "^13.5.0",
|
||||||
"lucide-react": "^0.562.0",
|
"react-simple-maps": "^3.0.0",
|
||||||
"next": "^14.2.35",
|
"resend": "^6.4.2",
|
||||||
"next-auth": "^4.24.5",
|
"sharp": "^0.33.1",
|
||||||
"nodemailer": "^8.0.4",
|
"stripe": "^19.1.0",
|
||||||
"papaparse": "^5.4.1",
|
"tailwind-merge": "^2.2.0",
|
||||||
"posthog-js": "^1.332.0",
|
"uuid": "^13.0.0",
|
||||||
"qr-code-styling": "^1.9.2",
|
"zod": "^3.25.76"
|
||||||
"qrcode": "^1.5.3",
|
},
|
||||||
"qrcode.react": "^3.1.0",
|
"devDependencies": {
|
||||||
"react": "^18.2.0",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"react-barcode": "^1.6.1",
|
"@types/file-saver": "^2.0.7",
|
||||||
"react-chartjs-2": "^5.2.0",
|
"@types/node": "^20.10.5",
|
||||||
"react-dom": "^18.2.0",
|
"@types/papaparse": "^5.3.14",
|
||||||
"react-dropzone": "^14.2.3",
|
"@types/qrcode": "^1.5.5",
|
||||||
"react-facebook-pixel": "^1.0.4",
|
"@types/react": "^18.2.45",
|
||||||
"react-i18next": "^13.5.0",
|
"@types/react-dom": "^18.2.18",
|
||||||
"react-simple-maps": "^3.0.0",
|
"autoprefixer": "^10.4.16",
|
||||||
"resend": "^6.4.2",
|
"eslint": "^8.56.0",
|
||||||
"sanitize-html": "^2.17.1",
|
"eslint-config-next": "^16.1.1",
|
||||||
"stripe": "^19.1.0",
|
"next-sitemap": "^4.2.3",
|
||||||
"tailwind-merge": "^2.2.0",
|
"postcss": "^8.4.32",
|
||||||
"uuid": "^13.0.0",
|
"prettier": "^3.1.1",
|
||||||
"zod": "^3.25.76"
|
"prisma": "^5.7.0",
|
||||||
},
|
"tailwindcss": "^3.3.6",
|
||||||
"devDependencies": {
|
"tsx": "^4.7.0",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"typescript": "^5.3.3"
|
||||||
"@types/file-saver": "^2.0.7",
|
},
|
||||||
"@types/node": "^20.10.5",
|
"engines": {
|
||||||
"@types/papaparse": "^5.3.14",
|
"node": ">=18.0.0"
|
||||||
"@types/qrcode": "^1.5.5",
|
}
|
||||||
"@types/react": "^18.2.45",
|
}
|
||||||
"@types/react-dom": "^18.2.18",
|
|
||||||
"@types/sanitize-html": "^2.16.0",
|
|
||||||
"autoprefixer": "^10.4.16",
|
|
||||||
"cross-env": "^10.1.0",
|
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-config-next": "16.1.5",
|
|
||||||
"postcss": "^8.4.32",
|
|
||||||
"prettier": "^3.1.1",
|
|
||||||
"prisma": "^5.7.0",
|
|
||||||
"sharp": "^0.34.5",
|
|
||||||
"tailwindcss": "^3.3.6",
|
|
||||||
"tsx": "^4.7.0",
|
|
||||||
"typescript": "^5.3.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
-- CreateEnum
|
|
||||||
CREATE TYPE "QRType" AS ENUM ('STATIC', 'DYNAMIC');
|
|
||||||
|
|
||||||
-- CreateEnum
|
|
||||||
CREATE TYPE "ContentType" AS ENUM ('URL', 'WIFI', 'VCARD', 'PHONE', 'EMAIL', 'SMS', 'TEXT', 'WHATSAPP');
|
|
||||||
|
|
||||||
-- CreateEnum
|
|
||||||
CREATE TYPE "QRStatus" AS ENUM ('ACTIVE', 'PAUSED');
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "User" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"email" TEXT NOT NULL,
|
|
||||||
"name" TEXT,
|
|
||||||
"password" TEXT,
|
|
||||||
"image" TEXT,
|
|
||||||
"emailVerified" TIMESTAMP(3),
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Account" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"type" TEXT NOT NULL,
|
|
||||||
"provider" TEXT NOT NULL,
|
|
||||||
"providerAccountId" TEXT NOT NULL,
|
|
||||||
"refresh_token" TEXT,
|
|
||||||
"access_token" TEXT,
|
|
||||||
"expires_at" INTEGER,
|
|
||||||
"token_type" TEXT,
|
|
||||||
"scope" TEXT,
|
|
||||||
"id_token" TEXT,
|
|
||||||
"session_state" TEXT,
|
|
||||||
|
|
||||||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Session" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"sessionToken" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"expires" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "VerificationToken" (
|
|
||||||
"identifier" TEXT NOT NULL,
|
|
||||||
"token" TEXT NOT NULL,
|
|
||||||
"expires" TIMESTAMP(3) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "QRCode" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
"type" "QRType" NOT NULL DEFAULT 'DYNAMIC',
|
|
||||||
"contentType" "ContentType" NOT NULL DEFAULT 'URL',
|
|
||||||
"content" JSONB NOT NULL,
|
|
||||||
"tags" TEXT[],
|
|
||||||
"status" "QRStatus" NOT NULL DEFAULT 'ACTIVE',
|
|
||||||
"style" JSONB NOT NULL,
|
|
||||||
"slug" TEXT NOT NULL,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "QRCode_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "QRScan" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"qrId" TEXT NOT NULL,
|
|
||||||
"ts" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"ipHash" TEXT NOT NULL,
|
|
||||||
"userAgent" TEXT,
|
|
||||||
"device" TEXT,
|
|
||||||
"os" TEXT,
|
|
||||||
"country" TEXT,
|
|
||||||
"referrer" TEXT,
|
|
||||||
"utmSource" TEXT,
|
|
||||||
"utmMedium" TEXT,
|
|
||||||
"utmCampaign" TEXT,
|
|
||||||
"isUnique" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
|
|
||||||
CONSTRAINT "QRScan_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Integration" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"provider" TEXT NOT NULL,
|
|
||||||
"status" TEXT NOT NULL DEFAULT 'inactive',
|
|
||||||
"config" JSONB NOT NULL,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "Integration_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "QRCode_slug_key" ON "QRCode"("slug");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "QRCode_userId_createdAt_idx" ON "QRCode"("userId", "createdAt");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "QRScan_qrId_ts_idx" ON "QRScan"("qrId", "ts");
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "QRCode" ADD CONSTRAINT "QRCode_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "QRScan" ADD CONSTRAINT "QRScan_qrId_fkey" FOREIGN KEY ("qrId") REFERENCES "QRCode"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Integration" ADD CONSTRAINT "Integration_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- A unique constraint covering the columns `[stripeCustomerId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
|
||||||
- A unique constraint covering the columns `[stripeSubscriptionId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- CreateEnum
|
|
||||||
CREATE TYPE "Plan" AS ENUM ('FREE', 'PRO', 'BUSINESS');
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "plan" "Plan" NOT NULL DEFAULT 'FREE',
|
|
||||||
ADD COLUMN "stripeCurrentPeriodEnd" TIMESTAMP(3),
|
|
||||||
ADD COLUMN "stripeCustomerId" TEXT,
|
|
||||||
ADD COLUMN "stripePriceId" TEXT,
|
|
||||||
ADD COLUMN "stripeSubscriptionId" TEXT;
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "User_stripeCustomerId_key" ON "User"("stripeCustomerId");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "User_stripeSubscriptionId_key" ON "User"("stripeSubscriptionId");
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- The values [WIFI,EMAIL] on the enum `ContentType` will be removed. If these variants are still used in the database, this will fail.
|
|
||||||
- A unique constraint covering the columns `[resetPasswordToken]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterEnum
|
|
||||||
BEGIN;
|
|
||||||
CREATE TYPE "ContentType_new" AS ENUM ('URL', 'VCARD', 'GEO', 'PHONE', 'SMS', 'TEXT', 'WHATSAPP');
|
|
||||||
ALTER TABLE "QRCode" ALTER COLUMN "contentType" DROP DEFAULT;
|
|
||||||
ALTER TABLE "QRCode" ALTER COLUMN "contentType" TYPE "ContentType_new" USING ("contentType"::text::"ContentType_new");
|
|
||||||
ALTER TYPE "ContentType" RENAME TO "ContentType_old";
|
|
||||||
ALTER TYPE "ContentType_new" RENAME TO "ContentType";
|
|
||||||
DROP TYPE "ContentType_old";
|
|
||||||
ALTER TABLE "QRCode" ALTER COLUMN "contentType" SET DEFAULT 'URL';
|
|
||||||
COMMIT;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User" ADD COLUMN "resetPasswordExpires" TIMESTAMP(3),
|
|
||||||
ADD COLUMN "resetPasswordToken" TEXT;
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "NewsletterSubscription" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"email" TEXT NOT NULL,
|
|
||||||
"source" TEXT NOT NULL DEFAULT 'ai-coming-soon',
|
|
||||||
"status" TEXT NOT NULL DEFAULT 'subscribed',
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "NewsletterSubscription_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Lead" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"email" TEXT NOT NULL,
|
|
||||||
"source" TEXT NOT NULL DEFAULT 'reprint-calculator',
|
|
||||||
"reprintCost" DOUBLE PRECISION,
|
|
||||||
"updatesPerYear" INTEGER,
|
|
||||||
"annualSavings" DOUBLE PRECISION,
|
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
CONSTRAINT "Lead_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "NewsletterSubscription_email_key" ON "NewsletterSubscription"("email");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "NewsletterSubscription_email_idx" ON "NewsletterSubscription"("email");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "NewsletterSubscription_createdAt_idx" ON "NewsletterSubscription"("createdAt");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Lead_email_idx" ON "Lead"("email");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Lead_createdAt_idx" ON "Lead"("createdAt");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "Lead_source_idx" ON "Lead"("source");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "User_resetPasswordToken_key" ON "User"("resetPasswordToken");
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- Added the required column `updatedAt` to the `Lead` table without a default value. This is not possible if the table is not empty.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterEnum
|
|
||||||
-- This migration adds more than one value to an enum.
|
|
||||||
-- With PostgreSQL versions 11 and earlier, this is not possible
|
|
||||||
-- in a single migration. This can be worked around by creating
|
|
||||||
-- multiple migrations, each migration adding only one value to
|
|
||||||
-- the enum.
|
|
||||||
|
|
||||||
|
|
||||||
ALTER TYPE "ContentType" ADD VALUE 'PDF';
|
|
||||||
ALTER TYPE "ContentType" ADD VALUE 'APP';
|
|
||||||
ALTER TYPE "ContentType" ADD VALUE 'COUPON';
|
|
||||||
ALTER TYPE "ContentType" ADD VALUE 'FEEDBACK';
|
|
||||||
|
|
||||||
-- DropIndex
|
|
||||||
DROP INDEX "Lead_createdAt_idx";
|
|
||||||
|
|
||||||
-- DropIndex
|
|
||||||
DROP INDEX "Lead_email_idx";
|
|
||||||
|
|
||||||
-- DropIndex
|
|
||||||
DROP INDEX "Lead_source_idx";
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "Lead" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL,
|
|
||||||
ALTER COLUMN "updatesPerYear" SET DATA TYPE DOUBLE PRECISION;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User"
|
|
||||||
ADD COLUMN "activationNudgeSentAt" TIMESTAMP(3),
|
|
||||||
ADD COLUMN "upgradeNudgeSentAt" TIMESTAMP(3);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "User"
|
|
||||||
ADD COLUMN "thirtyDayNudgeSentAt" TIMESTAMP(3);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Please do not edit this file manually
|
|
||||||
# It should be added in your version-control system (i.e. Git)
|
|
||||||
provider = "postgresql"
|
|
||||||
@@ -1,184 +1,167 @@
|
|||||||
// 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"
|
||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
email String @unique
|
email String @unique
|
||||||
name String?
|
name String?
|
||||||
password String?
|
password String?
|
||||||
image String?
|
image String?
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
// Stripe subscription fields
|
// Stripe subscription fields
|
||||||
stripeCustomerId String? @unique
|
stripeCustomerId String? @unique
|
||||||
stripeSubscriptionId String? @unique
|
stripeSubscriptionId String? @unique
|
||||||
stripePriceId String?
|
stripePriceId String?
|
||||||
stripeCurrentPeriodEnd DateTime?
|
stripeCurrentPeriodEnd DateTime?
|
||||||
plan Plan @default(FREE)
|
plan Plan @default(FREE)
|
||||||
|
|
||||||
// Password reset fields
|
// Password reset fields
|
||||||
resetPasswordToken String? @unique
|
resetPasswordToken String? @unique
|
||||||
resetPasswordExpires DateTime?
|
resetPasswordExpires DateTime?
|
||||||
|
|
||||||
// Retention email tracking
|
// White-label subdomain
|
||||||
activationNudgeSentAt DateTime?
|
subdomain String? @unique
|
||||||
upgradeNudgeSentAt DateTime?
|
|
||||||
thirtyDayNudgeSentAt DateTime?
|
qrCodes QRCode[]
|
||||||
|
integrations Integration[]
|
||||||
qrCodes QRCode[]
|
accounts Account[]
|
||||||
integrations Integration[]
|
sessions Session[]
|
||||||
accounts Account[]
|
}
|
||||||
sessions Session[]
|
|
||||||
}
|
enum Plan {
|
||||||
|
FREE
|
||||||
enum Plan {
|
PRO
|
||||||
FREE
|
BUSINESS
|
||||||
PRO
|
}
|
||||||
BUSINESS
|
|
||||||
}
|
model Account {
|
||||||
|
id String @id @default(cuid())
|
||||||
model Account {
|
userId String
|
||||||
id String @id @default(cuid())
|
type String
|
||||||
userId String
|
provider String
|
||||||
type String
|
providerAccountId String
|
||||||
provider String
|
refresh_token String? @db.Text
|
||||||
providerAccountId String
|
access_token String? @db.Text
|
||||||
refresh_token String? @db.Text
|
expires_at Int?
|
||||||
access_token String? @db.Text
|
token_type String?
|
||||||
expires_at Int?
|
scope String?
|
||||||
token_type String?
|
id_token String? @db.Text
|
||||||
scope String?
|
session_state String?
|
||||||
id_token String? @db.Text
|
|
||||||
session_state String?
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
@@unique([provider, providerAccountId])
|
||||||
|
}
|
||||||
@@unique([provider, providerAccountId])
|
|
||||||
}
|
model Session {
|
||||||
|
id String @id @default(cuid())
|
||||||
model Session {
|
sessionToken String @unique
|
||||||
id String @id @default(cuid())
|
userId String
|
||||||
sessionToken String @unique
|
expires DateTime
|
||||||
userId String
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
expires DateTime
|
}
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
}
|
model VerificationToken {
|
||||||
|
identifier String
|
||||||
model VerificationToken {
|
token String @unique
|
||||||
identifier String
|
expires DateTime
|
||||||
token String @unique
|
|
||||||
expires DateTime
|
@@unique([identifier, token])
|
||||||
|
}
|
||||||
@@unique([identifier, token])
|
|
||||||
}
|
model QRCode {
|
||||||
|
id String @id @default(cuid())
|
||||||
model QRCode {
|
userId String
|
||||||
id String @id @default(cuid())
|
title String
|
||||||
userId String
|
type QRType @default(DYNAMIC)
|
||||||
title String
|
contentType ContentType @default(URL)
|
||||||
type QRType @default(DYNAMIC)
|
content Json
|
||||||
contentType ContentType @default(URL)
|
tags String[]
|
||||||
content Json
|
status QRStatus @default(ACTIVE)
|
||||||
tags String[]
|
style Json
|
||||||
status QRStatus @default(ACTIVE)
|
slug String @unique
|
||||||
style Json
|
createdAt DateTime @default(now())
|
||||||
slug String @unique
|
updatedAt DateTime @updatedAt
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
scans QRScan[]
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
scans QRScan[]
|
@@index([userId, createdAt])
|
||||||
|
}
|
||||||
@@index([userId, createdAt])
|
|
||||||
}
|
enum QRType {
|
||||||
|
STATIC
|
||||||
enum QRType {
|
DYNAMIC
|
||||||
STATIC
|
}
|
||||||
DYNAMIC
|
|
||||||
}
|
enum ContentType {
|
||||||
|
URL
|
||||||
enum ContentType {
|
VCARD
|
||||||
URL
|
GEO
|
||||||
VCARD
|
PHONE
|
||||||
GEO
|
SMS
|
||||||
PHONE
|
TEXT
|
||||||
SMS
|
WHATSAPP
|
||||||
TEXT
|
}
|
||||||
WHATSAPP
|
|
||||||
PDF
|
enum QRStatus {
|
||||||
APP
|
ACTIVE
|
||||||
COUPON
|
PAUSED
|
||||||
FEEDBACK
|
}
|
||||||
}
|
|
||||||
|
model QRScan {
|
||||||
enum QRStatus {
|
id String @id @default(cuid())
|
||||||
ACTIVE
|
qrId String
|
||||||
PAUSED
|
ts DateTime @default(now())
|
||||||
}
|
ipHash String
|
||||||
|
userAgent String?
|
||||||
model QRScan {
|
device String?
|
||||||
id String @id @default(cuid())
|
os String?
|
||||||
qrId String
|
country String?
|
||||||
ts DateTime @default(now())
|
referrer String?
|
||||||
ipHash String
|
utmSource String?
|
||||||
userAgent String?
|
utmMedium String?
|
||||||
device String?
|
utmCampaign String?
|
||||||
os String?
|
isUnique Boolean @default(false)
|
||||||
country String?
|
|
||||||
referrer String?
|
qr QRCode @relation(fields: [qrId], references: [id], onDelete: Cascade)
|
||||||
utmSource String?
|
|
||||||
utmMedium String?
|
@@index([qrId, ts])
|
||||||
utmCampaign String?
|
}
|
||||||
isUnique Boolean @default(false)
|
|
||||||
|
model Integration {
|
||||||
qr QRCode @relation(fields: [qrId], references: [id], onDelete: Cascade)
|
id String @id @default(cuid())
|
||||||
|
userId String
|
||||||
@@index([qrId, ts])
|
provider String
|
||||||
}
|
status String @default("inactive")
|
||||||
|
config Json
|
||||||
model Integration {
|
createdAt DateTime @default(now())
|
||||||
id String @id @default(cuid())
|
updatedAt DateTime @updatedAt
|
||||||
userId String
|
|
||||||
provider String
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
status String @default("inactive")
|
}
|
||||||
config Json
|
|
||||||
createdAt DateTime @default(now())
|
model NewsletterSubscription {
|
||||||
updatedAt DateTime @updatedAt
|
id String @id @default(cuid())
|
||||||
|
email String @unique
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
source String @default("ai-coming-soon")
|
||||||
}
|
status String @default("subscribed")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
model NewsletterSubscription {
|
updatedAt DateTime @updatedAt
|
||||||
id String @id @default(cuid())
|
|
||||||
email String @unique
|
@@index([email])
|
||||||
source String @default("ai-coming-soon")
|
@@index([createdAt])
|
||||||
status String @default("subscribed")
|
}
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@index([email])
|
|
||||||
@@index([createdAt])
|
|
||||||
}
|
|
||||||
|
|
||||||
model Lead {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
email String
|
|
||||||
source String @default("reprint-calculator")
|
|
||||||
reprintCost Float?
|
|
||||||
updatesPerYear Float?
|
|
||||||
annualSavings Float?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
}
|
|
||||||
232
prisma/seed.ts
@@ -1,117 +1,117 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import * as bcrypt from 'bcryptjs';
|
import * as bcrypt from 'bcryptjs';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// Create admin user for newsletter management
|
// Create admin user for newsletter management
|
||||||
const hashedPassword = await bcrypt.hash('Timo.16092005', 12);
|
const hashedPassword = await bcrypt.hash('Timo.16092005', 12);
|
||||||
|
|
||||||
const user = await prisma.user.upsert({
|
const user = await prisma.user.upsert({
|
||||||
where: { email: 'demo@qrmaster.net' },
|
where: { email: 'demo@qrmaster.net' },
|
||||||
update: {
|
update: {
|
||||||
password: hashedPassword, // Update password if user exists
|
password: hashedPassword, // Update password if user exists
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
email: 'demo@qrmaster.net',
|
email: 'demo@qrmaster.net',
|
||||||
name: 'Admin User',
|
name: 'Admin User',
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Created/Updated admin user:', user.email);
|
console.log('Created/Updated admin user:', user.email);
|
||||||
|
|
||||||
// Create demo QR codes
|
// Create demo QR codes
|
||||||
const qrCodes = [
|
const qrCodes = [
|
||||||
{
|
{
|
||||||
title: 'Support Phone',
|
title: 'Support Phone',
|
||||||
contentType: 'PHONE' as const,
|
contentType: 'PHONE' as const,
|
||||||
content: { phone: '+1-555-0123' },
|
content: { phone: '+1-555-0123' },
|
||||||
tags: ['support', 'contact'],
|
tags: ['support', 'contact'],
|
||||||
slug: 'support-phone-demo',
|
slug: 'support-phone-demo',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Event Details',
|
title: 'Event Details',
|
||||||
contentType: 'URL' as const,
|
contentType: 'URL' as const,
|
||||||
content: { url: 'https://example.com/event-2025' },
|
content: { url: 'https://example.com/event-2025' },
|
||||||
tags: ['event', 'conference'],
|
tags: ['event', 'conference'],
|
||||||
slug: 'event-details-demo',
|
slug: 'event-details-demo',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Product Demo',
|
title: 'Product Demo',
|
||||||
contentType: 'URL' as const,
|
contentType: 'URL' as const,
|
||||||
content: { url: 'https://example.com/product-demo' },
|
content: { url: 'https://example.com/product-demo' },
|
||||||
tags: ['product', 'demo'],
|
tags: ['product', 'demo'],
|
||||||
slug: 'product-demo-qr',
|
slug: 'product-demo-qr',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Company Website',
|
title: 'Company Website',
|
||||||
contentType: 'URL' as const,
|
contentType: 'URL' as const,
|
||||||
content: { url: 'https://company.example.com' },
|
content: { url: 'https://company.example.com' },
|
||||||
tags: ['website', 'company'],
|
tags: ['website', 'company'],
|
||||||
slug: 'company-website-qr',
|
slug: 'company-website-qr',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Contact Card',
|
title: 'Contact Card',
|
||||||
contentType: 'VCARD' as const,
|
contentType: 'VCARD' as const,
|
||||||
content: {
|
content: {
|
||||||
firstName: 'John',
|
firstName: 'John',
|
||||||
lastName: 'Doe',
|
lastName: 'Doe',
|
||||||
email: 'john@company.com',
|
email: 'john@company.com',
|
||||||
phone: '+1234567890',
|
phone: '+1234567890',
|
||||||
organization: 'Example Corp',
|
organization: 'Example Corp',
|
||||||
title: 'CEO'
|
title: 'CEO'
|
||||||
},
|
},
|
||||||
tags: ['contact', 'vcard'],
|
tags: ['contact', 'vcard'],
|
||||||
slug: 'contact-card-qr',
|
slug: 'contact-card-qr',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Event Details',
|
title: 'Event Details',
|
||||||
contentType: 'URL' as const,
|
contentType: 'URL' as const,
|
||||||
content: { url: 'https://example.com/event-duplicate' },
|
content: { url: 'https://example.com/event-duplicate' },
|
||||||
tags: ['event', 'duplicate'],
|
tags: ['event', 'duplicate'],
|
||||||
slug: 'event-details-dup',
|
slug: 'event-details-dup',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const baseDate = new Date('2025-08-07T10:00:00Z');
|
const baseDate = new Date('2025-08-07T10:00:00Z');
|
||||||
|
|
||||||
for (let i = 0; i < qrCodes.length; i++) {
|
for (let i = 0; i < qrCodes.length; i++) {
|
||||||
const qrData = qrCodes[i];
|
const qrData = qrCodes[i];
|
||||||
const createdAt = new Date(baseDate.getTime() + i * 60000); // 1 minute apart
|
const createdAt = new Date(baseDate.getTime() + i * 60000); // 1 minute apart
|
||||||
|
|
||||||
await prisma.qRCode.upsert({
|
await prisma.qRCode.upsert({
|
||||||
where: { slug: qrData.slug },
|
where: { slug: qrData.slug },
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
create: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
title: qrData.title,
|
title: qrData.title,
|
||||||
type: 'DYNAMIC',
|
type: 'DYNAMIC',
|
||||||
contentType: qrData.contentType,
|
contentType: qrData.contentType,
|
||||||
content: qrData.content,
|
content: qrData.content,
|
||||||
tags: qrData.tags,
|
tags: qrData.tags,
|
||||||
status: 'ACTIVE',
|
status: 'ACTIVE',
|
||||||
style: {
|
style: {
|
||||||
foregroundColor: '#000000',
|
foregroundColor: '#000000',
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
cornerStyle: 'square',
|
cornerStyle: 'square',
|
||||||
size: 200,
|
size: 200,
|
||||||
},
|
},
|
||||||
slug: qrData.slug,
|
slug: qrData.slug,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt: createdAt,
|
updatedAt: createdAt,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Created 6 demo QR codes');
|
console.log('Created 6 demo QR codes');
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
})
|
})
|
||||||
.finally(async () => {
|
.finally(async () => {
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
});
|
});
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
Contact: mailto:security@qrmaster.net
|
|
||||||
Expires: 2027-01-01T00:00:00.000Z
|
|
||||||
Strategies: https://www.qrmaster.net/.well-known/security.txt
|
|
||||||
Preferred-Languages: en, de
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
google.com, pub-2782770414424875, DIRECT, f08c47fec0942fa0
|
|
||||||
|
Before Width: | Height: | Size: 398 KiB |
@@ -1 +0,0 @@
|
|||||||
bb6dfaacf1ed41a880281c426c54ed7c
|
|
||||||
BIN
public/blog/1-boy.png
Normal file
|
After Width: | Height: | Size: 4.2 MiB |
BIN
public/blog/1-hero.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 86 KiB |
BIN
public/blog/2-body.png
Normal file
|
After Width: | Height: | Size: 4.1 MiB |
|
Before Width: | Height: | Size: 139 KiB |
BIN
public/blog/2-hero.png
Normal file
|
After Width: | Height: | Size: 3.8 MiB |
|
Before Width: | Height: | Size: 126 KiB |
BIN
public/blog/3-body.png
Normal file
|
After Width: | Height: | Size: 5.5 MiB |
BIN
public/blog/3-hero.png
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
BIN
public/blog/4-body.png
Normal file
|
After Width: | Height: | Size: 5.8 MiB |
BIN
public/blog/4-hero.png
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
|
Before Width: | Height: | Size: 749 KiB |