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.
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ó.
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és | Mié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 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',
},
})
})
proxyRequest eredményét —, az lesz a válasz. Ez a mechanizmus teszi ezt a tizenöt sort működőképessé..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.| Fázis | Mi történik | Visszavonás |
|---|---|---|
| 0 · Kapu | a Nuxt-váz felállítva, a Worker mindent proxyz | a Cloudflare route törlése |
| 1 · Első útvonal | egy alacsony kockázatú oldal átvéve, SSR-rel | egy sor törlése a MAR_MIENK listából |
| 2 · Olvasó felület | a listázó és részletező oldalak átvéve; írás még a régin | ugyanaz, útvonalanként |
| 3 · Auth és írás | session-híd, majd a mutáló végpontok átvétele | a session-híd kétirányú marad, amíg biztos nem vagy |
| 4 · Adatréteg | a Nuxt közvetlenül éri el az adatbázist, nem a régi API-n át | a régi API-ra visszakapcsoló feature flag |
| 5 · Leállítás | a MAR_MIENK „minden”, a proxy törölhető | — |
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 volt | Nuxtban | Munka |
|---|---|---|
.vue komponensek | ugyanaz | má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 guardok | middleware/ | újraírás — és eldönteni, védelem-e vagy csak UX (4.4) |
| Pinia store | Pinia vagy useState | a modul-szintű állapot átgondolása (6. modul) |
axios-hívások | useFetch / $fetch | nem 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 onMounted | hidratálási eltérés forrása (3. és 6. modul) |
index.html meta-tagek | useSeoMeta | és most már SSR-ben is látszanak (20.2) |
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.A 8. modulban végigmentünk a leképezésen: req.params → getRouterParam, req.query → getQuery, res.json → egyszerű return, és így tovább. A migráció szempontjából három dolog fontos ezen felül:
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.
| Express-minta | Miért nem megy | Helyette |
|---|---|---|
express-session + memóriatár | nincs folyamat, ami tartsa | lezárt süti-session (10. modul) |
multer lemezre írással | nincs valódi lemez (19.2) | R2 közvetlenül (8. modul) |
| hosszú futású route | CPU-limit | Queues / Workflows |
node-cron a folyamatban | a Worker nem él a kérések között | Cron Triggers |
| WebSocket-szerver | nincs tartós kapcsolat a Workerben | Durable Object |
| natív modul (sharp, bcrypt) | C++ addon | 19.9 helyettesítő tábla |
/api/v2/…), és a régit hagyd békén.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ás | Hogyan | Mérleg |
|---|---|---|
| ① Session-tár olvasása | a Worker ugyanabból a session-táblából olvas, mint az Express | nincs kettős bejelentkezés; kell hozzá adatbázis-elérés |
| ② Ellenőrző végpont | a Worker meghív egy régi végpontot, ami validálja a sütit | egyszerű, de kérésenként egy alhívás |
| ③ Kettős süti | bejelentkezéskor mindkét formátum kiadva | a 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,
})
})
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ó.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.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 állapot | Migráció alatt | Utána megfontolandó |
|---|---|---|
| Postgres, publikusan elérhető | marad + Hyperdrive | semmi — ez jó végállapot is |
| Postgres, privát hálózatban | Hyperdrive + Cloudflare Tunnel | menedzselt Postgresre költözés |
| MySQL | marad + Hyperdrive | — |
| MongoDB | HTTP-s Data API vagy megmaradó BFF | a 9. modul hibrid útja |
| SQLite a szerveren | ez az egyetlen, ami tényleg költözik | D1 (5. modul) |
| Redis session/cache | session → süti; cache → KV | a Redis elhagyható |
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.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.
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ét | Mi történik | Kimenet |
|---|---|---|
| 1 | leltár, kockázatlista, architektúra-döntés (9. modul) | írásos döntés a három út közül |
| 2 | Nuxt-váz, CI/CD, preview-deploy, füstteszt (16–17. modul) | üres app élesben, minden proxyzva |
| 3 | az első, alacsony kockázatú útvonal | a build-lánc, a cache és a hidratálás meglepetései |
| 4–5 | olvasó felület: listák, részletező oldalak, SEO (20. modul) | a forgalom java már a Nuxton |
| 6 | session-híd, hitelesített útvonalak | a legnagyobb kockázatú lépés, külön hétben |
| 7 | írás, űrlapok, mutáló végpontok | az Express nagyrészt üres |
| 8 | adatréteg közvetlenül, a maradék eldöntése | a proxy törölhető vagy célzottá szűkül |
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.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.
Ú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.
99-legacy-proxy.ts a fájlnév?
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.
(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.
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.
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.
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.
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.
MAR_MIENK lista konfiguráció és ne kód?
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.
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.