[{"data":1,"prerenderedAt":697},["ShallowReactive",2],{"blog:/blog/migrate-from-firebase-to-nolag":3},{"id":4,"title":5,"author":6,"body":7,"category":686,"date":687,"description":688,"excerpt":689,"extension":690,"meta":691,"navigation":194,"path":692,"readTime":693,"seo":694,"stem":695,"__hash__":696},"blog/blog/migrate-from-firebase-to-nolag.md","Migrate from Firebase to NoLag","Henco Burger",{"type":8,"value":9,"toc":677},"minimark",[10,14,19,31,40,44,72,76,156,160,319,323,601,604,608,648,652,673],[11,12,13],"p",{},"Firebase Realtime Database and Firestore give you realtime by syncing stored documents and firing client listeners when the data changes. NoLag takes a different approach: explicit publish and subscribe over topics. That difference matters for migration, so let us be clear up front about what to move and what to keep.",[15,16,18],"h2",{"id":17},"what-to-migrate-and-what-not-to","What to migrate, and what not to",[11,20,21,22,26,27,30],{},"NoLag is a ",[23,24,25],"strong",{},"messaging layer, not a database",". If you use Firebase purely as a realtime signal, presence, or message bus, NoLag replaces that cleanly and gives you more control over delivery. If you use Firebase as your ",[23,28,29],{},"system of record",", keep a database. Migrate the realtime and pub/sub usage to NoLag, and let your data live wherever suits you. Many teams end up with NoLag for the live layer and a database of their choice behind it.",[11,32,33,34,39],{},"This also answers the question every Firebase team asks first: \"what does a client see when it reconnects?\" In Firebase, the current document. In NoLag, its subscriptions are restored and it receives what is published from then on; nothing is replayed and a fresh subscription gets no history. The database you keep is where \"current state\" lives, and the client reads it on connect. (The broker does replay for load-balanced worker groups with persistent sessions, which is a different job; see ",[35,36,38],"a",{"href":37},"/docs/concepts/replay","Replay and Durable Delivery",".)",[15,41,43],{"id":42},"why-teams-move-the-realtime-layer","Why teams move the realtime layer",[45,46,47,54,60,66],"ul",{},[48,49,50,53],"li",{},[23,51,52],{},"Explicit pub/sub"," instead of modelling every realtime feature as a change on a stored document.",[48,55,56,59],{},[23,57,58],{},"Database-agnostic",": add realtime to any stack rather than standardising on Firestore.",[48,61,62,65],{},[23,63,64],{},"Blueprint SDKs"," for chat, notifications, dashboards, and tracking.",[48,67,68,71],{},[23,69,70],{},"A coordination layer for AI agents"," on the same platform.",[15,73,75],{"id":74},"concept-map","Concept map",[77,78,79,92],"table",{},[80,81,82],"thead",{},[83,84,85,89],"tr",{},[86,87,88],"th",{},"Firebase",[86,90,91],{},"NoLag",[93,94,95,104,122,140,148],"tbody",{},[83,96,97,101],{},[98,99,100],"td",{},"Realtime Database ref / Firestore collection",[98,102,103],{},"Room + topic",[83,105,106,117],{},[98,107,108,112,113,116],{},[109,110,111],"code",{},"onValue"," / ",[109,114,115],{},"onSnapshot"," listener",[98,118,119],{},[109,120,121],{},"room.on('topic', cb)",[83,123,124,135],{},[98,125,126,112,129,112,132],{},[109,127,128],{},"set",[109,130,131],{},"update",[109,133,134],{},"push",[98,136,137],{},[109,138,139],{},"room.emit('topic', data)",[83,141,142,145],{},[98,143,144],{},"Security rules",[98,146,147],{},"Actors, access tokens, per-topic ACL",[83,149,150,153],{},[98,151,152],{},"Presence via connection state",[98,154,155],{},"Presence (per room)",[15,157,159],{"id":158},"before-firebase-realtime-database","Before: Firebase Realtime Database",[161,162,167],"pre",{"className":163,"code":164,"language":165,"meta":166,"style":166},"language-js shiki shiki-themes github-light github-dark","import { getDatabase, ref, onValue, push } from 'firebase/database'\n\nconst db = getDatabase()\nconst messagesRef = ref(db, 'chat/general/messages')\n\nonValue(messagesRef, (snapshot) => {\n  console.log('Data changed:', snapshot.val())\n})\n\npush(messagesRef, { text: 'Hello!', sender: 'user-123' })\n","js","",[109,168,169,189,196,216,238,243,264,288,294,299],{"__ignoreMap":166},[170,171,174,178,182,185],"span",{"class":172,"line":173},"line",1,[170,175,177],{"class":176},"szBVR","import",[170,179,181],{"class":180},"sVt8B"," { getDatabase, ref, onValue, push } ",[170,183,184],{"class":176},"from",[170,186,188],{"class":187},"sZZnC"," 'firebase/database'\n",[170,190,192],{"class":172,"line":191},2,[170,193,195],{"emptyLinePlaceholder":194},true,"\n",[170,197,199,202,206,209,213],{"class":172,"line":198},3,[170,200,201],{"class":176},"const",[170,203,205],{"class":204},"sj4cs"," db",[170,207,208],{"class":176}," =",[170,210,212],{"class":211},"sScJk"," getDatabase",[170,214,215],{"class":180},"()\n",[170,217,219,221,224,226,229,232,235],{"class":172,"line":218},4,[170,220,201],{"class":176},[170,222,223],{"class":204}," messagesRef",[170,225,208],{"class":176},[170,227,228],{"class":211}," ref",[170,230,231],{"class":180},"(db, ",[170,233,234],{"class":187},"'chat/general/messages'",[170,236,237],{"class":180},")\n",[170,239,241],{"class":172,"line":240},5,[170,242,195],{"emptyLinePlaceholder":194},[170,244,246,248,251,255,258,261],{"class":172,"line":245},6,[170,247,111],{"class":211},[170,249,250],{"class":180},"(messagesRef, (",[170,252,254],{"class":253},"s4XuR","snapshot",[170,256,257],{"class":180},") ",[170,259,260],{"class":176},"=>",[170,262,263],{"class":180}," {\n",[170,265,267,270,273,276,279,282,285],{"class":172,"line":266},7,[170,268,269],{"class":180},"  console.",[170,271,272],{"class":211},"log",[170,274,275],{"class":180},"(",[170,277,278],{"class":187},"'Data changed:'",[170,280,281],{"class":180},", snapshot.",[170,283,284],{"class":211},"val",[170,286,287],{"class":180},"())\n",[170,289,291],{"class":172,"line":290},8,[170,292,293],{"class":180},"})\n",[170,295,297],{"class":172,"line":296},9,[170,298,195],{"emptyLinePlaceholder":194},[170,300,302,304,307,310,313,316],{"class":172,"line":301},10,[170,303,134],{"class":211},[170,305,306],{"class":180},"(messagesRef, { text: ",[170,308,309],{"class":187},"'Hello!'",[170,311,312],{"class":180},", sender: ",[170,314,315],{"class":187},"'user-123'",[170,317,318],{"class":180}," })\n",[15,320,322],{"id":321},"after-nolag","After: NoLag",[161,324,328],{"className":325,"code":326,"language":327,"meta":166,"style":166},"language-ts shiki shiki-themes github-light github-dark","import { NoLag } from '@nolag/js-sdk'\n\n// Create the app and its `general` room in the control plane first. The app\n// slug you get back has a random suffix (for example `chat-a3f9`): that is\n// what you pass to setApp(). Room slugs are kept verbatim.\nconst APP_SLUG = 'chat-a3f9'\n\n// One actor listens...\nconst receiver = NoLag('receiver_access_token')\nawait receiver.connect()\n\nconst inbox = receiver.setApp(APP_SLUG).setRoom('general')\ninbox.subscribe('messages')\ninbox.on('messages', (data) => {\n  console.log('Message received:', data)\n})\n\n// ...and another publishes. Publishers never receive their own messages,\n// so the sender appends its own message to the UI locally.\nconst sender = NoLag('sender_access_token')\nawait sender.connect()\nsender.setApp(APP_SLUG).setRoom('general').emit('messages', { text: 'Hello!', sender: 'user-123' })\n","ts",[109,329,330,342,346,352,357,362,374,378,383,402,415,420,453,469,493,508,513,518,524,530,549,561],{"__ignoreMap":166},[170,331,332,334,337,339],{"class":172,"line":173},[170,333,177],{"class":176},[170,335,336],{"class":180}," { NoLag } ",[170,338,184],{"class":176},[170,340,341],{"class":187}," '@nolag/js-sdk'\n",[170,343,344],{"class":172,"line":191},[170,345,195],{"emptyLinePlaceholder":194},[170,347,348],{"class":172,"line":198},[170,349,351],{"class":350},"sJ8bj","// Create the app and its `general` room in the control plane first. The app\n",[170,353,354],{"class":172,"line":218},[170,355,356],{"class":350},"// slug you get back has a random suffix (for example `chat-a3f9`): that is\n",[170,358,359],{"class":172,"line":240},[170,360,361],{"class":350},"// what you pass to setApp(). Room slugs are kept verbatim.\n",[170,363,364,366,369,371],{"class":172,"line":245},[170,365,201],{"class":176},[170,367,368],{"class":204}," APP_SLUG",[170,370,208],{"class":176},[170,372,373],{"class":187}," 'chat-a3f9'\n",[170,375,376],{"class":172,"line":266},[170,377,195],{"emptyLinePlaceholder":194},[170,379,380],{"class":172,"line":290},[170,381,382],{"class":350},"// One actor listens...\n",[170,384,385,387,390,392,395,397,400],{"class":172,"line":296},[170,386,201],{"class":176},[170,388,389],{"class":204}," receiver",[170,391,208],{"class":176},[170,393,394],{"class":211}," NoLag",[170,396,275],{"class":180},[170,398,399],{"class":187},"'receiver_access_token'",[170,401,237],{"class":180},[170,403,404,407,410,413],{"class":172,"line":301},[170,405,406],{"class":176},"await",[170,408,409],{"class":180}," receiver.",[170,411,412],{"class":211},"connect",[170,414,215],{"class":180},[170,416,418],{"class":172,"line":417},11,[170,419,195],{"emptyLinePlaceholder":194},[170,421,423,425,428,430,432,435,437,440,443,446,448,451],{"class":172,"line":422},12,[170,424,201],{"class":176},[170,426,427],{"class":204}," inbox",[170,429,208],{"class":176},[170,431,409],{"class":180},[170,433,434],{"class":211},"setApp",[170,436,275],{"class":180},[170,438,439],{"class":204},"APP_SLUG",[170,441,442],{"class":180},").",[170,444,445],{"class":211},"setRoom",[170,447,275],{"class":180},[170,449,450],{"class":187},"'general'",[170,452,237],{"class":180},[170,454,456,459,462,464,467],{"class":172,"line":455},13,[170,457,458],{"class":180},"inbox.",[170,460,461],{"class":211},"subscribe",[170,463,275],{"class":180},[170,465,466],{"class":187},"'messages'",[170,468,237],{"class":180},[170,470,472,474,477,479,481,484,487,489,491],{"class":172,"line":471},14,[170,473,458],{"class":180},[170,475,476],{"class":211},"on",[170,478,275],{"class":180},[170,480,466],{"class":187},[170,482,483],{"class":180},", (",[170,485,486],{"class":253},"data",[170,488,257],{"class":180},[170,490,260],{"class":176},[170,492,263],{"class":180},[170,494,496,498,500,502,505],{"class":172,"line":495},15,[170,497,269],{"class":180},[170,499,272],{"class":211},[170,501,275],{"class":180},[170,503,504],{"class":187},"'Message received:'",[170,506,507],{"class":180},", data)\n",[170,509,511],{"class":172,"line":510},16,[170,512,293],{"class":180},[170,514,516],{"class":172,"line":515},17,[170,517,195],{"emptyLinePlaceholder":194},[170,519,521],{"class":172,"line":520},18,[170,522,523],{"class":350},"// ...and another publishes. Publishers never receive their own messages,\n",[170,525,527],{"class":172,"line":526},19,[170,528,529],{"class":350},"// so the sender appends its own message to the UI locally.\n",[170,531,533,535,538,540,542,544,547],{"class":172,"line":532},20,[170,534,201],{"class":176},[170,536,537],{"class":204}," sender",[170,539,208],{"class":176},[170,541,394],{"class":211},[170,543,275],{"class":180},[170,545,546],{"class":187},"'sender_access_token'",[170,548,237],{"class":180},[170,550,552,554,557,559],{"class":172,"line":551},21,[170,553,406],{"class":176},[170,555,556],{"class":180}," sender.",[170,558,412],{"class":211},[170,560,215],{"class":180},[170,562,564,567,569,571,573,575,577,579,581,583,586,588,590,593,595,597,599],{"class":172,"line":563},22,[170,565,566],{"class":180},"sender.",[170,568,434],{"class":211},[170,570,275],{"class":180},[170,572,439],{"class":204},[170,574,442],{"class":180},[170,576,445],{"class":211},[170,578,275],{"class":180},[170,580,450],{"class":187},[170,582,442],{"class":180},[170,584,585],{"class":211},"emit",[170,587,275],{"class":180},[170,589,466],{"class":187},[170,591,592],{"class":180},", { text: ",[170,594,309],{"class":187},[170,596,312],{"class":180},[170,598,315],{"class":187},[170,600,318],{"class":180},[11,602,603],{},"The key shift is from \"write to a path and listen for changes\" to \"publish an event and subscribe to it.\" You send exactly the message you mean, rather than reshaping your data model so that a write triggers the right listeners. The other shift is that a Firebase listener fires for the writer too, and a NoLag publisher never receives its own message: update your local state when you send, and let the subscription carry everyone else's.",[15,605,607],{"id":606},"access-control","Access control",[11,609,610,611,614,615,618,619,622,623,622,626,622,629,622,632,622,635,638,639,642,643,647],{},"Firebase security rules become ",[23,612,613],{},"per-topic ACL"," in NoLag, tied to a typed ",[23,616,617],{},"actor"," (",[109,620,621],{},"user",", ",[109,624,625],{},"device",[109,627,628],{},"service",[109,630,631],{},"session",[109,633,634],{},"agent",[109,636,637],{},"orchestrator"," or ",[109,640,641],{},"observer",") and enforced at the broker rather than written in a rules language. A connection authenticates with an actor token, and browser clients use short-lived JWTs (",[35,644,646],{"href":645},"/docs/client-tokens","client tokens",") your backend signs with a project signing key.",[15,649,651],{"id":650},"next-steps","Next steps",[45,653,654,662,670],{},[48,655,656,657,661],{},"Follow the ",[35,658,660],{"href":659},"/docs/getting-started","5-minute quick start",".",[48,663,664,665,669],{},"See the full ",[35,666,668],{"href":667},"/compare/firebase","NoLag vs Firebase"," comparison.",[48,671,672],{},"If you are keeping a database, use NoLag alongside it for the live layer only.",[674,675,676],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":166,"searchDepth":191,"depth":191,"links":678},[679,680,681,682,683,684,685],{"id":17,"depth":191,"text":18},{"id":42,"depth":191,"text":43},{"id":74,"depth":191,"text":75},{"id":158,"depth":191,"text":159},{"id":321,"depth":191,"text":322},{"id":606,"depth":191,"text":607},{"id":650,"depth":191,"text":651},"Migration Guide","2026-08-01","How to move the realtime layer of a Firebase app to NoLag, when it makes sense, and what to keep. Includes a concept map and before-and-after code.",null,"md",{},"/blog/migrate-from-firebase-to-nolag","7 min read",{"title":5,"description":688},"blog/migrate-from-firebase-to-nolag","xIPKlr1J0w7D2fXKJufUpJexA4dHSjvGnS3dw29OtCk",1789869417312]