9. modulA nagy döntés: full-stack vagy BFF
Nuxt for Devs · 9. modul · elágazás

A nagy döntés: full-stack Nuxt vagy külön API?

Ez a tananyag elágazása. Nem mondom meg, melyiket válaszd — végigvezetem a hét szempontot, ami tényleg dönt, megmutatom azt az egy műszaki tényezőt, ami Cloudflare-en gyakran eldönti a kérdést, és megadom a hibrid határvonalát, ami a legvalószínűbb válasz. A végén egy konkrét checklist, amit a saját helyzetedre kitöltesz.

9.1A két út — és a harmadik, ami a valóság

A · FULL-STACK NUXT böngésző Worker: SSR + server/api egy deploy, közös típusok D1 / Hyperdrive → DB + nincs hálózati ugrás − mindent át kell írni B · BFF — A MEGLÉVŐ API MARAD böngésző Worker: SSR + BFF vékony réteg, összevonás Express API AWS, egy régióban Aurora + kis kockázat − plusz hálózati ugrás C · HIBRID — A LEGVALÓSZÍNŰBB VÉGÁLLAPOT böngésző Worker: SSR + auth + új végpontok ami közel kell legyen a felhasználóhoz + összevonó BFF a maradékhoz a nehéz maradék batch, riport, natív libek + fokozatosan mozgatható − két rendszer marad
A kérdés nem az, hogy „A vagy B”, hanem hogy hol húzod meg a határt — és hogy az idővel mozog-e.

9.2A hét szempont

SzempontFull-stack Nuxt mellett szólKülön API mellett szól
Kliensek Csak webes felület, esetleg egy-két integráció Mobilapp, partner-API, több frontend — ezeknek úgyis kell egy stabil, verziózott API
Migrációs kockázat Zöldmezős rész, vagy vállalható a nagyobb átírás Nagy, működő Express-kódbázis, amit nem akarsz egyszerre mozgatni
Csapat Egy csapat viszi a frontendet és a backendet Külön backend-csapat, saját release-ciklussal
Latency Az adat is Cloudflare-en van (D1/R2/KV), vagy Hyperdrive-on át közel Az API és az adatbázis egy régióban marad — lásd a 9.3-at, ez a legfontosabb
Típusok, sebesség Végponttól a komponensig egy típuslánc, nincs duplikált szerződés OpenAPI-generált kliens is működik, csak lassabb a kör
Üzemeltetés, költség Egy deploy, egy log-forrás, egy skálázási modell Marad a megszokott üzemeltetés, nem kell két platformot tanulni
Ami nem fut Workers-en Nincs nehéz számítás, natív lib, vagy hosszú futású munka Van: PDF-generálás natív libbel, nagy batch, ML-inferencia, órákig futó job

9.3A latency-paradoxon — ez dönt Cloudflare-en

Ez a modul legfontosabb szakasza, és pont az a szempont, ami az általános „Nuxt full-stack vagy nem” vitákban nem szokott szerepelni, mert csak edge-hostingnál él.

Az SSR értelme az, hogy a szerver gyorsan össze tudja rakni a HTML-t. Ehhez adatot kér le. Ha a Worker az edge-en fut — mondjuk a felhasználóhoz közel, Budapesten —, de az API az AWS eu-central-1-ben, akkor minden egyes SSR-lekérés kimegy Frankfurtba és vissza.

Számoljunk. Tegyük fel, hogy a Budapest→Frankfurt oda-vissza út 25 ms, egy tengerentúli felhasználónál viszont 150 ms. Ha az oldalad SSR-je három API-hívást igényel sorosan, az +75 ms, illetve +450 ms a TTFB-hez — a szerveri feldolgozási időn felül. És minél távolabb van a felhasználó, annál rosszabb: a Worker ugyan közel van hozzá, de az adat nem. Az „edge” előnyt pont az adatlekérés eszi meg.

