21. modulMigrációs recept: Vue SPA + Express → Nuxt
Nuxt for Devs · 21. modul

Migrációs recept: Vue SPA + Express → Nuxt a Cloudflare-en

Az 1. modul azzal kezdődött, hogy miért érdemes. Ez a modul arról szól, hogyan — úgy, hogy közben az alkalmazás egy napig sem áll le, és minden lépés külön visszavonható. A recept magja egy Cloudflare-specifikus trükk: a Worker kapuként áll a régi rendszer elé, és útvonalanként veszi át tőle a munkát.

21.1A vasszabály: nincs big-bang

A „félreteszünk hat hetet, újraírjuk, aztán átkapcsolunk” terv azért csábító, mert egyszerűnek hangzik. A gyakorlatban két dolog történik vele: a hat hétből öt hónap lesz, és mivel közben a régi rendszer is fejlődik, folyamatosan utol kell érned egy mozgó célt. Az átkapcsolás napja pedig egyetlen, hatalmas, visszavonhatatlan kockázat.

A működő alternatíva a fojtófüge-minta: az új rendszer körbenövi a régit, útvonalanként átveszi a funkciókat, és amikor az utolsó is átkerült, a régi egyszerűen kikapcsolható. Minden lépés kicsi, mérhető, és önmagában visszafordítható.

Cloudflare-en ez a minta különösen olcsó, mert nem kell hozzá külön reverse proxyt üzemeltetni. A Worker eleve ott ül a domain előtt — csak azt kell megmondani neki, mit tartson meg magának, és mit adjon tovább.

21.2A leltár: hat kérdés indulás előtt

Mielőtt egy sort írnál, ezekre kell írásos válasz. Nem azért, mert szeretjük a dokumentációt, hanem mert mindegyik megváltoztathatja a migráció alakját.

KérdésMiért dönt
Hány végpont van, és mennyi forgalmat kapnak?a hosszú farok gyakran migrálatlanul hagyható — arányos ráfordítás
Mi tart életben állapotot a szerveren?session, WebSocket, memóriabeli cache — ezek Workers-en máshogy mennek (19. modul)
Hol van az adatbázis, és elérhető-e a nyilvános hálózatról?Hyperdrive vs. Tunnel vs. adatbázis-költözés (4. modul)
Van-e natív függőség vagy fájlfeldolgozás?ami nem megy Workers-en, az marad — és ez rendben van (21.9)
Hogyan működik ma a hitelesítés?ez lesz a legnehezebb rész a párhuzamos üzemben (21.7)
Mi a legfontosabb három üzleti útvonal?ezeket utoljára migrálod, nem elsőként
A hatodik sor ellentmond az ösztönnek. Sokan a legfontosabb funkcióval kezdenék, „hogy lássuk az értéket”. Fordítva helyes: az első migrált útvonal a tanulópénz — ott fogsz belefutni a build-lánc, a session, a cache és a deploy összes meglepetésébe. Ezt egy alacsony forgalmú, alacsony kockázatú oldalon akarod megtenni. Egy súgóoldal, egy kapcsolat-űrlap, egy statikus marketinglap tökéletes első jelölt.

21.3A fojtófüge Cloudflare-en: a Worker mint kapu

látogató bolt.hu A WORKER (Nuxt) ① statikus asset? → kiszolgál ② már migrált útvonal? → Nuxt SSR / server route ③ minden más: → proxy a régi rendszerhez a ②-es lista hétről hétre nő ÚJ: Nuxt server routes + bindingok D1 / Hyperdrive · KV · R2 · Queues amit már átvettél RÉGI: Express-alkalmazás a saját szerverén, változatlanul a lista fogy, míg üres nem lesz
A Worker az első naptól a domain előtt áll, de kezdetben szinte mindent továbbenged. A ②-es lista minden migrált útvonallal hosszabb, a ③-as forgalom kevesebb.

A proxyzás egyetlen h3-segédfüggvény (8. modul), egy szerver-middleware-ben:

// server/middleware/99-legacy-proxy.ts — a NÉV SZÁMÍT: fusson utolsóként
const MAR_MIENK = [
  '/sugo',
  '/kapcsolat',
  '/api/termekek',
  // ide kerül minden átvett útvonal — ez a migráció mérőszáma
]

export default defineEventHandler(async (event) => {
  const url = getRequestURL(event)

  // már a miénk → engedd tovább a Nuxtnak (a middleware nem tér vissza semmivel)
  if (MAR_MIENK.some(p => url.pathname === p || url.pathname.startsWith(p + '/'))) return

  // minden más → a régi rendszer
  const { legacyOrigin } = useRuntimeConfig(event)
  return proxyRequest(event, legacyOrigin + url.pathname + url.search, {
    headers: {
      'X-Forwarded-Host': url.host,
      'X-Forwarded-Proto': 'https',
    },
  })
})
Emlékeztető a 8.6-ból: a szerver-middleware akkor „engedi tovább” a kérést, ha nem tér vissza semmivel. Ha bármit visszaadsz — akár egy proxyRequest eredményét —, az lesz a válasz. Ez a mechanizmus teszi ezt a tizenöt sort működőképessé.
Négy dolog, amit a proxy hoz magával. (1) Minden át nem vett kérés is meghívja a Workert, tehát számlázódik — a statikus assetek viszont csak akkor ingyenesek, ha már a te .output/public/-odból jönnek (17.1). (2) A proxyzott kérés alhívásnak számít, és a 6 egyidejű kimenő kapcsolat rá is vonatkozik (19.5). (3) A késleltetés nő: a látogató → edge → régi origin lánc egy ugrással hosszabb. (4) Ha a régi rendszer nem érhető el a nyilvános internetről, kell egy Cloudflare Tunnel, mert a Workerből nincs privát hálózati elérés.

21.4Öt fázis, mindegyik külön visszavonható

FázisMi történikVisszavonás
0 · Kapua Nuxt-váz felállítva, a Worker mindent proxyza Cloudflare route törlése
1 · Első útvonalegy alacsony kockázatú oldal átvéve, SSR-relegy sor törlése a MAR_MIENK listából
2 · Olvasó felületa listázó és részletező oldalak átvéve; írás még a réginugyanaz, útvonalanként
3 · Auth és írássession-híd, majd a mutáló végpontok átvételea session-híd kétirányú marad, amíg biztos nem vagy
4 · Adatrétega Nuxt közvetlenül éri el az adatbázist, nem a régi API-n áta régi API-ra visszakapcsoló feature flag
5 · Leállítása MAR_MIENK „minden”, a proxy törölhető
A második oszlop a modul lényege: a visszavonás minden fázisban egy sor kód vagy egy konfigurációs kapcsoló, nem újratelepítés. Ha ez nem igaz egy lépésre, akkor a lépés túl nagy — bontsd ketté.

21.5A Vue SPA átemelése

Jó hír, hogy a komponensek nagy része szó szerint átmásolható. A rossz hír, hogy pont a keretrendszer-szintű dolgok nem — és azok vannak mindenütt.

Ami voltNuxtbanMunka
.vue komponensekugyanazmásolás, az importok törölhetők (auto-import)
vue-router konfigurációfájlrendszer-alapú útvonalakátstrukturálás app/pages/ alá (4. modul)
Route guardokmiddleware/újraírás — és eldönteni, védelem-e vagy csak UX (4.4)
Pinia storePinia vagy useStatea modul-szintű állapot átgondolása (6. modul)
axios-hívásokuseFetch / $fetchnem csak csere: SSR-tudatosság kell (5. modul)
import.meta.env.VITE_*runtimeConfigés eldönteni, mi publikus (12. modul)
Böngésző-API a setupban.client.vue vagy onMountedhidratálási eltérés forrása (3. és 6. modul)
index.html meta-tagekuseSeoMetaés most már SSR-ben is látszanak (20.2)
A negyedik és hetedik sor a valódi munka. Egy SPA-ban minden a böngészőben fut, ezért senki nem gondolt arra, hogy a store betöltéskor localStorage-ból olvas, vagy hogy egy komponens window.innerWidth-et néz a setupban. Ezek SPA-ként hibátlanul működtek; SSR alatt vagy elhasalnak, vagy — ami rosszabb — hidratálási eltérést okoznak, ami csak néha, csak bizonyos oldalakon látszik. Az első migrált útvonalon ez lesz a legnagyobb meglepetés; a 16.5-ös E2E-tippel (bukj el minden hydration-warningon) rendszerezhetővé válik.

21.6Az Express-réteg átköltöztetése

A 8. modulban végigmentünk a leképezésen: req.paramsgetRouterParam, req.querygetQuery, res.json → egyszerű return, és így tovább. A migráció szempontjából három dolog fontos ezen felül:

A sorrend átértelmeződik

Expressben a middleware-ek regisztrálási sorrendben futnak, és a next() hívása dönt. A Nitro szerver-middleware-jei fájlnév szerint ábécésorrendben futnak, és a „továbbengedés” azt jelenti, hogy nem térsz vissza semmivel. Ezért kezdődik a proxy fájlneve 99--cel: hogy garantáltan utolsóként fusson, miután az auth- és tenant-middleware-ek már elvégezték a dolgukat.

Amit nem tudsz átvinni

Express-mintaMiért nem megyHelyette
express-session + memóriatárnincs folyamat, ami tartsalezárt süti-session (10. modul)
multer lemezre írássalnincs valódi lemez (19.2)R2 közvetlenül (8. modul)
hosszú futású routeCPU-limitQueues / Workflows
node-cron a folyamatbana Worker nem él a kérések közöttCron Triggers
WebSocket-szervernincs tartós kapcsolat a WorkerbenDurable Object
natív modul (sharp, bcrypt)C++ addon19.9 helyettesítő tábla

Az API-szerződés megőrzése

Migráció közben ne javíts API-t. A csábítás nagy: „ha már átírom, legyen szebb a válaszformátum”. Ne. Amíg a régi frontend egy része még él, minden formátumváltozás két rendszert tör el egyszerre, és a hibakeresés során nem tudod majd eldönteni, a migráció vagy a „javítás” okozta. A szépítés önálló lépés, a migráció után. Ha egy végpont formátuma tényleg tarthatatlan, versionözd (/api/v2/…), és a régit hagyd békén.

21.7A session-híd — a legnehezebb rész

A párhuzamos üzem egyetlen igazán nehéz problémája ez: a felhasználó bejelentkezett a régi rendszeren, és most egy új útvonalra navigál. Az új rendszernek fel kell ismernie. Fordítva ugyanígy.

Három megközelítés van, egyre növekvő tisztasággal:

MegoldásHogyanMérleg
① Session-tár olvasásaa Worker ugyanabból a session-táblából olvas, mint az Expressnincs kettős bejelentkezés; kell hozzá adatbázis-elérés
② Ellenőrző végponta Worker meghív egy régi végpontot, ami validálja a sütitegyszerű, de kérésenként egy alhívás
③ Kettős sütibejelentkezéskor mindkét formátum kiadvaa legtöbb hibalehetőség; kerülendő

Az ①-es a helyes választás, ha a session-tár adatbázis (Postgres, Redis). A minta, ami ezt elegánssá teszi, a felolvasáskori átemelés: az első alkalommal, amikor a felhasználó egy migrált útvonalra téved, a régi session alapján kiállítunk neki egy újat. Onnantól a gyors, kriptográfiai úton ellenőrzött süti-session él, adatbázis-hívás nélkül.

// server/utils/session-hid.ts
export async function regiSessionFeloldas(event: H3Event) {
  const nyers = getCookie(event, 'connect.sid')
  if (!nyers) return null

  const sid = sidKibont(nyers)               // s:<id>.<aláírás> → id
  if (!alairasErvenyes(nyers)) return null   // a régi titokkal ellenőrizve

  const db = useDb(event)
  const [sor] = await db.query(
    'SELECT sess FROM session WHERE sid = $1 AND expire > now()', [sid],
  )
  return sor?.sess?.passport?.user ?? null
}
// server/middleware/10-auth.ts — fut a proxy ELŐTT (10 < 99)
export default defineEventHandler(async (event) => {
  const munkamenet = await getUserSession(event)
  if (munkamenet?.user) return                // már az új rendszerben van

  const regiUser = await regiSessionFeloldas(event)
  if (!regiUser) return                        // nincs bejelentkezve — rendben

  // ÁTEMELÉS: mostantól az új session él, DB-hívás nélkül
  await setUserSession(event, {
    user: { id: regiUser.id, email: regiUser.email, tenantId: regiUser.tenant_id },
    atemeltRegiSessionbol: true,
  })
})
Miért ez a legjobb minta? Mert magától lejár. Ahogy telnek a hetek, egyre kevesebb felhasználónak van már csak régi session-je; a híd forgalma monoton csökken. Amikor a régi session-ek természetes lejárati ideje letelt az utolsó bejelentkezés óta, a regiSessionFeloldas egyszerűen törölhető — nincs migrációs szkript, nincs kényszerített kijelentkeztetés. Érdemes az atemeltRegiSessionbol mezőt naplózni: amikor a száma nullára megy, tudod, hogy a híd elbontható.
Amire figyelj: a süti Domain és Path attribútumainak illeszkedniük kell, különben a két rendszer nem látja egymás sütijét. Ha a régi app bolt.hu-n van és az új is, ez adott — ha viszont valaha uj.bolt.hu-n futtatnád az újat, a sütit .bolt.hu domainre kell kiadni. És ne feledd a 10. modul biztonsági alapjait: a híd olvasáskor ellenőrizze a régi aláírást is, különben egy találgatott sid elég lenne a belépéshez.

21.8Az adatréteg: mit viszel, mit hagysz

A leggyakoribb migrációs túlvállalás az, hogy az adatbázist is lecserélik közben. Ne. A 4. és 11. modul döntési fája szerint:

Jelenlegi állapotMigráció alattUtána megfontolandó
Postgres, publikusan elérhetőmarad + Hyperdrivesemmi — ez jó végállapot is
Postgres, privát hálózatbanHyperdrive + Cloudflare Tunnelmenedzselt Postgresre költözés
MySQLmarad + Hyperdrive
MongoDBHTTP-s Data API vagy megmaradó BFFa 9. modul hibrid útja
SQLite a szerverenez az egyetlen, ami tényleg költözikD1 (5. modul)
Redis session/cachesession → süti; cache → KVa Redis elhagyható
Az ORM kérdése a 11.3-ban részletesen szerepel. A rövid válasz: ha a meglévő kódod TypeORM-et használ Postgresszel, az nem indok a BFF mellett — van rá működő recept, kérésenkénti DataSource-szal és explicit driver-injektálással. Ha Drizzle-re váltanál, azt a migráció után tedd, önálló lépésként.

21.9Amit ne migrálj

Ez a szakasz felszabadító: a migráció sikere nem azon múlik, hogy minden átkerül. Egy jól eltalált végállapotban marad egy kis Node-szolgáltatás, ami azt csinálja, amire Workers-en nincs jó válasz — és ez nem kudarc, hanem tervezés.

A végállapot alakja: a Nuxt Workers-en szolgálja ki a felhasználót és a kérések 95%-át, a maradék pedig egy megmaradó szolgáltatáshoz megy — vagy ugyanazon a proxy-mechanizmuson keresztül, amit a migrációhoz építettél, vagy egy explicit belső API-hívással. A 9. modul „hibrid” doboza pontosan ez. A különbség annyi, hogy most nem kényszerből hibrid, hanem döntésből.

21.10Ütemterv és visszavonulás

Egy közepes méretű alkalmazásra reális minta. A hetek számai nem jóslatok, hanem arányok: a lényeg, hogy a felmérés és az első útvonal együtt az idő harmadát viszi, és ez nem pazarlás.

HétMi történikKimenet
1leltár, kockázatlista, architektúra-döntés (9. modul)írásos döntés a három út közül
2Nuxt-váz, CI/CD, preview-deploy, füstteszt (16–17. modul)üres app élesben, minden proxyzva
3az első, alacsony kockázatú útvonala build-lánc, a cache és a hidratálás meglepetései
4–5olvasó felület: listák, részletező oldalak, SEO (20. modul)a forgalom java már a Nuxton
6session-híd, hitelesített útvonalaka legnagyobb kockázatú lépés, külön hétben
7írás, űrlapok, mutáló végpontokaz Express nagyrészt üres
8adatréteg közvetlenül, a maradék eldöntésea proxy törölhető vagy célzottá szűkül

Három dolog, ami a visszavonulást működőképessé teszi

  1. A MAR_MIENK lista legyen konfiguráció, ne kód. Ha runtimeConfig-ból vagy egy KV-kulcsból jön, egy útvonal visszaadása a régi rendszernek deploy nélkül megtehető — másodpercek alatt, éjjel is.
  2. A régi rendszer maradjon futóképes a teljes migráció alatt, és még utána is néhány hétig. A leállítás önálló, tudatos lépés legyen, nem a migráció mellékterméke.
  3. Minden fázisnak legyen mérőszáma. Nem „kész-e”, hanem: hány útvonal a miénk, mekkora a proxyzott forgalom aránya, hány felhasználó jött még régi session-nel. Ezekből látszik a haladás — és az is, ha valami megállt.
És a legfontosabb, amit a 17. modulból hozol ide: minden fázis végén verzió-alapú kigördítés (17.5), füsttesztel a preview URL ellen (16.6). A migráció nem különleges státusz — ugyanaz a deploy-fegyelem érvényes rá, mint bármi másra. Sőt: itt fontosabb, mert egyszerre két rendszer viselkedését változtatod.

