Deploy Nuxt Ask AI to Cloudflare Pages with Vectorize and Workers AI for a fully serverless, edge-based ASK ai experience.
Cloudflare Workers deployment uses:
Build Time (Local or CI):
1. Generate embeddings locally (transformers.js or OpenAI)
2. Build static site with Nuxt
3. Output embeddings.jsonl for Vectorize upload
Runtime (Cloudflare Workers):
1. User queries via /api/search or /api/chat
2. Workers AI generates query embedding
3. Vectorize searches similar vectors
4. (Chat API) Workers AI generates RAG response
5. Return results to user
npm install -g wrangler
wrangler login
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-ai-search'],
nitro: {
preset: 'cloudflare-pages',
},
aiSearch: {
embeddings: {
model: 'bge-base-en-v1.5',
dimensions: 768,
// Build time: Use local provider
buildProvider: {
preset: 'transformers.js'
},
// Runtime: Use Workers AI
runtimeProvider: {
preset: 'workers-ai'
},
// Use Cloudflare Vectorize for vector storage
vectorDatabase: {
provider: 'cloudflare-vectorize',
indexName: 'ai-search',
metric: 'cosine',
},
},
// LLM for RAG (chat API)
llm: {
provider: 'workers-ai',
model: '@cf/meta/llama-3.1-8b-instruct',
},
// Enable all endpoints
search: { enabled: true },
chat: { enabled: true },
},
})
Create wrangler.toml in your project root:
name = "my-nuxt-site"
compatibility_date = "2025-09-16"
# Required for MCP SDK compatibility
compatibility_flags = ["nodejs_compat"]
# Workers AI binding
[ai]
binding = "AI"
# Vectorize binding
[[vectorize]]
binding = "VECTORIZE"
index_name = "ai-search"
Important: The nodejs_compat flag is required if you enable the MCP protocol.
pnpm generate
# or
npm run generate
This creates:
.output/public/ - Static site for deployment.data/ai-search/embeddings.jsonl - Vector embeddings.output/public/_ai-search/bulk - Bulk data (static file)Create the vector index on Cloudflare:
npx wrangler vectorize create ai-search \
--dimensions=384 \
--metric=cosine
Dimensions must match your embedding model:
Xenova/all-MiniLM-L6-v2: 384text-embedding-3-small: 1536@cf/baai/bge-base-en-v1.5: 768Upload your embeddings to Vectorize:
npx wrangler vectorize insert ai-search \
--file=.data/ai-search/embeddings.jsonl \
--remote
For local testing, use --local instead of --remote.
npx wrangler pages deploy .output/public \
--project-name=my-nuxt-site
Your site is now live! Cloudflare provides a *.pages.dev URL.
Automate deployments with GitHub Actions:
# .github/workflows/deploy-cloudflare.yaml
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Build site
run: pnpm generate
env:
# For build-time embeddings (if using OpenAI)
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Setup Vectorize
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
# Create index if it doesn't exist
npx wrangler vectorize create ai-search \
--dimensions=384 \
--metric=cosine || true
# Upload embeddings
npx wrangler vectorize insert ai-search \
--file=.data/ai-search/embeddings.jsonl \
--remote
- name: Deploy to Cloudflare Pages
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
npx wrangler pages deploy .output/public \
--project-name=my-nuxt-site
Add these secrets to your GitHub repository:
CLOUDFLARE_API_TOKEN: Your Cloudflare API token
CLOUDFLARE_ACCOUNT_ID: Your account ID
OPENAI_API_KEY: (Optional) If using OpenAI for build-time embeddingsUse local providers during development:
// nuxt.config.ts
export default defineNuxtConfig({
aiSearch: {
embeddings: {
provider: 'transformers.js', // Local, no API calls
vectorDatabase: {
provider: 'sqlite-vec', // Local SQLite
},
},
},
})
Test with actual Cloudflare bindings:
# Build first
pnpm generate
# Create local Vectorize index
npx wrangler vectorize create ai-search-local \
--dimensions=384 --metric=cosine
# Upload embeddings locally
npx wrangler vectorize insert ai-search-local \
--file=.data/ai-search/embeddings.jsonl \
--local
# Start local dev server
npx wrangler pages dev .output/public \
--binding AI=ai \
--vectorize VECTORIZE=ai-search-local
Access at http://localhost:8788
Available via workers-ai provider:
| Model | Dimensions | Use Case |
|---|---|---|
@cf/baai/bge-base-en-v1.5 | 768 | General purpose (recommended) |
@cf/baai/bge-small-en-v1.5 | 384 | Faster, smaller |
@cf/baai/bge-large-en-v1.5 | 1024 | Higher quality |
For the llm configuration:
| Model | Size | Use Case |
|---|---|---|
@cf/meta/llama-3.1-8b-instruct | 8B | Recommended for Ask API |
@cf/meta/llama-3.2-3b-instruct | 3B | Faster, lighter |
@cf/mistral/mistral-7b-instruct-v0.1 | 7B | Alternative quality |
Note: Small models (<7B) may not perform well with query rewriting. Set queryRewriting.enabled: false for models <7B.
Free Tier:
Paid:
Example Cost:
Free Tier:
Paid Plans:
For production quality and speed:
// nuxt.config.ts
aiSearch: {
embeddings: {
model: 'text-embedding-3-small',
dimensions: 1536,
buildProvider: {
preset: 'openai', // Fast, reliable build
apiKey: process.env.OPENAI_API_KEY
},
runtimeProvider: {
preset: 'workers-ai' // Free runtime inference
}
},
llm: {
provider: 'workers-ai',
model: '@cf/meta/llama-3.1-8b-instruct',
},
}
Cloudflare automatically caches static assets. Bulk JSONL is served as a static file with no runtime overhead.
Track your Vectorize and Workers AI usage in the Cloudflare dashboard:
// nuxt.config.ts
const isDev = process.env.NODE_ENV === 'development'
export default defineNuxtConfig({
aiSearch: {
embeddings: {
model: 'bge-base-en-v1.5',
buildProvider: {
preset: 'transformers.js'
},
runtimeProvider: {
preset: isDev ? 'transformers.js' : 'workers-ai'
},
vectorDatabase: {
provider: isDev ? 'sqlite-vec' : 'cloudflare-vectorize',
indexName: isDev ? undefined : 'ai-search'
},
},
},
})
"Local DB provider configured with Cloudflare preset"
sqlite-vec with cloudflare-pages presetembeddings.vectorDatabase.provider to 'cloudflare-vectorize'"Could not load workers-ai-provider"
pnpm add workers-ai-provider"Cannot read property 'VECTORIZE' of undefined"
wrangler.toml"AI binding not found"
[ai] section to wrangler.toml"nodejs_compat flag required"
compatibility_flags = ["nodejs_compat"] to wrangler.toml"Vector dimension mismatch"
embeddings.dimensions matches Vectorize index:
# Delete old index
npx wrangler vectorize delete ai-search
npx wrangler vectorize create ai-search --dimensions=1536 --metric=cosine
npx wrangler vectorize insert ai-search --file=.data/ai-search/embeddings.jsonl --remote
## Migration from Other Platforms
### From Vercel/Netlify
1. Change Nitro preset to `'cloudflare-pages'`
2. Update vector DB provider to `'cloudflare-vectorize'`
3. Add `wrangler.toml` configuration
4. Follow deployment steps above
### From sqlite-vec (Local)
```bash
# 1. Update config
# Change embeddings.vectorDatabase.provider to 'cloudflare-vectorize'
# 2. Rebuild
pnpm generate
# 3. Create Vectorize index
npx wrangler vectorize create ai-search --dimensions=384 --metric=cosine
# 4. Upload embeddings
npx wrangler vectorize insert ai-search \
--file=.data/ai-search/embeddings.jsonl --remote
# 5. Deploy
npx wrangler pages deploy .output/public
Configure custom domains in Cloudflare Pages dashboard:
Set runtime environment variables:
npx wrangler pages deployment create \
--project-name=my-nuxt-site \
--branch=production \
--commit-hash=$(git rev-parse HEAD) \
--commit-message="Deploy with env vars" \
--env-var KEY=VALUE
Use different Vectorize indexes per environment:
// nuxt.config.ts
const indexName = process.env.NODE_ENV === 'production'
? 'ai-search-prod'
: 'ai-search-dev'
export default defineNuxtConfig({
aiSearch: {
embeddings: {
model: 'bge-base-en-v1.5',
buildProvider: { preset: 'transformers.js' },
runtimeProvider: { preset: 'workers-ai' },
vectorDatabase: {
provider: 'cloudflare-vectorize',
indexName,
},
},
},
})
Use the automated test script to validate your deployment:
# From repo root
pnpm test:cloudflare
# With cleanup (deletes test resources)
pnpm test:cloudflare --cleanup
This script: