Compare commits
6 Commits
d623f39c54
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 5541c0553c | |||
| 172730cf0f | |||
| 9ccce7cbfd | |||
| 45f6c4d83b | |||
| a9057b25dd | |||
| 769d06e04e |
20
CLAUDE.md
20
CLAUDE.md
@@ -289,7 +289,25 @@ Pattern observed: QR Master gets cited by AI models only where first-party compa
|
||||
|
||||
Tested outside the original 9-theme set. QR Master is cited as the "Best overall option" for this query — credited for trackable/editable dynamic barcodes, bulk generation, unified analytics, and UTM tracking, plus static EAN-13/UPC-A/Code 128 support when editability isn't needed. The AI answer also included a "requirement → recommended format" decision table (dynamic QR vs. EAN-13/UPC-A vs. Code 128 vs. GS1 Digital Link) that maps closely to existing on-site content.
|
||||
|
||||
This maps directly to the live `/dynamic-barcode-generator` page and `/tools/barcode-generator` tool, reinforcing the pattern above: dedicated first-party pages targeting a query cluster get cited, gaps without dedicated pages don't. Counts as a 4th confirmed positive theme alongside the original 3 from the baseline audit.
|
||||
This maps to the `/tools/barcode-generator` tool, reinforcing the pattern above: dedicated first-party pages targeting a query cluster get cited, gaps without dedicated pages don't. Counts as a 4th confirmed positive theme alongside the original 3 from the baseline audit.
|
||||
|
||||
Note: `/dynamic-barcode-generator` and `/barcode-generator` are **not** live pages — both 301 to `/tools/barcode-generator` (see `redirects()` in `next.config.mjs`). Cite the canonical `/tools/barcode-generator` URL in any AEO work.
|
||||
|
||||
### Sitemap / IndexNow drift (fixed 2026-08-13)
|
||||
|
||||
`src/app/sitemap.ts` and `getAllIndexableUrls()` in `src/lib/indexnow.ts` are two hand-maintained URL lists that had silently diverged: the whole `/alternatives/*` + `/vs/*` cluster (1,942 GSC impressions in the 3 months to 2026-08-13, avg pos ~29-47) was submitted to IndexNow and linked from the footer but missing from the sitemap, while `/dynamic-barcode-generator` was listed in both despite 301-ing.
|
||||
|
||||
**When adding or redirecting a marketing page, update both lists.** A redirected URL must appear in neither.
|
||||
|
||||
### Google Review cluster — biggest non-brand opportunity (2026-08-13)
|
||||
|
||||
`/tools/google-review-qr-code` is the **#2 page on the site by impressions** (2,201 in 3 months) and converts almost none of it: 2 clicks, avg position 33. The query cluster is ~1,667 impressions across 50+ queries, all at position 20-50, zero clicks. Head term `google review qr code generator` = 268 impr at pos 35.
|
||||
|
||||
**On-page is not the constraint — do not "improve the content".** Audited 2026-08-13: it is already the deepest tool page on the site (1,352 rendered words, 8 h2 / 18 h3 vs 549 for crypto, 741 for wifi), with SoftwareApplication + HowTo + FAQPage schema, a correctly matched title, and canonical set. It also does not meaningfully cannibalize `/use-cases/qr-codes-for-review-collection` (36 impr, pos 11), which targets the tracking angle.
|
||||
|
||||
What was done: added `google-review-qr-code` to `toolsMap` in `qr-code-for/[industry]/page.tsx` and to the `tools[]` array of the 32 local-business industries in `src/lib/industry-pages.ts`, creating contextual internal links from crawled, well-ranking pages (barbershops pos 9.7, hotels, cafes, bars). The 20 institutional industries (airports, schools, stadiums, libraries, churches, universities, museums, theaters, cinemas, art-galleries, events, trade-shows, retail, and the non-local stores) were deliberately excluded — review collection is not their job, and blanket-linking all 52 would be boilerplate.
|
||||
|
||||
Remaining gap is off-site authority, not anything in this repo. Note the fallback in that template silently rewrites an unknown tool slug to `/tools/url-qr-code`, so any new slug must be added to `toolsMap` or the link disappears without erroring.
|
||||
|
||||
## Deployment Notes
|
||||
|
||||
|
||||
314
DEPLOY_TESTUMGEBUNG_ANLEITUNG.md
Normal file
314
DEPLOY_TESTUMGEBUNG_ANLEITUNG.md
Normal file
@@ -0,0 +1,314 @@
|
||||
# Anleitung: Testumgebung testmodul.qrmaster.net aufsetzen
|
||||
|
||||
Diese Anleitung richtet auf dem Produktionsserver eine **zweite, getrennte Instanz** von
|
||||
QR Master ein, erreichbar unter `testmodul.qrmaster.net`. Sie läuft auf dem Branch `test`
|
||||
mit einer eigenen, leeren Datenbank.
|
||||
|
||||
Die laufende Produktion wird dabei **nicht angefasst**. Alle Schritte hier legen neue
|
||||
Container, ein neues Volume und ein neues Verzeichnis an.
|
||||
|
||||
## Kurzfassung zum Abhaken
|
||||
|
||||
Wer die Begründungen nicht braucht, arbeitet diese Liste ab. Die ausführlichen Abschnitte
|
||||
darunter erklären jeden Schritt und was schiefgehen kann.
|
||||
|
||||
- [ ] **1.** Repo klonen, Branch `test`, **eigenes Verzeichnis** neben der Produktion
|
||||
- [ ] **2.** `.env.test` von Timo dort ablegen und die vier Kernwerte prüfen
|
||||
- [ ] **3.** Stack bauen und starten (`-p qrmaster-test`)
|
||||
- [ ] **4.** Schema aus Prod dumpen und einspielen - **kein** `prisma migrate`
|
||||
- [ ] **5.** Testaccount per SQL anlegen (Registrierungsformular funktioniert nicht)
|
||||
- [ ] **6.** Caddy-Block ergänzen und neu laden
|
||||
- [ ] **7.** Abnahme: DB-Trennung, keine Migrationen, robots.txt, Browser-Test
|
||||
|
||||
### Alle Befehle am Stück
|
||||
|
||||
```bash
|
||||
# 1 - Checkout (NICHT im Produktionsverzeichnis)
|
||||
git clone -b test https://git.bizmatch.net/tknuth/QR-master.git qrmaster-test
|
||||
cd qrmaster-test
|
||||
git branch --show-current # muss "test" zeigen
|
||||
|
||||
# 2 - .env.test hier ablegen, dann pruefen
|
||||
grep -E "NEXT_PUBLIC_WWW_URL|NEXT_PUBLIC_APP_URL|AUTH_COOKIE_NAME|POSTGRES_DB" .env.test
|
||||
# Erwartet: beide URLs auf testmodul, AUTH_COOKIE_NAME=userId_test,
|
||||
# POSTGRES_DB=qrmaster_test. Steht dort app./www. -> STOPP, siehe Schritt 2.
|
||||
|
||||
# 3 - Stack starten
|
||||
docker compose -p qrmaster-test --env-file .env.test \
|
||||
-f docker-compose.yml -f docker-compose.test.yml up -d --build
|
||||
docker ps --filter "name=qrmaster-test" --format "table {{.Names}}\t{{.Status}}"
|
||||
|
||||
# 4 - Schema (nur Struktur, keine Kundendaten)
|
||||
docker exec qrmaster-db pg_dump -U postgres --schema-only qrmaster > schema.sql
|
||||
docker exec -i qrmaster-test-db psql -U postgres -d qrmaster_test < schema.sql
|
||||
docker exec qrmaster-test-db psql -U postgres -d qrmaster_test -c "\dt" | head -20
|
||||
|
||||
# 5 - Testaccount: erst Hash erzeugen, dann in den INSERT einsetzen
|
||||
docker exec qrmaster-test-web node -e "console.log(require('bcryptjs').hashSync('DEIN_TESTPASSWORT',12))"
|
||||
docker exec -i qrmaster-test-db psql -U postgres -d qrmaster_test -c "INSERT INTO \"User\" (id,email,name,password,\"emailVerified\",\"updatedAt\") VALUES ('testuser1','test@qrmaster.net','Test','HIER_DER_HASH',now(),now());"
|
||||
|
||||
# 6 - Caddy: Block ergaenzen (siehe Schritt 6), dann
|
||||
caddy reload --config /etc/caddy/Caddyfile
|
||||
|
||||
# 7 - Abnahme
|
||||
docker exec qrmaster-test-db psql -U postgres -d qrmaster_test -c 'SELECT count(*) FROM "User";'
|
||||
docker exec qrmaster-db psql -U postgres -d qrmaster -c 'SELECT count(*) FROM "User";'
|
||||
docker logs qrmaster-test-web 2>&1 | head -20
|
||||
curl -s https://testmodul.qrmaster.net/robots.txt
|
||||
```
|
||||
|
||||
Die beiden `count(*)` müssen sich unterscheiden, in den Logs darf kein "Applying Prisma
|
||||
migrations" stehen, und `robots.txt` muss `Disallow: /` liefern.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- SSH-Zugang zum Server, auf dem QR Master läuft
|
||||
- Docker und Docker Compose (mindestens v2.24 - wird für `!override` und `!reset` gebraucht;
|
||||
prüfen mit `docker compose version`)
|
||||
- Schreibrechte auf die Caddy-Konfiguration
|
||||
- Die Datei **`.env.test`** - die kommt von Timo und ist nicht im Repository, weil sie
|
||||
Passwörter enthält
|
||||
- Der DNS-Eintrag `testmodul.qrmaster.net` existiert bereits (CNAME)
|
||||
|
||||
## Was NICHT angefasst wird
|
||||
|
||||
- Das bestehende Produktionsverzeichnis: dort **nicht** den Branch wechseln. Ein späterer
|
||||
Prod-Rebuild würde sonst Testcode bauen.
|
||||
- Die Produktions-`.env`
|
||||
- Die bestehenden Caddy-Blöcke für `www.qrmaster.net`, `app.qrmaster.net` und `qrmaster.net`
|
||||
- Die Produktionsdatenbank. Der einzige Zugriff darauf ist ein `pg_dump --schema-only`,
|
||||
das ausschließlich liest.
|
||||
|
||||
---
|
||||
|
||||
## 1. Zweites Checkout anlegen
|
||||
|
||||
**Nicht** im Produktionsverzeichnis arbeiten. Ein eigenes Verzeichnis daneben, z.B. im
|
||||
selben übergeordneten Ordner:
|
||||
|
||||
```bash
|
||||
git clone -b test https://git.bizmatch.net/tknuth/QR-master.git qrmaster-test
|
||||
```
|
||||
|
||||
Danach in dieses Verzeichnis wechseln. **Alle weiteren Befehle laufen von dort**, sofern
|
||||
nicht anders angegeben.
|
||||
|
||||
```bash
|
||||
cd qrmaster-test
|
||||
```
|
||||
|
||||
Prüfen, dass der richtige Branch ausgecheckt ist - es muss `test` erscheinen:
|
||||
|
||||
```bash
|
||||
git branch --show-current
|
||||
```
|
||||
|
||||
## 2. `.env.test` ablegen
|
||||
|
||||
Die von Timo erhaltene Datei als `.env.test` in dieses Verzeichnis legen (also
|
||||
`qrmaster-test/.env.test`).
|
||||
|
||||
Kurz gegenprüfen, dass die vier wichtigsten Werte stimmen:
|
||||
|
||||
```bash
|
||||
grep -E "NEXT_PUBLIC_WWW_URL|NEXT_PUBLIC_APP_URL|AUTH_COOKIE_NAME|POSTGRES_DB" .env.test
|
||||
```
|
||||
|
||||
Erwartet:
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_WWW_URL=https://testmodul.qrmaster.net
|
||||
NEXT_PUBLIC_APP_URL=https://testmodul.qrmaster.net
|
||||
AUTH_COOKIE_NAME=userId_test
|
||||
POSTGRES_DB=qrmaster_test
|
||||
```
|
||||
|
||||
Steht bei einer der URLs `app.qrmaster.net` oder `www.qrmaster.net`, **nicht starten** -
|
||||
dann würden Klicks in der Testumgebung in die Produktion umleiten.
|
||||
|
||||
## 3. Stack bauen und starten
|
||||
|
||||
```bash
|
||||
docker compose -p qrmaster-test --env-file .env.test -f docker-compose.yml -f docker-compose.test.yml up -d --build
|
||||
```
|
||||
|
||||
Der erste Build dauert einige Minuten. Der Projektname `-p qrmaster-test` ist wichtig: er
|
||||
sorgt dafür, dass eigene Container und ein eigenes Volume entstehen und nichts aus der
|
||||
Produktion überschrieben wird.
|
||||
|
||||
Läuft alles, sollten drei neue Container existieren:
|
||||
|
||||
```bash
|
||||
docker ps --filter "name=qrmaster-test" --format "table {{.Names}}\t{{.Status}}"
|
||||
```
|
||||
|
||||
Erwartet: `qrmaster-test-db`, `qrmaster-test-redis`, `qrmaster-test-web`.
|
||||
|
||||
Die Anwendung kann zu diesem Zeitpunkt noch nichts anzeigen - die Datenbank ist leer. Das
|
||||
ist normal und wird im nächsten Schritt behoben.
|
||||
|
||||
## 4. Datenbankschema einspielen
|
||||
|
||||
Die Testdatenbank bekommt **nur die Struktur** aus der Produktion, keine Daten. Es werden
|
||||
also keine Kundendaten kopiert.
|
||||
|
||||
Struktur aus der Produktionsdatenbank exportieren (reiner Lesezugriff):
|
||||
|
||||
```bash
|
||||
docker exec qrmaster-db pg_dump -U postgres --schema-only qrmaster > schema.sql
|
||||
```
|
||||
|
||||
In die Testdatenbank einspielen:
|
||||
|
||||
```bash
|
||||
docker exec -i qrmaster-test-db psql -U postgres -d qrmaster_test < schema.sql
|
||||
```
|
||||
|
||||
> **Wichtig:** Nicht `prisma migrate` verwenden. Die Migrationsdateien im Repository sind
|
||||
> seit April 2026 nicht mehr gepflegt - alle Schemaänderungen seitdem wurden per SQL
|
||||
> gemacht. Ein `migrate deploy` würde ein veraltetes Schema erzeugen, mit dem die
|
||||
> Anwendung nicht läuft. Der Container startet deshalb bewusst ohne Migrationsschritt.
|
||||
|
||||
Prüfen, dass Tabellen angekommen sind:
|
||||
|
||||
```bash
|
||||
docker exec qrmaster-test-db psql -U postgres -d qrmaster_test -c "\dt" | head -20
|
||||
```
|
||||
|
||||
## 5. Testaccount anlegen
|
||||
|
||||
Die Registrierung über das Formular funktioniert hier **nicht**: die Testumgebung
|
||||
verschickt bewusst keine E-Mails, und ohne Bestätigungsmail wird der Account vom System
|
||||
wieder gelöscht. Der Account wird deshalb direkt in der Datenbank angelegt.
|
||||
|
||||
Zuerst einen Passwort-Hash erzeugen (`DEIN_TESTPASSWORT` durch ein selbst gewähltes
|
||||
Passwort ersetzen):
|
||||
|
||||
```bash
|
||||
docker exec qrmaster-test-web node -e "console.log(require('bcryptjs').hashSync('DEIN_TESTPASSWORT',12))"
|
||||
```
|
||||
|
||||
Die Ausgabe ist eine Zeichenkette, die mit `$2a$12$` oder `$2b$12$` beginnt. Diese im
|
||||
folgenden Befehl anstelle von `HIER_DER_HASH` einsetzen:
|
||||
|
||||
```bash
|
||||
docker exec -i qrmaster-test-db psql -U postgres -d qrmaster_test -c "INSERT INTO \"User\" (id,email,name,password,\"emailVerified\",\"updatedAt\") VALUES ('testuser1','test@qrmaster.net','Test','HIER_DER_HASH',now(),now());"
|
||||
```
|
||||
|
||||
Anmeldung erfolgt danach ganz normal über `/login` mit `test@qrmaster.net` und dem
|
||||
gewählten Passwort.
|
||||
|
||||
## 6. Caddy konfigurieren
|
||||
|
||||
Einen neuen Block in die Caddy-Konfiguration aufnehmen (Pfad ggf. anpassen). Die
|
||||
bestehenden Blöcke bleiben unverändert:
|
||||
|
||||
```caddyfile
|
||||
testmodul.qrmaster.net {
|
||||
reverse_proxy qrmaster-test-web:3000
|
||||
}
|
||||
```
|
||||
|
||||
Konfiguration neu laden:
|
||||
|
||||
```bash
|
||||
caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
Caddy holt das TLS-Zertifikat automatisch. Das kann eine Minute dauern.
|
||||
|
||||
## 7. Abnahme
|
||||
|
||||
**a) Datenbanken sind getrennt.** Die beiden Zahlen müssen sich unterscheiden - die
|
||||
Testdatenbank enthält nur den eben angelegten Account:
|
||||
|
||||
```bash
|
||||
docker exec qrmaster-test-db psql -U postgres -d qrmaster_test -c 'SELECT count(*) FROM "User";'
|
||||
```
|
||||
|
||||
```bash
|
||||
docker exec qrmaster-db psql -U postgres -d qrmaster -c 'SELECT count(*) FROM "User";'
|
||||
```
|
||||
|
||||
**b) Keine Migrationen gelaufen.** In der Ausgabe darf **nicht** "Applying Prisma
|
||||
migrations" stehen:
|
||||
|
||||
```bash
|
||||
docker logs qrmaster-test-web 2>&1 | head -20
|
||||
```
|
||||
|
||||
**c) Suchmaschinen ausgesperrt.** Muss `Disallow: /` liefern:
|
||||
|
||||
```bash
|
||||
curl -s https://testmodul.qrmaster.net/robots.txt
|
||||
```
|
||||
|
||||
**d) Im Browser:**
|
||||
|
||||
- `https://testmodul.qrmaster.net` lädt mit gültigem Zertifikat
|
||||
- Anmeldung mit dem Testaccount funktioniert
|
||||
- `https://testmodul.qrmaster.net/dashboard` **bleibt auf testmodul** und springt nicht auf
|
||||
`app.qrmaster.net`. Passiert das doch, sind die URLs in der `.env.test` falsch.
|
||||
- In den Entwicklertools unter Application → Cookies liegen zwei getrennte Cookies:
|
||||
`userId` mit Domain `.qrmaster.net` (Produktion) und `userId_test` mit Domain
|
||||
`testmodul.qrmaster.net`
|
||||
- Die Produktion ist weiterhin erreichbar und man ist dort weiterhin angemeldet
|
||||
|
||||
---
|
||||
|
||||
## Laufender Betrieb
|
||||
|
||||
Neuen Stand deployen, nachdem auf dem Branch `test` etwas gepusht wurde - aus dem
|
||||
Verzeichnis `qrmaster-test`:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose -p qrmaster-test --env-file .env.test -f docker-compose.yml -f docker-compose.test.yml up -d --build
|
||||
```
|
||||
|
||||
Ein Rebuild ist **immer** nötig, ein bloßer Neustart genügt nicht: die Host-URLs und der
|
||||
Cookie-Name werden beim Bauen fest in die Anwendung kompiliert.
|
||||
|
||||
Schemaänderungen werden weiterhin **von Hand per SQL** ausgeführt - erst auf Test, nach
|
||||
erfolgreicher Prüfung dasselbe Statement auf Produktion. Es gibt keinen automatischen Weg
|
||||
dazwischen.
|
||||
|
||||
## Testumgebung stoppen oder entfernen
|
||||
|
||||
Stoppen, Daten bleiben erhalten:
|
||||
|
||||
```bash
|
||||
docker compose -p qrmaster-test --env-file .env.test -f docker-compose.yml -f docker-compose.test.yml down
|
||||
```
|
||||
|
||||
Vollständig entfernen inklusive Testdatenbank - der Projektname `-p qrmaster-test` sorgt
|
||||
dafür, dass ausschließlich die Test-Volumes gelöscht werden:
|
||||
|
||||
```bash
|
||||
docker compose -p qrmaster-test --env-file .env.test -f docker-compose.yml -f docker-compose.test.yml down -v
|
||||
```
|
||||
|
||||
## Wenn etwas nicht funktioniert
|
||||
|
||||
| Symptom | Ursache |
|
||||
|---|---|
|
||||
| Build bricht ab mit `set AUTH_COOKIE_NAME in .env.test` | `.env.test` fehlt oder liegt im falschen Verzeichnis |
|
||||
| `qrmaster-test-db` bleibt `unhealthy`, `web` startet nicht | In der `.env.test` steht nicht `POSTGRES_DB=qrmaster_test` |
|
||||
| Caddy liefert 502 | Containername im Caddy-Block stimmt nicht, oder der Container läuft nicht - mit `docker ps` prüfen |
|
||||
| Anwendung meldet `column ... does not exist` | Schema-Import aus Schritt 4 war unvollständig - erneut einspielen |
|
||||
| `/dashboard` springt auf `app.qrmaster.net` | Die URLs in der `.env.test` zeigen nicht auf testmodul. Korrigieren und **neu bauen**, nicht nur neu starten. |
|
||||
| Anmeldung wirkt zufällig abgelaufen | `AUTH_COOKIE_NAME` ist nicht gesetzt oder steht auf `userId` - dann kollidiert es mit dem Produktions-Cookie |
|
||||
| Registrierung über das Formular schlägt fehl | Erwartet - die Testumgebung verschickt keine E-Mails. Account per SQL anlegen, Schritt 5. |
|
||||
|
||||
## Bekannte Einschränkungen der Testumgebung
|
||||
|
||||
Bewusst deaktiviert, weil die Umgebung nach außen nichts auslösen soll:
|
||||
|
||||
- **Kein E-Mail-Versand** - Registrierung, Passwort-Reset und Benachrichtigungen funktionieren nicht
|
||||
- **Kein Google-Login** - Zugangsdaten sind nicht hinterlegt
|
||||
- **Keine Datei-Uploads** - der Objektspeicher (R2) ist nicht konfiguriert
|
||||
- **Kein Stripe** - Checkout und Abo-Verwaltung funktionieren nicht, solange keine Testschlüssel eingetragen sind
|
||||
- **Keine Analytics** - damit die Produktionszahlen nicht verfälscht werden
|
||||
@@ -49,6 +49,8 @@ ARG NEXT_PUBLIC_UMAMI_SRC=""
|
||||
ARG NEXT_PUBLIC_UMAMI_ID=""
|
||||
ENV NEXT_PUBLIC_UMAMI_SRC=$NEXT_PUBLIC_UMAMI_SRC
|
||||
ENV NEXT_PUBLIC_UMAMI_ID=$NEXT_PUBLIC_UMAMI_ID
|
||||
ARG SMTP_USER=""
|
||||
ENV SMTP_USER=$SMTP_USER
|
||||
# Shared session cookie across www.* and app.*. Needed at build time too: process.env is
|
||||
# inlined into the Edge middleware bundle, so a runtime-only value would leave the
|
||||
# middleware and the route handlers disagreeing about the cookie scope.
|
||||
|
||||
@@ -85,7 +85,7 @@ services:
|
||||
RESEND_API_KEY: ${RESEND_API_KEY:-}
|
||||
SMTP_HOST: ${SMTP_HOST:-smtp.qrmaster.net}
|
||||
SMTP_PORT: ${SMTP_PORT:-465}
|
||||
SMTP_USER: ${SMTP_USER:-timo@qrmaster.net}
|
||||
SMTP_USER: ${SMTP_USER:-info@qrmaster.net}
|
||||
SMTP_PASS: ${SMTP_PASS:-}
|
||||
NEWSLETTER_ADMIN_EMAIL: ${NEWSLETTER_ADMIN_EMAIL:-}
|
||||
NEWSLETTER_ADMIN_PASSWORD: ${NEWSLETTER_ADMIN_PASSWORD:-}
|
||||
|
||||
@@ -95,6 +95,11 @@ const nextConfig = {
|
||||
destination: '/restaurants',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/blog/qr-code-print-size-guide',
|
||||
destination: '/qr-code-print-size-guide',
|
||||
permanent: true,
|
||||
},
|
||||
|
||||
{
|
||||
source: '/create-qr',
|
||||
|
||||
@@ -28,6 +28,7 @@ const toolsMap: Record<string, { href: string; title: string; description: strin
|
||||
"paypal-qr-code": { href: "/tools/paypal-qr-code", title: "PayPal QR Code", description: "Link directly to a PayPal payment, invoice, or donation page." },
|
||||
"pdf-qr-code": { href: "/tools/url-qr-code", title: "PDF QR Code", description: "Use a URL QR code to open a hosted PDF like a menu, brochure, or operating guide." },
|
||||
"whatsapp-qr-code": { href: "/tools/whatsapp-qr-code", title: "WhatsApp QR Code", description: "Open a WhatsApp chat with your number pre-loaded for faster support." },
|
||||
"google-review-qr-code": { href: "/tools/google-review-qr-code", title: "Google Review QR Code", description: "Send customers straight to your Google review form while they are still on site." },
|
||||
};
|
||||
|
||||
const industryPrimaryCtas: Record<string, Cta> = {
|
||||
|
||||
@@ -144,6 +144,25 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
},
|
||||
];
|
||||
|
||||
// Alternatives & head-to-head comparison pages.
|
||||
// These are hardcoded route files (no data module), so keep this list in sync
|
||||
// with `alternativesPages` in src/lib/indexnow.ts and the "Compare" column in
|
||||
// src/components/ui/Footer.tsx.
|
||||
const comparisonPages = [
|
||||
{ path: '/alternatives', priority: 0.9 },
|
||||
{ path: '/alternatives/beaconstac', priority: 0.85 },
|
||||
{ path: '/alternatives/bitly', priority: 0.85 },
|
||||
{ path: '/alternatives/flowcode', priority: 0.85 },
|
||||
{ path: '/alternatives/qr-code-generator', priority: 0.85 },
|
||||
{ path: '/vs', priority: 0.9 },
|
||||
{ path: '/vs/beaconstac', priority: 0.85 },
|
||||
].map(({ path, priority }) => ({
|
||||
url: `${baseUrl}${path}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly' as const,
|
||||
priority,
|
||||
}));
|
||||
|
||||
const publishedPseoPages = [
|
||||
...publishedComparisonPages.map((page) => ({
|
||||
url: `${baseUrl}${page.canonicalPath}`,
|
||||
@@ -216,12 +235,8 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.95,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/dynamic-barcode-generator`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.9,
|
||||
},
|
||||
// NOTE: /dynamic-barcode-generator and /barcode-generator are 301'd to
|
||||
// /tools/barcode-generator in next.config.mjs and must not be listed here.
|
||||
{
|
||||
url: `${baseUrl}/bulk-qr-code-generator`,
|
||||
lastModified: new Date(),
|
||||
@@ -234,6 +249,24 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/manage-qr-codes`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.9,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/qr-code-print-size-guide`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/developers`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.8,
|
||||
},
|
||||
|
||||
{
|
||||
url: `${baseUrl}/pricing`,
|
||||
@@ -272,6 +305,18 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.4,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/terms`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.4,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/cookie-policy`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 0.4,
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/contact`,
|
||||
lastModified: new Date(),
|
||||
@@ -301,6 +346,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
...blogPages,
|
||||
...learnPages,
|
||||
...growthUseCasePages,
|
||||
...comparisonPages,
|
||||
...publishedPseoPages,
|
||||
...industryUrls,
|
||||
...authorPages,
|
||||
|
||||
@@ -12,6 +12,9 @@ import type { BlogPost, PillarKey, AuthorProfile } from "./types";
|
||||
export const REDIRECTED_BLOG_SLUGS = new Set<string>([
|
||||
"qr-code-analytics",
|
||||
"qr-code-restaurant-menu",
|
||||
// Duplicate of the standalone /qr-code-print-size-guide, which is the version
|
||||
// Google actually ranks. Both were live and self-canonical until 2026-08-13.
|
||||
"qr-code-print-size-guide",
|
||||
]);
|
||||
|
||||
export function isRedirectedSlug(slug: string): boolean {
|
||||
|
||||
@@ -48,6 +48,20 @@ async function waitForRateLimit() {
|
||||
lastEmailSent = Date.now();
|
||||
}
|
||||
|
||||
function getEmailFrom(name = 'Timo from QR Master'): string {
|
||||
const address = process.env.SMTP_USER || 'timo@qrmaster.net';
|
||||
return `${name} <${address}>`;
|
||||
}
|
||||
|
||||
function getEmailFromSecurity(): string {
|
||||
const address = process.env.SMTP_USER || 'noreply@qrmaster.net';
|
||||
return `QR Master Security <${address}>`;
|
||||
}
|
||||
|
||||
function getEmailReplyTo(): string {
|
||||
return process.env.SMTP_USER || 'support@qrmaster.net';
|
||||
}
|
||||
|
||||
/**
|
||||
* Password Reset Email - Security focused with clear urgency
|
||||
*/
|
||||
@@ -58,8 +72,8 @@ export async function sendPasswordResetEmail(email: string, resetToken: string)
|
||||
|
||||
try {
|
||||
await resend.emails.send({
|
||||
from: 'QR Master Security <noreply@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFromSecurity(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: '🔐 Reset Your QR Master Password (Expires in 1 Hour)',
|
||||
html: `
|
||||
@@ -190,8 +204,8 @@ export async function sendNewsletterWelcomeEmail(email: string) {
|
||||
|
||||
try {
|
||||
await resend.emails.send({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: '🎉 You\'re In! Here\'s What Happens Next (AI QR Features)',
|
||||
html: `
|
||||
@@ -362,8 +376,8 @@ export async function sendAIFeatureLaunchEmail(email: string) {
|
||||
|
||||
try {
|
||||
await resend.emails.send({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: '🚀 They\'re Live! Your AI QR Features Are Ready',
|
||||
html: `
|
||||
@@ -568,8 +582,8 @@ export async function sendEmailVerificationEmail(email: string, name: string, ve
|
||||
const firstName = name.trim().split(/\s+/)[0] || 'there';
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: 'Confirm your QR Master email address',
|
||||
html: `<!doctype html><html><body style="margin:0;background:#f5f4ef;color:#1b1c19;font-family:Arial,sans-serif;"><table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr><td align="center" style="padding:32px 12px;"><table role="presentation" width="600" cellspacing="0" cellpadding="0" style="width:100%;max-width:600px;background:#fff;"><tr><td style="padding:20px 32px;border-bottom:1px solid #e3e3de;font-size:11px;font-weight:bold;letter-spacing:2px;">QR MASTER</td></tr><tr><td style="padding:36px 32px;"><h1 style="margin:0 0 18px;font-family:Georgia,serif;font-size:30px;font-weight:normal;line-height:1.2;">Confirm your email address</h1><p style="margin:0;font-size:16px;line-height:1.65;">Hi ${escapeHtml(firstName)},</p><p style="font-size:16px;line-height:1.65;">Click the button below to finish creating your QR Master account.</p><a href="${verificationUrl}" style="display:inline-block;margin:10px 0 22px;background:#0047ff;color:#fff;padding:14px 22px;text-decoration:none;font-size:14px;font-weight:bold;">CONFIRM EMAIL</a><p style="margin:0;color:#747878;font-size:13px;line-height:1.6;">This link expires in 24 hours. If you did not create an account, you can ignore this email.</p></td></tr></table></td></tr></table></body></html>`,
|
||||
@@ -586,8 +600,8 @@ export async function sendDesignerAnnouncementEmail(email: string, unsubscribeUr
|
||||
const transport = createSmtpTransport();
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: 'Your QR codes can now look like your brand',
|
||||
html: `
|
||||
@@ -652,8 +666,8 @@ export async function sendNewsletterEmail({
|
||||
const transport = createSmtpTransport();
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject,
|
||||
html: `<!doctype html><html><body style="margin:0;background:#f5f4ef;color:#1b1c19;font-family:Arial,sans-serif;"><table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr><td align="center" style="padding:32px 12px;"><table role="presentation" width="600" cellspacing="0" cellpadding="0" style="width:100%;max-width:600px;background:#fff;"><tr><td style="padding:20px 32px;border-bottom:1px solid #e3e3de;font-size:11px;font-weight:bold;letter-spacing:2px;">QR MASTER</td></tr><tr><td style="padding:36px 32px;">${body}</td></tr><tr><td style="padding:20px 32px;border-top:1px solid #e3e3de;color:#747878;font-size:11px;line-height:1.6;">You are receiving this email from QR Master.<br><a href="${unsubscribeUrl}" style="color:#747878;">Unsubscribe from product updates</a></td></tr></table></td></tr></table></body></html>`,
|
||||
@@ -929,8 +943,8 @@ export async function sendWelcomeEmail(email: string, name: string) {
|
||||
`);
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: 'Your QR Master account is ready',
|
||||
html,
|
||||
@@ -1066,8 +1080,8 @@ export async function sendActivationNudgeEmail(email: string, name: string) {
|
||||
`);
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: "Your 3 free codes are still sitting there",
|
||||
html,
|
||||
@@ -1237,8 +1251,8 @@ export async function sendUpgradeNudgeEmail(email: string, name: string, qrCount
|
||||
`);
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: 'You just hit the free limit',
|
||||
html,
|
||||
@@ -1413,8 +1427,8 @@ export async function sendThirtyDayNudgeEmail(
|
||||
`);
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: `${firstName}, your codes were scanned ${scanCount} time${scanCount !== 1 ? 's' : ''} this month`,
|
||||
html,
|
||||
@@ -1528,8 +1542,8 @@ export async function sendFirstScanEmail(
|
||||
`);
|
||||
|
||||
await transport.sendMail({
|
||||
from: 'Timo from QR Master <timo@qrmaster.net>',
|
||||
replyTo: 'support@qrmaster.net',
|
||||
from: getEmailFrom(),
|
||||
replyTo: getEmailReplyTo(),
|
||||
to: email,
|
||||
subject: 'Your QR code was just scanned for the first time',
|
||||
html,
|
||||
|
||||
@@ -89,7 +89,8 @@ export function getAllIndexableUrls(): string[] {
|
||||
`${baseUrl}/qr-code-tracking`,
|
||||
`${baseUrl}/reprint-calculator`,
|
||||
`${baseUrl}/dynamic-qr-code-generator`,
|
||||
`${baseUrl}/dynamic-barcode-generator`,
|
||||
// /dynamic-barcode-generator and /barcode-generator are 301'd to
|
||||
// /tools/barcode-generator in next.config.mjs - never submit them.
|
||||
`${baseUrl}/bulk-qr-code-generator`,
|
||||
`${baseUrl}/custom-qr-code-generator`,
|
||||
`${baseUrl}/manage-qr-codes`,
|
||||
@@ -105,6 +106,7 @@ export function getAllIndexableUrls(): string[] {
|
||||
`${baseUrl}/restaurants`,
|
||||
`${baseUrl}/qr-code-analytics`,
|
||||
`${baseUrl}/qr-code-print-size-guide`,
|
||||
`${baseUrl}/developers`,
|
||||
];
|
||||
|
||||
// Alternatives & comparison hub pages
|
||||
|
||||
@@ -58,7 +58,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Loyalty programs and promotional offers",
|
||||
"WiFi access for guest connectivity"
|
||||
],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "pdf-qr-code"],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "Do customers prefer QR code menus?", answer: "Yes, 78% of customers enjoy using QR codes for menus. The convenience of contactless access appeals especially to modern diners." },
|
||||
{ question: "Should I use static or dynamic QR codes?", answer: "Dynamic QR codes are essential. They allow you to update menu items and prices without replacing the physical codes on your tables." },
|
||||
@@ -97,7 +97,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Social media promotion to grow your following",
|
||||
"Customer feedback forms to improve service"
|
||||
],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "sms-qr-code"],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "sms-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "Are QR codes cost-effective for small cafes?", answer: "Yes! They eliminate menu reprinting costs, reduce paper waste, and many generators offer free basic plans. For a step-by-step menu setup, see the <a href=\"/blog/qr-code-restaurant-menu\" class=\"text-blue-600 underline\">Restaurant Menu QR Code Guide →</a>" },
|
||||
{ question: "Can I use one QR code for multiple purposes?", answer: "Yes, with dynamic QR codes you can easily update the destination URL to serve different purposes over time." },
|
||||
@@ -136,7 +136,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Amenity booking for spa and dining",
|
||||
"Guest feedback surveys and review collection"
|
||||
],
|
||||
tools: ["wifi-qr-code", "url-qr-code", "pdf-qr-code"],
|
||||
tools: ["wifi-qr-code", "url-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do QR codes improve hotel check-in?", answer: "They enable guests to access digital registration forms instantly upon arrival, reducing front desk queues and wait times." },
|
||||
{ question: "Can hotels update QR code information?", answer: "Yes, dynamic QR codes allow you to update linked content like restaurant hours or pool rules without replacing physical signs." },
|
||||
@@ -175,7 +175,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Agent vCard contact information",
|
||||
"Neighborhood information and local amenities"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "video-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "video-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "Can I reuse a QR code when a property sells?", answer: "Yes, with dynamic QR codes you can simply update the destination URL to point to your next listing." },
|
||||
{ question: "How do I track buyer interest from yard signs?", answer: "QR code analytics show total scans, geographic locations, device types, and time of day to reveal buyer interest patterns." },
|
||||
@@ -214,7 +214,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Fitness app downloads at key touchpoints",
|
||||
"Loyalty programs with attendance tracking"
|
||||
],
|
||||
tools: ["url-qr-code", "app-store-qr-code", "video-qr-code"],
|
||||
tools: ["url-qr-code", "app-store-qr-code", "video-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do QR codes reduce injuries?", answer: "Codes on equipment link to tutorial videos showing proper setup and correct form, building confidence and reducing injury risks." },
|
||||
{ question: "Can QR codes improve member retention?", answer: "Yes, gyms using QR codes for personalized content report increased member retention due to a vastly improved, self-serve experience." },
|
||||
@@ -253,7 +253,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Aftercare instructions and prescription information",
|
||||
"Staff directory with provider specialties"
|
||||
],
|
||||
tools: ["wifi-qr-code", "url-qr-code", "pdf-qr-code"],
|
||||
tools: ["wifi-qr-code", "url-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "Are QR codes HIPAA compliant?", answer: "QR codes themselves are just links. As long as the destination page (like your patient portal) is HIPAA-compliant, you are entirely secure." },
|
||||
{ question: "How do QR codes reduce administrative burden?", answer: "They replace paper forms and allow patients to self-serve WiFi passwords and appointment booking, freeing up your front desk staff." },
|
||||
@@ -370,7 +370,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Table reservation and waitlist management",
|
||||
"Loyalty punch card replacement for regulars"
|
||||
],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "instagram-qr-code"],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "instagram-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do bars use QR codes for menus?", answer: "Dynamic QR codes on table tents or bar top stickers link to a digital menu page. When you update the page, the code stays the same. See the full setup guide: <a href=\"/blog/qr-code-restaurant-menu\" class=\"text-blue-600 underline\">Restaurant Menu QR Code Guide →</a>" },
|
||||
{ question: "Can I show different menus for happy hour vs. regular hours?", answer: "Yes, with a dynamic QR code you can schedule URL redirects so the same physical code shows a happy hour menu during those specific hours." },
|
||||
@@ -409,7 +409,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Pre-order or call-ahead link for lunch rush management",
|
||||
"Feedback form to collect reviews between stops"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "geolocation-qr-code"],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "geolocation-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do food trucks update their location via QR code?", answer: "Use a dynamic QR code pointing to a simple webpage or Google Maps link. Update the URL each morning - your physical sticker never changes." },
|
||||
{ question: "Should a food truck QR code link to Instagram or a menu?", answer: "Both. Use a single link-in-bio style landing page that shows today's location, menu, and your Instagram handle from one scan." },
|
||||
@@ -448,7 +448,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Pre-order link for morning rush management",
|
||||
"Newsletter sign-up to notify customers of seasonal items"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "email-qr-code"],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "email-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do bakeries handle allergen info with QR codes?", answer: "Each product card or display case label can include a QR code linking to a full ingredient and allergen breakdown page." },
|
||||
{ question: "Can a bakery replace paper loyalty cards with QR codes?", answer: "Yes, link a QR code to a simple stamp card app or loyalty platform. Customers scan at checkout instead of presenting a paper card." },
|
||||
@@ -487,7 +487,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Online shop or local delivery ordering",
|
||||
"Beer club membership sign-up at the bar"
|
||||
],
|
||||
tools: ["url-qr-code", "event-qr-code", "instagram-qr-code"],
|
||||
tools: ["url-qr-code", "event-qr-code", "instagram-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do tap rooms show tasting notes with QR codes?", answer: "Place a small QR placard at each tap handle linking to a product page with full tasting notes, ABV, and pairing suggestions." },
|
||||
{ question: "Can I put a QR code on a beer label?", answer: "Yes, use a small QR code on the back label. It can link to the brew story, vintage notes, or a food pairing guide." },
|
||||
@@ -526,7 +526,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Wristband QR linking to after-party details",
|
||||
"Guest list sign-up from social media flyers"
|
||||
],
|
||||
tools: ["url-qr-code", "event-qr-code", "instagram-qr-code"],
|
||||
tools: ["url-qr-code", "event-qr-code", "instagram-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do nightclubs use QR codes for entry?", answer: "Tickets are issued as QR codes. Door staff scan each guest's phone screen to verify and grant entry - faster than checking names on a list." },
|
||||
{ question: "Can QR codes replace printed event flyers?", answer: "QR codes on digital flyers or social media posts replace printed flyers while adding interactivity - linking directly to tickets or table booking." },
|
||||
@@ -565,7 +565,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"vCard contact details for event planners",
|
||||
"Video testimonials from past events"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "pdf-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do caterers use QR codes to win more business?", answer: "A QR code on a business card links to a portfolio page with photos, menus, and a quote form - giving a full sales pitch long after you've left the room." },
|
||||
{ question: "Should catering QR codes be on business cards or event signage?", answer: "Both. Business cards capture leads from planners you meet directly. Event signage captures guests who taste your food and want to hire you." },
|
||||
@@ -604,7 +604,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Food pairing suggestions and recipe ideas",
|
||||
"Online shop for direct-to-consumer sales"
|
||||
],
|
||||
tools: ["url-qr-code", "event-qr-code", "instagram-qr-code"],
|
||||
tools: ["url-qr-code", "event-qr-code", "instagram-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "Can wineries put QR codes on bottle labels?", answer: "Yes, a small QR code on the back label links to tasting notes, food pairings, and booking pages without cluttering the front design." },
|
||||
{ question: "What should a winery QR code link to?", answer: "The most effective destination combines the vintage story, food pairing guide, cellar door booking link, and wine club sign-up in one page." },
|
||||
@@ -644,7 +644,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Workshop and retreat registration",
|
||||
"Member portal for existing students"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "event-qr-code"],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "event-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do yoga studios attract new students with QR codes?", answer: "A QR on the studio window or door links to an intro offer - like a first-week free deal - that captures email and converts walk-by traffic to bookings." },
|
||||
{ question: "Can QR codes reduce class no-shows?", answer: "Yes, linking to a booking system that sends automated reminders significantly reduces no-show rates compared to informal reservations." },
|
||||
@@ -687,7 +687,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Post-treatment care instructions and product recommendations",
|
||||
"Google review link on checkout receipts"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "instagram-qr-code"],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "instagram-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do spas use QR codes to sell more gift cards?", answer: "A QR code near the front desk or on a checkout card links directly to the gift card purchase page - converting satisfied clients into gift-givers on the spot." },
|
||||
{ question: "Can QR codes replace spa brochures?", answer: "Yes, a QR code linking to a digital treatment menu with photos is more current and far cheaper to maintain than printed brochures." },
|
||||
@@ -729,7 +729,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Loyalty program with visit tracking",
|
||||
"Instagram follow link at reception"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "vcard-qr-code"],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "vcard-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do beauty salons use QR codes to get more bookings?", answer: "A QR code on the station mirror or checkout counter links directly to the booking platform. Clients rebook while still at the salon, capturing high-intent conversions." },
|
||||
{ question: "Can a QR code replace a printed price list at a salon?", answer: "Yes, link a QR to your service menu page. When prices change, update the page - the physical QR code never needs to be reprinted. The same principle applies across hospitality: <a href=\"/blog/qr-code-restaurant-menu\" class=\"text-blue-600 underline\">see how restaurants handle this →</a>" },
|
||||
@@ -769,7 +769,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Google review request on checkout card",
|
||||
"Loyalty card replacement for regular clients"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "call-qr-code-generator"],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "call-qr-code-generator", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do barbershops reduce walk-in wait times with QR codes?", answer: "A QR on the shop window links to your booking system. Clients book a slot instead of waiting, spreading demand more evenly throughout the day." },
|
||||
{ question: "Should each barber have their own QR code?", answer: "For shops with distinct stylists, yes. Individual barber portfolio QR codes help clients pick a preferred barber and follow them on Instagram." },
|
||||
@@ -811,7 +811,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Aftercare instructions for gel and acrylic maintenance",
|
||||
"Instagram follow link to see new seasonal designs"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "event-qr-code"],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "event-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do nail salons use QR codes to fill appointment slots?", answer: "A QR at the nail station while clients wait for polish to dry links directly to the booking page - capturing rebooking at the highest-intent moment." },
|
||||
{ question: "Can QR codes replace paper loyalty cards at nail salons?", answer: "Yes, a QR linked to a digital stamp card app tracks visits automatically and doesn't get lost at the bottom of a handbag." },
|
||||
@@ -850,7 +850,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Consent and health waiver form filled on the client's phone",
|
||||
"Booking page for consultation requests"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "pdf-qr-code"],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do tattoo artists use QR codes for their portfolio?", answer: "A QR code on a small artist card or studio display links directly to their portfolio page or Instagram. Walk-in clients scan and browse before choosing an artist." },
|
||||
{ question: "Can QR codes replace printed aftercare sheets?", answer: "Yes, a QR code sticker on the care wrap or a card given at checkout links to a detailed aftercare page - easier to follow and always up to date." },
|
||||
@@ -889,7 +889,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Loyalty card sign-up at the counter",
|
||||
"Health guide resources for common conditions"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "email-qr-code"],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "email-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do pharmacies use QR codes on prescription bags?", answer: "A printed QR code on the bag label links to the specific medication information page - dosing, side effects, and storage instructions in the patient's preferred language." },
|
||||
{ question: "Can QR codes increase flu shot bookings at pharmacies?", answer: "Yes, a QR code at the counter linking directly to the vaccination booking form removes friction and converts prescription pick-up visits into booked appointments." },
|
||||
@@ -967,7 +967,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Vehicle history and inspection report",
|
||||
"Salesperson vCard for after-hours follow-up"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "video-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "video-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do car dealerships use QR codes on the lot?", answer: "A QR on the windshield visor card links to the full vehicle listing with specs, photos, and a booking form - giving buyers all the information without needing a salesperson present." },
|
||||
{ question: "Can QR codes increase test drive conversions?", answer: "Yes, removing friction from the booking process by linking directly from the physical car to the form significantly increases test drive sign-ups." },
|
||||
@@ -1006,7 +1006,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Wedding and event flower inquiry form",
|
||||
"Gift message and delivery scheduling"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "email-qr-code"],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "email-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do florists use QR codes on delivery packaging?", answer: "A QR on the wrapping paper or box links to care instructions for the specific flowers included - reducing the most common post-purchase support question." },
|
||||
{ question: "Can QR codes increase florist repeat orders?", answer: "Yes, a reorder link in the care page or on a small card inside the bouquet makes it trivial for satisfied customers to order again." },
|
||||
@@ -1123,7 +1123,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Engagement ring guide for nervous buyers",
|
||||
"Gift wrapping and message scheduling for occasions"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "pdf-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do jewelry stores use QR codes to build trust?", answer: "A QR on the price tag or display card links to the gemstone's provenance - origin country, ethical sourcing certification, and grading report - answering the buyer's biggest concern." },
|
||||
{ question: "Can QR codes help jewelry stores sell custom pieces?", answer: "Yes, a QR near display cases links to a custom design inquiry form. Browsers who are unsure of available options can explore customization without a sales conversation." },
|
||||
@@ -1591,7 +1591,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Preferred vendor list for referred couples",
|
||||
"Wedding planning guide and checklist download"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "instagram-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "instagram-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do wedding planners use QR codes at bridal fairs?", answer: "A QR on stand signage and handed cards links directly to the portfolio and consultation booking page - converting fair visitors to booked clients without relying on follow-up emails." },
|
||||
{ question: "Should wedding planners use QR codes on business cards?", answer: "Yes, a QR on the reverse side of a business card linking to the portfolio page gives couples a full picture of your work that no card can show." },
|
||||
@@ -1630,7 +1630,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Client gallery access with QR for private delivery",
|
||||
"Google review request on delivery card"
|
||||
],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "vcard-qr-code"],
|
||||
tools: ["url-qr-code", "instagram-qr-code", "vcard-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do photographers use QR codes to get more bookings?", answer: "A QR on every business card and photo delivery links to the portfolio and booking calendar - capturing the highest-intent moment: when someone has just seen or received your work." },
|
||||
{ question: "Can photographers use QR codes to sell print packages?", answer: "Yes, a QR inside the delivery packaging links to the print and album store. Clients who love their digital gallery are significantly more likely to order physical products." },
|
||||
@@ -1712,7 +1712,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Client portal access for case updates",
|
||||
"Firm overview and partner biographies for referrals"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "email-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "email-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do law firms use QR codes on business cards?", answer: "A vCard QR on the back of each attorney's card lets clients save the full contact - name, title, direct line, and practice area - in one scan." },
|
||||
{ question: "Can law firm QR codes increase consultation bookings?", answer: "Yes, a QR on the firm brochure or waiting room poster linking to the scheduling page converts interested visitors without requiring a receptionist to manage every booking." },
|
||||
@@ -1751,7 +1751,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Client portal access for accounts and reports",
|
||||
"Referral program landing page for new client acquisition"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "email-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "email-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do accountants use QR codes to collect client documents?", answer: "A QR in client correspondence links to the firm's secure document upload portal. Clients submit tax records and financial statements directly - avoiding insecure email attachments." },
|
||||
{ question: "Can QR codes reduce missed tax deadlines for accounting clients?", answer: "Yes, a QR in monthly emails or on the firm brochure linking to an upcoming deadlines calendar significantly improves client compliance." },
|
||||
@@ -1790,7 +1790,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Claims process guide and contact links",
|
||||
"Policy renewal reminder and self-service link"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "pdf-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do insurance agencies use QR codes for lead generation?", answer: "A QR on every direct mail piece and business card links to the online quote form - converting cold mail recipients into warm leads at their moment of highest attention." },
|
||||
{ question: "Can QR codes help insurance clients understand their policies?", answer: "Yes, a QR in the policy document linking to a plain-English summary or video explainer reduces client confusion and claims-related call volume." },
|
||||
@@ -1829,7 +1829,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Booking confirmation and travel document portal",
|
||||
"Loyalty and returning customer discount landing page"
|
||||
],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "instagram-qr-code"],
|
||||
tools: ["url-qr-code", "pdf-qr-code", "instagram-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do travel agencies use QR codes to get more bookings?", answer: "A QR on each destination brochure links directly to the package details and booking form - turning print material into an active booking channel." },
|
||||
{ question: "Can QR codes work for travel agency window displays?", answer: "Yes, window display QRs capture passerby interest after hours. Passersby scan the destination card and access the full package details immediately." },
|
||||
@@ -1989,7 +1989,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Treatment information and FAQ for nervous patients",
|
||||
"Google review request at checkout"
|
||||
],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "pdf-qr-code"],
|
||||
tools: ["url-qr-code", "wifi-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do dental practices use QR codes for new patients?", answer: "A QR in the waiting room links to the digital new patient intake form. Patients complete it on their phone before being called in - speeding up the check-in process." },
|
||||
{ question: "Can QR codes help dentists get more Google reviews?", answer: "Yes, a QR on the checkout card linking directly to the Google review page captures reviews at the highest-intent moment - immediately after a positive visit." },
|
||||
@@ -2028,7 +2028,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Digital vCard for instant contact saving",
|
||||
"Special offer and referral program links"
|
||||
],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "pdf-qr-code"],
|
||||
tools: ["url-qr-code", "vcard-qr-code", "pdf-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do pet groomers use QR codes for booking?", answer: "They place codes on business cards and their shop window that link directly to their booking software, reducing the need for phone tag." },
|
||||
{ question: "Can I show a portfolio using a QR code?", answer: "Yes, you can link to an Instagram page or a dedicated gallery showing the dogs and cats you've groomed." },
|
||||
@@ -2067,7 +2067,7 @@ export const allIndustries: IndustryPage[] = [
|
||||
"Post-operative care and medication guides",
|
||||
"Pet insurance information and quote links"
|
||||
],
|
||||
tools: ["url-qr-code", "call-qr-code-generator", "vcard-qr-code"],
|
||||
tools: ["url-qr-code", "call-qr-code-generator", "vcard-qr-code", "google-review-qr-code"],
|
||||
faq: [
|
||||
{ question: "How do vets use QR codes for emergencies?", answer: "They print codes that automatically dial the clinic or open a map to the nearest emergency animal hospital when scanned." },
|
||||
{ question: "Can QR codes hold a pet's medical history?", answer: "They link to a secure portal where pet owners and other care providers can see the pet's latest records and vaccines." },
|
||||
|
||||
Reference in New Issue
Block a user