Clone
NUXT-MIGRATION-PLAN.md
Blame History Raw Edit 6.6 KB

Migrating QRurl to Nuxt 3 with NuxtFlare

Why Nuxt 3 + NuxtFlare?

Current Architecture Problems:

  • Separate frontend (Vue) and backend (Workers) codebases
  • Complex CORS configuration
  • Manual static file serving
  • Two different routing systems
  • Complicated deployment process

Nuxt 3 + NuxtFlare Benefits:

  • Single codebase - Frontend and backend unified
  • SSR on Workers - Better SEO and performance
  • Built-in API routes - /server/api/* routes
  • Direct Cloudflare bindings - D1, R2, KV without wrangler
  • Zero CORS issues - Everything on same origin
  • Simple deployment - npm run deploy
  • Hot module replacement - Full-stack HMR in development
  • TypeScript support - End-to-end type safety

Migration Strategy

Phase 1: Setup Nuxt 3 Project

# Create new Nuxt 3 app
npx nuxi@latest init qrurl-nuxt
# Install dependencies
cd qrurl-nuxt
npm install
# Add NuxtFlare module
npm install @nuxflare/core

Phase 2: Configure NuxtFlare

// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxflare/core'],
nitro: {
preset: 'cloudflare-pages',
// Cloudflare bindings
cloudflare: {
bindings: {
DB: {
type: 'd1',
databaseName: 'qrurl-db',
databaseId: '17eb6fdb-19da-4ed7-931c-a4cdef281f8c'
},
STORAGE: {
type: 'r2',
bucketName: 'qrurl-storage'
},
CACHE: {
type: 'kv',
namespaceId: '1cacb0f1b44b4324b62c1bc010ff15f5'
}
}
}
}
})

Phase 3: Migrate Components

Move Vue components to Nuxt structure:

frontend/src/components/* components/
frontend/src/views/* pages/
frontend/src/stores/* stores/ (with Pinia)

Phase 4: Convert API Routes

Transform Workers routes to Nuxt server routes:

Before (Workers):

// src/routes/api.js
export async function apiRoutes(request, env, ctx) {
if (path === '/api/links') {
return getLinks(request, env);
}
}

After (Nuxt):

// server/api/links.get.ts
export default defineEventHandler(async (event) => {
const db = useD1Database('DB', event);
return await db.prepare('SELECT * FROM links').all();
})

Phase 5: Authentication with Nuxt

// server/api/auth/request.post.ts
import { usePostmark } from '#nuxflare/postmark';
export default defineEventHandler(async (event) => {
const { email } = await readBody(event);
const postmark = usePostmark(event);
// Generate magic link
const token = generateToken();
// Store in D1
const db = useD1Database('DB', event);
await db.prepare('INSERT INTO auth_tokens...').run();
// Send email
await postmark.sendEmail({
From: 'noreply@qrurl.us',
To: email,
Subject: 'Your QRurl Login Link',
HtmlBody: `...`
});
return { success: true };
})

Phase 6: File Uploads with R2

// server/api/logo/upload.post.ts
export default defineEventHandler(async (event) => {
const form = await readFormData(event);
const file = form.get('logo') as File;
const r2 = useR2Bucket('STORAGE', event);
await r2.put(`logos/${file.name}`, file);
return { url: `/api/logo/${file.name}` };
})

File Structure (Nuxt)

qrurl-nuxt/
├── server/
├── api/
├── auth/
├── request.post.ts
└── verify.post.ts
├── links/
├── index.get.ts
├── index.post.ts
└── [id].delete.ts
└── logo/
├── upload.post.ts
└── [id].get.ts
└── routes/
└── [slug].ts # Short link redirects
├── pages/
├── index.vue # Homepage
├── dashboard.vue # Dashboard
└── auth/
└── verify.vue # Magic link verification
├── components/
├── NavBar.vue
├── CreateLinkForm.vue
├── QRCodeGenerator.vue
└── LogoUploader.vue
├── stores/
└── auth.ts # Pinia store
├── nuxt.config.ts
└── wrangler.toml # Minimal, just for secrets

Deployment (Simplified!)

Development:

npm run dev
# That's it! Full-stack hot reload

Production:

npm run build
npm run deploy
# Deploys everything to Cloudflare Workers/Pages

Benefits Over Current Architecture

Feature Current (Vue + Workers) Nuxt 3 + NuxtFlare
Codebase 2 separate 1 unified
Routing Vue Router + Workers Router Nuxt file-based routing
API Manual Workers setup Built-in server routes
SSR No (SPA only) Yes (better SEO)
Development 2 servers + CORS 1 server, no CORS
Type Safety Partial Full-stack TypeScript
Deployment Complex scripts Single command
HMR Frontend only Full-stack
Data Fetching Manual fetch + CORS $fetch with auto-typing

Migration Timeline

  1. Week 1: Set up Nuxt project, migrate components
  2. Week 2: Convert API routes to server routes
  3. Week 3: Integrate D1, R2, KV with NuxtFlare
  4. Week 4: Testing and deployment optimization

Example: Complete Link Creation Flow

<!-- pages/dashboard.vue -->
<template>
<form @submit.prevent="createLink">
<input v-model="url" />
<button type="submit">Shorten</button>
</form>
</template>
<script setup>
const url = ref('');
async function createLink() {
// No CORS, no API URL config needed!
const { slug } = await $fetch('/api/links', {
method: 'POST',
body: { url: url.value }
});
// Redirect to success page
await navigateTo(`/link/${slug}`);
}
</script>
// server/api/links.post.ts
export default defineEventHandler(async (event) => {
const { url } = await readBody(event);
const db = useD1Database('DB', event);
const slug = generateSlug();
await db.prepare(
'INSERT INTO links (slug, url) VALUES (?, ?)'
).bind(slug, url).run();
return { slug };
})

Conclusion

Migrating to Nuxt 3 with NuxtFlare would:

  • Eliminate complexity - No more CORS, separate deployments, or complex configs
  • Improve developer experience - Single codebase, full-stack HMR
  • Enhance performance - SSR, edge rendering, better caching
  • Simplify deployment - One command to deploy everything

The migration effort would be worth it for the massive simplification and improved developer experience.