21.11Ellenőrizd magad

  1. Miért nem a legfontosabb üzleti funkcióval kezded a migrációt?
    ▸ Válasz

    Mert az első migrált útvonal a tanulópénz: ott futsz bele a build-lánc, a session, a cache és a hidratálás összes meglepetésébe. Ezt alacsony forgalmú, alacsony kockázatú oldalon akarod megtenni — súgó, kapcsolat, marketinglap. A kritikus útvonalak utoljára jönnek.

  2. Hogyan „engedi tovább” a proxy-middleware a kérést a Nuxtnak?
    ▸ Válasz

    Úgy, hogy nem tér vissza semmivel. A Nitro szerver-middleware-e csak akkor válaszol, ha visszaad valamit — ha nincs return, a kérés megy tovább a normál útvonal-feloldásra. Ezért működik a tizenöt soros proxy.

  3. Miért 99-legacy-proxy.ts a fájlnév?
    ▸ Válasz

    Mert a Nitro szerver-middleware-jei fájlnév szerint ábécésorrendben futnak. A 99- előtag garantálja, hogy a proxy utolsóként fusson — miután az auth- (10-) és tenant-middleware-ek már elvégezték a dolgukat.

  4. Négy költsége van annak, hogy a Worker proxyz. Mondj hármat.
    ▸ Válasz

    (1) Minden át nem vett kérés is meghívja a Workert, tehát számlázódik. (2) A proxyzott kérés alhívásnak számít, és a 6 egyidejű kimenő kapcsolat rá is vonatkozik. (3) Nő a késleltetés: látogató → edge → régi origin. (4) Ha a régi rendszer nem publikus, Cloudflare Tunnel kell, mert a Workerből nincs privát hálózati elérés.

  5. Mi a „felolvasáskori átemelés”, és miért jobb, mint egy migrációs szkript?
    ▸ Válasz

    Az első alkalommal, amikor a felhasználó migrált útvonalra téved, a régi session alapján kiállítunk neki egy újat — onnantól a süti-session él, DB-hívás nélkül. Azért jobb, mert magától lejár: ahogy telnek a hetek, egyre kevesebben jönnek régi session-nel, és amikor a szám nullára megy, a híd egyszerűen törölhető. Nincs kényszerített kijelentkeztetés.

  6. Melyik SPA-minta okozza a legtöbb meglepetést SSR alatt?
    ▸ Válasz

    A böngésző-API használata a setupban vagy a store betöltésekor — localStorage, window, document. SPA-ként hibátlanul mentek, SSR alatt vagy elhasalnak, vagy — rosszabb — hidratálási eltérést okoznak, ami csak néha, csak bizonyos oldalakon látszik.

  7. Migráció közben találsz egy csúnya API-válaszformátumot. Mit teszel?
    ▸ Válasz

    Békén hagyod. Amíg a régi frontend egy része él, minden formátumváltozás két rendszert tör el egyszerre, és a hibakeresésnél nem tudod eldönteni, mi okozta. Ha tényleg tarthatatlan, versionözd (/api/v2/…) és a régit hagyd meg. A szépítés a migráció után jön.

  8. Az adatbázisod Postgres, publikusan elérhető. Mit csinálsz vele a migráció alatt?
    ▸ Válasz

    Semmit — marad, Hyperdrive-val elé kötve. Ez nem csak átmeneti megoldás, hanem jó végállapot is. Az adatbázis-csere külön projekt, nem a migráció része. Az egyetlen adatbázis, ami tényleg költözni szokott, a szerveren futó SQLite → D1.

  9. Miért legyen a MAR_MIENK lista konfiguráció és ne kód?
    ▸ Válasz

    Mert ha runtimeConfig-ból vagy KV-ből jön, egy útvonal visszaadása a régi rendszernek deploy nélkül megtehető, másodpercek alatt. Ez teszi a visszavonulást valódi lehetőséggé éjjel fél háromkor is.

  10. A migráció végén marad egy kis Node-szolgáltatás. Kudarc?
    ▸ Válasz

    Nem — tervezés. PDF-tömeggenerálás, natív bináris köré épült funkció, ritkán hívott bonyolult végpont, perzisztens külső kapcsolat: ezekre Workers-en nincs jó válasz, és nem is kell erőltetni. Ez a 9. modul hibrid doboza — a különbség annyi, hogy most nem kényszerből hibrid, hanem döntésből.

Előző20. modul — Éles üzem: SEO, i18n, cache, hibakövetés Következő 22. modul — Referencia: a Nuxt-konvenciók térképe