Négy válasz létezik erre, és mind a négy legitim:

VálaszMit csinálMikor jó
Összevonás Egy BFF-végpont, ami egyetlen kéréssel elhozza, amit az oldal igényel — N round trip helyett egy Szinte mindig. Ez az első lépés, olcsó és sokat hoz.
Smart Placement A Cloudflare a Workert nem a felhasználóhoz, hanem az origin közelébe helyezi Ha az SSR sokat beszélget egy távoli backenddel. Egy konfigsor.
Adat közelebb Hyperdrive az Aurora elé (kapcsolat-pooling + query cache), vagy olvasási replika Ha a Postgres marad, de a kapcsolatfelépítés a szűk keresztmetszet
Kevesebb SSR-adat A személyre szabott rész server: false-szal a kliensre kerül (5. modul) Ha a lassú lekérés nem is kell az első festéshez
A Smart Placement ellentmondásosnak tűnik, pedig logikus. Ha a Workered a kérésenkénti munkájának nagy részét egy távoli origin megkérdezésével tölti, akkor jobb, ha ő van közel az originhez, és a felhasználó felé megy egy hosszú út — mert az egy út, nem három. A Cloudflare ezt méri és automatikusan dönti el. A Cloudflare-anyag 18. modulja részletezi, mikor nem segít: ha a Worker nem beszél sokat az originnel, vagy ha a bindingokat használja (azok amúgy is közel vannak).

9.4Hol húzd meg a határt (a hibrid)

A legtöbb csapat ide köt ki. Nem kompromisszum, hanem tudatos felosztás: ami a felhasználóhoz közel értékes, az menjen a Nitróba; ami nehéz vagy nem edge-barát, maradjon kint.

Tedd a NitróbaHagyd kint (vagy más eszközben)
Session és auth (cookie-kezelés, tenant-feloldás)
Az SSR-hez kellő adat összevonása
Publikus, cache-elhető végpontok
Webhook-fogadás (gyors 200 + sorba tétel)
Feltöltési URL kiadása, R2-műveletek
Minden új végpont, ha nincs okod máshova tenni
Nagy batch-feldolgozás, éjszakai zárás
PDF/Excel generálás natív könyvtárral
ML-inferencia, képfeldolgozás sharp-pal
Percekig futó tranzakciók
Ami a meglévő API-ban jól működik és nincs okod hozzányúlni
Egy hasznos gondolatkísérlet: ha holnap ki kellene kapcsolnod az Express API-t, mi az, ami tényleg nem menne át? Ha a válasz „két-három nehéz job”, akkor a full-stack irány reális, és azt a hármat lehet Queue-ra, Workflow-ra vagy Containerre tenni. Ha a válasz „a fél rendszer”, akkor a hibrid a cél, és a kérdés csak az, hogy hol a határ.

9.5Ha BFF-et választasz: a helyes minta

Amit semmiképp ne csinálj: vak proxy. Egy server/api/[...].ts, ami mindent továbbtol az Express-nek, a legrosszabb mindkét világból: megkapod a plusz hálózati ugrást, de nem kapsz cserébe semmit — se összevonást, se típusokat, se hibafordítást. Ha csak proxyzol, akkor hívd az API-t közvetlenül a böngészőből, és spórold meg a Workert.

// server/utils/upstream.ts — egy helyen kezelt kimenő hívás
export function upstream(event: H3Event) {
  const cfg = useRuntimeConfig(event)

  return $fetch.create({
    baseURL: cfg.apiBaseUrl,
    timeout: 5000,

    onRequest({ options }) {
      // ✗ SOHA ne told tovább a felhasználó cookie-ját
      // ✓ a szerver a saját, rövid életű szolgáltatás-tokenjével hív
      options.headers.set('authorization', `Bearer ${serviceToken(cfg)}`)
      options.headers.set('x-tenant-id',   event.context.tenant.id)
      options.headers.set('x-request-id',  event.context.requestId)
    },

    onResponseError({ response }) {
      // a belső hibaformátumot fordítsuk a sajátunkra (5. modul)
      throw createError({
        statusCode:    response.status === 401 ? 403 : response.status,
        statusMessage: response._data?.message ?? 'Upstream hiba',
      })
    },
  })
}
// server/api/dashboard.get.ts — ÖSSZEVONÁS: 1 kérés a böngészőnek, párhuzamos upstream
export default defineEventHandler(async (event) => {
  await requireSession(event)
  const api = upstream(event)

  const [projects, usage, notices] = await Promise.all([
    api('/projects?limit=10'),
    api('/usage/current'),
    api('/notices'),
  ])

  // és itt formázzuk a DTO-t — a belső séma nem szivárog ki (5. modul)
  return { projects: projects.map(toProjectDto), usage: toUsageDto(usage), notices }
})
Két Workers-korlát, amivel BFF-nél számolni kell. ① Egyszerre 6 kimenő kapcsolat lehet nyitva — ha egy oldal tíz upstream hívást indít párhuzamosan, azok sorba állnak. ② A subrequestek száma is korlátos kérésenként. Mindkettő ugyanabba az irányba mutat: kevesebb, nagyobb hívás. Ez pont az összevonás, amit amúgy is akarsz.

9.6Ha full-stack Nuxtot választasz: mire figyelj

9.7Ha B-ből A felé mész: a sorrend

A biztonságos út a strangler-minta: nem cserélsz, hanem körbenősz.

  1. 1. fázis — Nuxt frontend + vékony BFF. Az Express API változatlan. Csak a session/auth kerül a Nitróba (ez amúgy is oda való, mert cookie-kezelés), plusz az összevonó végpontok. Kockázat: alacsony. Ez már önmagában érték: gyorsabb oldal, kevesebb kérés.
  2. 2. fázis — minden új végpont a Nitróban készül. Nem migrálsz, csak nem növeled tovább a régit. Itt derül ki, mennyire kényelmes a Nitro a csapatnak — élesben, valós funkción.
  3. 3. fázis — meglévő végpontok átköltöztetése domain-enként. Nem fájlonként: egy összetartozó terület (mondjuk „projektek”) egyszerre, a service-logikájával együtt. Útvonalanként lehet átkapcsolni, és vissza is.
  4. 4. fázis — az Express kikapcsolása. Csak akkor, ha tényleg üres. Nem cél, hanem lehetséges végállapot.
És a legfontosabb: a 2. fázis is lehet a végállapot. Ha ott stabil, gyors és karbantartható a rendszer, nincs okod tovább menni. A „mindent egy helyre” nem érték önmagában — a kevesebb mozgó alkatrész az, és ezt a 2. fázis már nagyrészt megadja.

9.8Döntési checklist

Válaszolj őszintén; a jelölés mutatja, merre húz a válasz.

Van vagy lesz mobilapp, partner-API vagy más frontend a következő évben? igen → BFF Egy stabil, verziózott API-nak akkor is lennie kell — akkor viszont ne duplázd a logikát.
Az Express API-d hány végpontból áll? < 30 → full-stack> 100 → BFF A közte lévő tartomány a hibridé.
Hol lesz az adatbázis egy év múlva? Cloudflare-en (D1) vagy Hyperdrive mögöttAWS-ben marad, változatlanul Ez a legerősebb műszaki jel — lásd a 9.3-at.
Van olyan funkciód, ami natív könyvtárat, hosszú futást vagy nagy memóriát igényel? igen → marad kint Legalább az a rész. Ez önmagában nem dönti el a többit.
Külön backend-csapat viszi az API-t, saját release-ütemmel? igen → BFF A szervezeti határ erősebb érv, mint a technikai elegancia.
Mennyire fáj ma, hogy a típusok elvesznek a frontend és a backend között? sokat → full-stack Ez a full-stack irány legkonkrétabb napi haszna.
Az SSR-elt oldalaid hány adatlekérést igényelnek? sokat → full-stack vagy összevonás Ha marad a távoli API, akkor az összevonás nem opció, hanem kötelező.
A leggyakoribb helyes válasz a te helyzetedben (működő Express API, AWS-adatbázis, egy csapat, multitenant SaaS): indulj BFF-ként, tervezz a hibridre. Az 1. fázis kis kockázattal ad mérhető javulást, a 2. fázis pedig élesben megmutatja, akarod-e a full-stackot — még mielőtt visszafordíthatatlan döntést hoznál.
Mi jön ezután? A 10. modul az auth és a session — az a réteg, ami mindkét úton a Nitróba kerül, tehát a döntéstől függetlenül meg kell csinálni. Sealed cookie-k, jelszókezelés Workers-en, tenant-tagság ellenőrzése, és hogy mi működik a megszokott könyvtárakból.

