Clone
Complete Plan: QRurl with Nuxt on Cloudflare Workers
Overview
Build a URL shortener with QR code generation using Nuxt (latest stable version) deployed to Cloudflare Workers with D1 database, R2 storage, and Postmark email integration.
Architecture Components
- Frontend & Backend: Single Nuxt application (SSR)
- Database: Cloudflare D1 (SQLite)
- File Storage: Cloudflare R2 (for logos)
- Cache: Cloudflare KV
- Email: Postmark API
- Deployment: Cloudflare Workers via Wrangler
Prerequisites
- Node.js 20.x or later (stable LTS)
- Cloudflare account with Workers, D1, R2, KV enabled
- Domain in Cloudflare (qrurl.us)
- Postmark account and API key
- GitHub account (optional for CI/CD)
Step-by-Step Implementation Plan
Phase 1: Project Setup
1.1 Initialize Nuxt Project
npx nuxi@latest init qrurl --package-manager npm
cd qrurl
1.2 Install Core Dependencies
npm install --save-dev wrangler@latest
npm install @nuxt/ui @pinia/nuxt @vueuse/nuxt
npm install qrcode jsonwebtoken bcryptjs
npm install --save-dev @types/jsonwebtoken @types/bcryptjs
1.3 Configure Nuxt for Cloudflare
Create nuxt.config.ts:
- Set nitro preset to
cloudflare-pagesorcloudflare-module - Configure build output for Workers
- Set up environment variables
- Configure TypeScript
Phase 2: Database Setup
2.1 Create D1 Database
wrangler d1 create qrurl-db
2.2 Database Schema
Create schema.sql:
CREATE TABLE links (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT UNIQUE NOT NULL,
url TEXT NOT NULL,
name TEXT,
logo_key TEXT,
user_email TEXT,
clicks INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE auth_tokens (
token TEXT PRIMARY KEY,
email TEXT NOT NULL,
used INTEGER DEFAULT 0,
expires_at DATETIME NOT NULL
);
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
user_email TEXT NOT NULL,
expires_at DATETIME NOT NULL
);
CREATE INDEX idx_links_slug ON links(slug);
CREATE INDEX idx_sessions_email ON sessions(user_email);
2.3 Initialize Database
wrangler d1 execute qrurl-db --file=./schema.sql --local
wrangler d1 execute qrurl-db --file=./schema.sql --remote
Phase 3: Cloudflare Resources Setup
3.1 Create R2 Bucket
wrangler r2 bucket create qrurl-logos
3.2 Create KV Namespace
wrangler kv namespace create cache
3.3 Update wrangler.toml
name = "qrurl"
compatibility_date = "2024-12-01"
pages_build_output_dir = ".output/public"
[[d1_databases]]
binding = "DB"
database_name = "qrurl-db"
database_id = "YOUR_DB_ID"
[[r2_buckets]]
binding = "STORAGE"
bucket_name = "qrurl-logos"
[[kv_namespaces]]
binding = "CACHE"
id = "YOUR_KV_ID"
[vars]
EMAIL_FROM = "noreply@qrurl.us"
Phase 4: Application Development
4.1 Directory Structure
qrurl/
├── server/
│ ├── api/
│ │ ├── auth/
│ │ │ ├── request.post.ts
│ │ │ └── verify.get.ts
│ │ ├── links/
│ │ │ ├── index.get.ts
│ │ │ ├── index.post.ts
│ │ │ └── [id].delete.ts
│ │ ├── qr/
│ │ │ └── [slug].get.ts
│ │ └── logo/
│ │ ├── upload.post.ts
│ │ └── [id].get.ts
│ ├── middleware/
│ │ └── auth.ts
│ └── utils/
│ ├── db.ts
│ ├── auth.ts
│ └── email.ts
├── pages/
│ ├── index.vue
│ ├── login.vue
│ ├── dashboard.vue
│ └── [slug].vue
├── components/
│ ├── NavBar.vue
│ ├── LinkForm.vue
│ ├── LinkList.vue
│ ├── QRCodeModal.vue
│ └── LogoUploader.vue
├── stores/
│ └── auth.ts
├── composables/
│ └── useApi.ts
└── public/
4.2 Server API Implementation
Database Utils (server/utils/db.ts):
- Direct D1 binding access
- Query builders
- Migration helpers
Auth Utils (server/utils/auth.ts):
- JWT token generation/verification
- Session management
- Cookie handling
Email Utils (server/utils/email.ts):
- Postmark integration
- Magic link generation
- Email templates
4.3 Frontend Implementation
Pages:
index.vue: Public URL shortenerlogin.vue: Magic link requestdashboard.vue: Authenticated link management[slug].vue: Redirect handler
Components:
- Form validation
- Real-time updates
- QR code generation with logo overlay
- File upload to R2
State Management (Pinia):
- Auth store
- Links store
- UI store
Phase 5: Authentication Flow
- User enters email on login page
- Server validates email against whitelist
- Generate magic link token, store in D1
- Send email via Postmark
- User clicks link
- Verify token, create session
- Set HTTP-only cookie
- Redirect to dashboard
Phase 6: Core Features
6.1 URL Shortening
- Generate random slug or accept custom
- Validate URL format
- Check slug uniqueness
- Store in D1 with metadata
6.2 QR Code Generation
- Use qrcode library
- High error correction for logo overlay
- Return as base64 or binary
- Cache in KV for performance
6.3 Logo Upload
- Accept image upload
- Validate file type/size
- Store in R2 with unique key
- Reference in link record
6.4 Analytics
- Track clicks in D1
- Store user agent, referrer
- Display in dashboard
- Export functionality
Phase 7: Environment Configuration
7.1 Development (.env)
NUXT_JWT_SECRET=dev-secret
NUXT_EMAIL_API_KEY=your-postmark-key
NUXT_AUTHORIZED_EMAILS=email1@example.com,email2@example.com
7.2 Production Secrets
wrangler secret put JWT_SECRET
wrangler secret put EMAIL_API_KEY
wrangler secret put AUTHORIZED_EMAILS
Phase 8: Deployment
8.1 Build Process
npm run build
8.2 Deploy to Cloudflare
wrangler pages deploy .output/public
8.3 Configure Custom Domain
- Cloudflare Dashboard → Pages → Custom domains
- Add qrurl.us
- Configure DNS if needed
Phase 9: Testing & Optimization
9.1 Local Testing
npm run dev # Development server
npm run preview # Production preview
9.2 Performance Optimization
- Implement caching strategies
- Optimize database queries
- Compress assets
- Lazy load components
9.3 Security
- Rate limiting
- Input validation
- CSRF protection
- Content Security Policy
Phase 10: CI/CD Setup (Optional)
10.1 GitHub Actions
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm run build
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Common Issues & Solutions
Issue 1: EBADF Errors
- Use Node.js LTS (20.x)
- Avoid Node.js 23.x
- Check file descriptor limits
Issue 2: D1 Binding Issues
- Ensure database ID matches
- Check wrangler.toml configuration
- Verify local vs remote execution
Issue 3: CORS Problems
- Not needed with unified deployment
- Everything on same domain
Issue 4: Build Failures
- Clear .nuxt and node_modules
- Reinstall dependencies
- Check TypeScript errors
Key Differences from Framework-Heavy Approach
- Simpler Structure: Single deployment unit
- No CORS: API and frontend on same domain
- Direct Bindings: Use Cloudflare resources directly
- Better Performance: Edge-optimized
- Easier Debugging: Unified logs
Testing Checklist
- Homepage loads
- URL shortening works
- QR codes generate
- Redirects work
- Login flow completes
- Dashboard accessible
- Logo upload works
- Analytics track
- Session persistence
- Logout works
Production Checklist
- Database migrated
- Secrets configured
- Custom domain active
- SSL working
- Email sending
- Error handling
- Monitoring setup
- Backup strategy
Estimated Timeline
- Phase 1-3: 1 hour (setup)
- Phase 4-6: 4-6 hours (development)
- Phase 7-8: 1 hour (deployment)
- Phase 9-10: 2 hours (testing/optimization)
Total: 8-10 hours for complete implementation
Success Criteria
- App deploys to qrurl.us
- All features from original app work
- No framework complexity
- Fast performance (<100ms response)
- Reliable email delivery
- Secure authentication
- Clean, maintainable code