nuxtcloudflareworkersd1kv · 6 min čitanja

Nuxt 3 na Cloudflare Workers: kako ispravno podesiti bindings

Kako povezati KV, D1 i R2 bindings sa Nuxt 3 server rutama na Cloudflare Workers — tipizirani env, lokalni Miniflare razvoj i greške koje ruše produkciju.
Izometrijska ilustracija užarenog serverskog bloka sa tri obojena modula za skladištenje koji su na njega priključeni snopovima svetlosti, na tamnoj mreži u pozadini.

Jedna stvar zbog koje se Workers isplati

Nuxt aplikaciju možete hostovati skoro bilo gde. Razlog da je stavite na Cloudflare Workers nije CDN — već bindings. KV namespace, D1 baza, R2 bucket ili Durable Object predati vašim server rutama kao živ objekat, bez connection stringa, bez poolinga i bez cold-start rukovanja.

Kvaka je u tome što bindings ne postoje u Node.js svetu u kome Nuxt inače radi. Oni stižu po zahtevu, zakačeni za Workers izvršni kontekst. Dovesti ih do vaših server/api handlera — i naterati ih da rade u nuxt dev — prvi je pravi deo instalacija u svakom Nuxt 3 + Workers projektu.

Podesite preset i Wrangler fajl

Nitro ima dva Cloudflare cilja koja su bitna. cloudflare_module gradi Worker sa ulaznom tačkom u stilu modula (export default { fetch }) i servira vaš statički izlaz kroz Workers Assets binding. cloudflare-pages cilja Pages Functions. Za nove projekte, birajte cloudflare_module: Workers Assets je ono u šta Cloudflare ulaže, a dobijate i pun skup Worker mogućnosti (Durable Objects, cron okidače, redove) umesto podskupa koji nudi Pages.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nitro-cloudflare-dev'],
  nitro: {
    preset: 'cloudflare_module',
  },
})
// wrangler.jsonc
{
  "name": "nuxt-cf-starter",
  "main": "./.output/server/index.mjs",
  "compatibility_date": "2025-04-01",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "./.output/public/",
    "binding": "ASSETS",
  },
  "kv_namespaces": [
    { "binding": "CACHE", "id": "3f9c...", "preview_id": "8a12..." },
  ],
  "d1_databases": [
    { "binding": "DB", "database_name": "app", "database_id": "b41e..." },
  ],
}

Dve napomene o tom fajlu. nodejs_compat je gotovo uvek neophodan — Nitro izlaz, i većina npm paketa koje ćete uvući, očekuju da postoje node:buffer, node:crypto ili node:async_hooks. I main pokazuje na rezultat build-a, pa nuxt build mora da se izvrši pre wrangler deploy. Skripta "deploy": "nuxt build && wrangler deploy" drži to pod kontrolom.

Čitanje bindings-a unutar server rute

Nitro izlaže Workers okruženje na H3 event objektu:

event.context.cloudflare.env // vaši bindings + vars + secrets
event.context.cloudflare.context // ExecutionContext (waitUntil, passThroughOnException)
event.context.cloudflare.request // sirovi Request

Umotajte taj pristup jednom, tako da svaka ruta pukne na isti način kada nešto nije dobro podešeno:

// server/utils/cloudflare.ts
import type { H3Event } from 'h3'

export function cf(event: H3Event) {
  const cloudflare = event.context.cloudflare
  if (!cloudflare) {
    throw createError({
      statusCode: 500,
      statusMessage:
        'Cloudflare bindings unavailable — is nitro-cloudflare-dev enabled?',
    })
  }
  return cloudflare
}

Keš baziran na KV-u za spor upstream API, uz waitUntil da upis ne blokira odgovor:

// server/api/rates.get.ts
export default defineEventHandler(async (event) => {
  const { env, context } = cf(event)
  const key = 'rates:usd'

  const cached = await env.CACHE.get(key, 'json')
  if (cached) {
    setHeader(event, 'x-cache', 'HIT')
    return cached
  }

  const fresh = await $fetch<{ base: string; rates: Record<string, number> }>(
    'https://api.example.com/v1/rates/usd',
  )

  context.waitUntil(
    env.CACHE.put(key, JSON.stringify(fresh), { expirationTtl: 300 }),
  )

  setHeader(event, 'x-cache', 'MISS')
  return fresh
})

I D1, koji koristi pripremljene izraze sa pozicionim vezivanjem parametara:

// server/api/posts.get.ts
interface PostRow {
  id: number
  title: string
  created_at: string
}

export default defineEventHandler(async (event) => {
  const { env } = cf(event)
  const { limit = '20' } = getQuery<{ limit?: string }>(event)

  const { results } = await env.DB.prepare(
    `select id, title, created_at
         from posts
        where published = 1
        order by created_at desc
        limit ?1`,
  )
    .bind(Math.min(Number(limit) || 20, 100))
    .all<PostRow>()

  return results
})

Ovde nikada nemojte graditi SQL nadovezivanjem stringova. D1 podržava .batch() za više izraza u jednom odlasku do baze, što je važnije nego što biste pomislili: svaki await prema D1 je mrežni skok od lokacije vašeg Worker-a do primarnog regiona baze.

Kako dobiti tipove umesto any