9.9Ellenőrizd magad

  1. Miért lehet egy edge-en futó SSR lassabb, mint egy régiós szerveren futó, ha az API távol van?
    Válasz

    Mert az SSR-hez adat kell, és minden lekérés oda-vissza utat jelent az edge és a távoli API között. Egy régiós szerver az API mellett van, tehát a lekérései gyorsak; az edge Workernek viszont a felhasználó van közel, az adat nem. Három soros lekérés × egy nagy RTT = jelentős TTFB-növekmény. Válaszok: összevonás, Smart Placement, Hyperdrive, vagy kevesebb SSR-adat.

  2. Mi a baj a „vak proxy” BFF-fel?
    Válasz

    Megkapod a plusz hálózati ugrás összes költségét, de semmit nem kapsz cserébe: se összevonást, se típusokat, se hibafordítást, se cache-t. Ha a BFF-ed csak továbbtol, akkor a böngésző hívja közvetlenül az API-t, és nem kell a Worker a láncba.

  3. Miért nem szabad a felhasználó session-cookie-ját továbbküldeni az upstream API-nak?
    Válasz

    Mert ezzel a felhasználói hitelesítő adat átkerül egy másik bizalmi zónába, és minden ottani hiba vagy naplózás kiteszi. A helyes minta: a BFF ellenőrzi a session-t, majd a saját, rövid életű szolgáltatás-tokenjével hív, és a felhasználó/tenant azonosítóját külön fejlécben adja át — így az upstream oldalon egyértelmű, hogy szolgáltatás hívja, kinek a nevében.

  4. Melyik két Workers-korlát tereli a BFF-tervezést, és milyen irányba?
    Válasz

    Az egyszerre nyitható 6 kimenő kapcsolat és a kérésenkénti subrequest-korlát. Mindkettő a kevesebb, nagyobb hívás felé terel — vagyis az összevonás felé, ami amúgy is a BFF fő értéke.

  5. Mit érdemes akkor is a Nitróba tenni, ha egyébként BFF-et választasz?
    Válasz

    A session- és auth-réteget (cookie-kezelés, tenant-feloldás), az SSR-hez való adatösszevonást, a publikus cache-elhető végpontokat, a webhook-fogadást és a feltöltési URL-ek kiadását. Ezek mind olyanok, amiknek a felhasználóhoz közel van értelme, és amiket a 10. modulban meg is csinálunk.

  6. Miért lehet a migráció 2. fázisa a végállapot?
    Válasz

    Mert ott már megkapod a fő hasznokat — új funkciók egy helyen készülnek, közös típusok, gyorsabb SSR —, miközben a működő, nehéz részek maradnak ott, ahol jól működnek. A „mindent egy helyre” nem önmagában érték; a kevesebb mozgó alkatrész az, és azt a 2. fázis nagyrészt megadja.

Előző8. modul — Server routes és h3 — Express-ből érkezve Következő 10. modul — Auth és session Nuxt-módra