NuxtHub Migration
When to Use
Activate this skill when:
- •User mentions NuxtHub Admin deprecation or sunset (Dec 31, 2025)
- •Project uses
.github/workflows/nuxthub.ymlor NuxtHub GitHub Action - •User wants to self-host NuxtHub on Cloudflare Workers
- •User asks about migrating to v0.10 or nightly version
- •Project has
NUXT_HUB_PROJECT_KEYorNUXT_HUB_PROJECT_DEPLOY_TOKENenv vars
Two-phase migration. Phase 1 is stable and recommended. Phase 2 is multi-cloud.
Phase 1: Self-Hosting (v0.9.X) - RECOMMENDED
Migrate from NuxtHub Admin / GitHub Actions to self-hosted Cloudflare Workers. No code changes required.
1.1 Remove GitHub Action Deployment
Delete .github/workflows/nuxthub.yml or any NuxtHub-specific GitHub Action. Workers CI (step 1.4) replaces this.
Remove deprecated env vars from CI/CD and .env:
- •
NUXT_HUB_PROJECT_KEY - •
NUXT_HUB_PROJECT_DEPLOY_TOKEN
Also remove any GitHub Actions secrets related to NuxtHub deployment.
Check and clean up Cloudflare Worker secrets:
npx wrangler secret list --name <worker-name> npx wrangler secret delete NUXT_HUB_PROJECT_DEPLOY_TOKEN --name <worker-name>
1.2 Get or Create Cloudflare Resources
NuxtHub Admin already created resources in your Cloudflare account. Reuse them to preserve existing data.
List existing resources:
npx wrangler d1 list # Find existing D1 databases npx wrangler kv namespace list # Find existing KV namespaces npx wrangler r2 bucket list # Find existing R2 buckets
Look for resources named after your project. Use their IDs in wrangler.jsonc.
Only create new resources if none exist:
# D1 Database (if hub.database: true) npx wrangler d1 create my-app-db # KV Namespace (if hub.kv: true) npx wrangler kv namespace create KV # KV Namespace for cache (if hub.cache: true) npx wrangler kv namespace create CACHE # R2 Bucket (if hub.blob: true) npx wrangler r2 bucket create my-app-bucket
1.3 Create wrangler.jsonc
Create wrangler.jsonc in project root. See references/wrangler-templates.md for full examples.
Minimal example with database:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-app",
"main": "dist/server/index.mjs",
"assets": { "directory": "dist/public" },
"compatibility_date": "2025-12-01",
"compatibility_flags": ["nodejs_compat"],
"d1_databases": [{ "binding": "DB", "database_name": "my-app-db", "database_id": "<from-wrangler-output>" }]
}
Note: Nuxt cloudflare-module preset outputs to
dist/, not.output/.
Required binding names:
| Feature | Binding | Type |
|---|---|---|
| Database | DB | D1 |
| KV | KV | KV Namespace |
| Cache | CACHE | KV Namespace |
| Blob | BLOB | R2 Bucket |
1.4 Set Up Workers Builds CI/CD
Ensure nuxt.config.ts uses the cloudflare_module preset:
nitro: { preset: 'cloudflare_module' }
In Cloudflare Dashboard:
- •Workers & Pages → Create → Import from Git
- •Connect GitHub/GitLab repository
- •Configure build settings (both fields required):
- •Build command:
pnpm build(ornpm run build) - •Deploy command:
npx wrangler deploy
- •Build command:
- •Add environment variables (e.g., secrets, API keys)
Common mistake: Only setting deploy command. Build must run first to generate
.output/.
1.5 Configure Environment Variables (Optional)
For advanced features (blob presigned URLs, cache DevTools, AI):
NUXT_HUB_CLOUDFLARE_ACCOUNT_ID=<account-id> NUXT_HUB_CLOUDFLARE_API_TOKEN=<token-with-appropriate-permissions> # Feature-specific (as needed): NUXT_HUB_CLOUDFLARE_BUCKET_ID=<bucket-id> NUXT_HUB_CLOUDFLARE_CACHE_NAMESPACE_ID=<namespace-id>
1.6 Test Remote Development
npx nuxt dev --remote
Phase 1 Checklist
- • Delete
.github/workflows/nuxthub.yml - • Remove
NUXT_HUB_PROJECT_KEYandNUXT_HUB_PROJECT_DEPLOY_TOKENenv vars - • Clean up old Worker secrets (
wrangler secret list/delete) - • Get existing or create new Cloudflare resources (D1, KV, R2 as needed)
- • Create
wrangler.jsoncwith bindings - • Set
nitro.preset: 'cloudflare_module'in nuxt.config.ts - • Connect repo to Cloudflare Workers Builds
- • Test with
npx nuxt dev --remote
No code changes required. Keep hub.database: true, server/database/, hubDatabase(), and @nuxthub/core.
Phase 2: v0.10/Nightly - MULTI-CLOUD
Multi-cloud support (Cloudflare, Vercel, Deno, Netlify). Breaking changes from v0.9.X.
2.1 Update Package
pnpm remove @nuxthub/core pnpm add @nuxthub/core-nightly
2.2 Update nuxt.config.ts
Before (v0.9.X):
hub: { database: true, kv: true, blob: true, cache: true }
After (v0.10):
hub: { db: 'sqlite', kv: true, blob: true, cache: true }
Key change: database: true → db: '<dialect>' (sqlite | postgresql | mysql)
2.3 Rename Database Directory
mv server/database server/db
Update imports: ~/server/database/ → ~/server/db/
Migrations generated via npx nuxt db generate go to server/db/migrations/{dialect}/.
2.4 Migrate Database API (hubDatabase → Drizzle)
Before:
const db = hubDatabase()
const users = await db.prepare('SELECT * FROM users').all()
After:
import { db, schema } from 'hub:db'
// Note: db and schema are auto-imported on server-side
const users = await db.select().from(schema.users)
2.5 Migrate KV API (hubKV → kv)
Before:
import { hubKV } from '#hub/server'
await hubKV().set('vue', { year: 2014 })
await hubKV().get('vue')
await hubKV().has('vue')
await hubKV().del('vue')
await hubKV().keys('vue:')
await hubKV().clear('vue:')
After:
import { kv } from 'hub:kv'
// Note: kv is auto-imported on server-side
await kv.set('vue', { year: 2014 })
await kv.get('vue')
await kv.has('vue')
await kv.del('vue')
await kv.keys('vue:')
await kv.clear('vue:')
Key change: hubKV() function call → kv direct object. Same methods, different access pattern.
2.6 Migrate Blob API (hubBlob → blob)
Before:
const blob = hubBlob()
await blob.put('file.txt', body, { contentType: 'text/plain' })
await blob.get('file.txt')
await blob.list({ prefix: 'uploads/' })
await blob.del('file.txt')
await blob.serve(event, 'file.txt')
After:
import { blob } from 'hub:blob'
// Note: blob is auto-imported on server-side
await blob.put('file.txt', body, { contentType: 'text/plain' })
await blob.get('file.txt')
await blob.list({ prefix: 'uploads/' })
await blob.del('file.txt')
await blob.serve(event, 'file.txt')
Key change: hubBlob() function call → blob direct object. Same methods, different access pattern.
2.7 New Import Pattern (Summary)
v0.10 uses virtual module imports. All are auto-imported on server-side:
import { db, schema } from 'hub:db' // Database
import { kv } from 'hub:kv' // KV Storage
import { blob } from 'hub:blob' // Blob Storage
2.8 CLI Commands
npx nuxt db generate # Generate migrations from schema npx nuxt db migrate # Apply migrations npx nuxt db mark-as-migrated [NAME] # Mark migration as applied without running npx nuxt db drop <TABLE> # Drop a table npx nuxt db sql [QUERY] # Execute SQL query npx nuxt db sql < dump.sql # Execute SQL from file # All commands support: --cwd <dir> # Run in different directory --dotenv <file> # Use different .env file -v, --verbose # Verbose output
2.9 Provider-Specific Setup (Non-Cloudflare)
Database Providers
PostgreSQL:
pnpm add drizzle-orm drizzle-kit postgres @electric-sql/pglite
- •Uses PGlite locally if no env vars set
- •Uses postgres-js if
DATABASE_URL,POSTGRES_URL, orPOSTGRESQL_URLset
MySQL:
pnpm add drizzle-orm drizzle-kit mysql2
- •Requires
DATABASE_URLorMYSQL_URLenv var
SQLite (Turso):
pnpm add drizzle-orm drizzle-kit @libsql/client
- •Uses libsql locally at
.data/db/sqlite.db - •Uses Turso if
TURSO_DATABASE_URLandTURSO_AUTH_TOKENset
KV Providers
| Provider | Package | Env Vars |
|---|---|---|
| Upstash | @upstash/redis | UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN |
| Redis | ioredis | REDIS_URL |
| Cloudflare KV | - | KV binding in wrangler.jsonc |
| Deno KV | - | Auto on Deno Deploy |
| Vercel | - | KV_REST_API_URL, KV_REST_API_TOKEN |
Blob Providers
| Provider | Package | Config |
|---|---|---|
| Vercel Blob | @vercel/blob | Dashboard setup |
| Cloudflare R2 | - | BLOB binding in wrangler.jsonc |
| S3 | aws4fetch | S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET, S3_REGION |
| Netlify Blobs | @netlify/blobs | NETLIFY_BLOB_STORE_NAME |
2.10 Database Hooks (For Nuxt Modules)
// Extend schema
nuxt.hook('hub:db:schema:extend', async ({ dialect, paths }) => {
paths.push(await resolvePath(`./schema/pages.${dialect}`))
})
// Add migration directories
nuxt.hook('hub:db:migrations:dirs', (dirs) => {
dirs.push(resolve('./db-migrations'))
})
// Post-migration queries (must be idempotent)
nuxt.hook('hub:db:queries:paths', (paths, dialect) => {
paths.push(resolve(`./db-queries/seed.${dialect}.sql`))
})
2.11 Schema Files
Schema can be in multiple locations:
- •
server/db/schema.ts - •
server/db/schema.{dialect}.ts - •
server/db/schema/*.ts - •
server/db/schema/*.{dialect}.ts
Generated schema at .nuxt/hub/db/schema.mjs.
Deprecated Features (v0.10)
Cloudflare-specific features removed:
- •
hubAI()- Use AI SDK with Workers AI Provider - •
hubBrowser()- Puppeteer - •
hubVectorize()- Vectorize - •
hubAutoRAG()- AutoRAG
Phase 2 Checklist
- • Complete Phase 1 first
- • Replace
@nuxthub/corewith@nuxthub/core-nightly - • Change
hub.database: truetohub.db: 'sqlite'(or other dialect) - • Rename
server/database/toserver/db/ - • Update imports from
~/server/database/to~/server/db/ - • Migrate
hubDatabase()→dbfromhub:db - • Migrate
hubKV()→kvfromhub:kv - • Migrate
hubBlob()→blobfromhub:blob - • Update table references:
tables.X→schema.X - • Run
npx nuxt db generateto generate migrations - • Test all database, KV, and blob operations
Quick Reference
| Aspect | v0.9.X (Phase 1) | v0.10/Nightly (Phase 2) |
|---|---|---|
| Package | @nuxthub/core | @nuxthub/core-nightly |
| Database config | hub.database: true | hub.db: 'sqlite' |
| Directory | server/database/ | server/db/ |
| DB access | hubDatabase() | db from hub:db |
| Schema access | N/A | schema from hub:db |
| KV access | hubKV() | kv from hub:kv |
| Blob access | hubBlob() | blob from hub:blob |
| Migrations | Manual SQL | npx nuxt db generate |
| Cloud support | Cloudflare only | Multi-cloud |
Resources
- •Self-hosting changelog
- •Deploy docs
- •v0.10 Installation
- •v0.10 Database
- •v0.10 KV
- •v0.10 Blob
- •Legacy v0.9 docs
- •
references/wrangler-templates.md- Cloudflare wrangler.jsonc templates