Wrangler generiše tipove iz vaše konfiguracije. Dodajte to u svoj dev tok:

npx wrangler types --env-interface CloudflareEnv

To upisuje worker-configuration.d.ts koji sadrži interfejs CloudflareEnv sa CACHE: KVNamespace, DB: D1Database i svakim var koji ste deklarisali. Povežite ga sa event kontekstom:

// server/types/h3.d.ts
declare module 'h3' {
  interface H3EventContext {
    cloudflare: {
      request: Request
      env: CloudflareEnv
      context: ExecutionContext
    }
  }
}

export {}

Sada env.DB.prepare() ima autodopunu, a slovna greška u imenu binding-a postaje greška pri build-u. Ponovo pokrenite wrangler types kad god izmenite wrangler.jsoncpostinstall skripta ili prefiks u dev skripti su uobičajena mesta za to.

Lokalni razvoj koji zaista koristi bindings

nitro-cloudflare-dev poziva Wrangler-ov getPlatformProxy() i ubacuje prave bindings u nuxt dev. Iza njih stoji Miniflare (isti workerd runtime koji Cloudflare pokreće u produkciji), sa stanjem koje se čuva u .wrangler/state. Dakle, lokalni KV je prava KV implementacija, a lokalni D1 je pravi SQLite fajl — ali drugačiji od produkcionog.

To znači da migracije morate primeniti dvaput:

# lokalni sqlite koji koristi nuxt dev
npx wrangler d1 migrations apply app --local

# prava baza
npx wrangler d1 migrations apply app --remote

Dodajte .wrangler u .gitignore. Kada treba da debagujete nad produkcionim podacima, wrangler dev --remote pokreće vaš izgrađeni Worker uz prave udaljene bindings — korisno, ali i vrlo dobar način da slučajno pišete u produkciju. Držite to u namenskoj, jasno imenovanoj skripti.

Kompromis kod nitro-cloudflare-dev: dodaje drugi runtime u vaš dev proces, pa je pokretanje sporije, a mali broj Vite/Nitro rubnih slučajeva ponaša se drugačije nego u Nuxt-ovom podrazumevanom Node dev serveru. Alternativa je da ga preskočite i da čuvate svaki pristup binding-u, čime menjate par sekundi pokretanja za „radi lokalno, 500 u produkciji“. Uzmite sporije pokretanje.

Secrets, vars i useRuntimeConfig

Vrednosti koje nisu tajne idu u wrangler.jsonc pod "vars". Tajne idu u wrangler secret put API_TOKEN i nikada ne dodiruju repozitorijum. Oboje završavaju na env — ne na process.env, koji na Workers-u jedva postoji.

Ako koristite runtimeConfig, ovaj detalj će vas ujesti tačno jednom:

// ✅ env override-i (NUXT_*) se primenjuju
const config = useRuntimeConfig(event)

// ❌ na Workers-u vraća samo podrazumevane vrednosti iz build-a
const config = useRuntimeConfig()

Pošto je okruženje vezano za zahtev, Nitro može da primeni override-e sa NUXT_ prefiksom samo kada vidi event. Uvek ga prosledite.

Šta ne raditi

Ne dirajte bindings na nivou modula. Ovo izgleda u redu, a pokvareno je:

// server/utils/db.ts — NEMOJTE OVO DA RADITE
const db = useCloudflareEnv().DB // nema zahteva, nema env-a

Inicijalizacija modula dešava se jednom po izolatu, pre nego što bilo koji zahtev postoji. Umesto toga, prosledite event (ili env) niz sloj za podatke. Ako koristite Drizzle, to znači da klijenta pravite po zahtevu — jeftino je, pošto nema konekcije koju treba otvarati:

export function useDb(event: H3Event) {
  return drizzle(cf(event).env.DB, { schema })
}

Ne keširajte vrednosti vezane za zahtev u promenljivama na nivou modula. Izolati se ponovo koriste između zahteva različitih korisnika. let currentUser na nivou modula je curenje podataka.

Ne posežite za KV-om kao za bazom. KV je eventually consistent — upisu može trebati do oko minut da postane vidljiv svuda, a čitanja se keširaju na edge-u sa minimalnim TTL-om od 60 sekundi. Odličan je za konfiguraciju, feature flag-ove, sesije i keširanje renderovanih fragmenata. Pogrešan je za bilo šta gde čitate odmah nakon upisa. Za to postoji D1 (relaciona, jedan primarni region, opcione read replike) ili Durable Object (jaka konzistentnost, jedna nit po instanci).

Kontrolna lista koja radi

  1. nitro.preset = 'cloudflare_module', uključen nodejs_compat, deklarisan assets binding.
  2. Bindings deklarisani u wrangler.jsonc; wrangler types pokrenut i uključen u korak provere tipova.
  3. nitro-cloudflare-dev u modules, .wrangler u gitignore-u.
  4. Jedan cf(event) helper; nikakav pristup binding-ima na nivou modula.
  5. Odvojene --local i --remote skripte za migracije.
  6. useRuntimeConfig(event) svuda, uvek sa event objektom.

Uradite tih šest stvari kako treba i ostatak Workers platforme — redovi, cron okidači, Durable Objects, R2 — samo je još jedan unos u istom konfiguracionom fajlu i još jedno svojstvo na istom env objektu.

Poslednja izmena: