[{"data":1,"prerenderedAt":740},["ShallowReactive",2],{"blog:/blog/migrate-from-ably-to-nolag":3},{"id":4,"title":5,"author":6,"body":7,"category":729,"date":730,"description":731,"excerpt":732,"extension":733,"meta":734,"navigation":170,"path":735,"readTime":736,"seo":737,"stem":738,"__hash__":739},"blog/blog/migrate-from-ably-to-nolag.md","Migrate from Ably to NoLag","Henco Burger",{"type":8,"value":9,"toc":719},"minimark",[10,14,19,22,42,46,125,129,310,314,584,587,591,616,624,628,646,650,685,689,715],[11,12,13],"p",{},"Ably is a mature realtime platform with rich delivery semantics. NoLag shares the same pub/sub foundation and presence model, so moving across is mostly a matter of renaming a few concepts. This guide maps Ably onto NoLag and shows the same flow in both.",[15,16,18],"h2",{"id":17},"why-teams-move","Why teams move",[11,20,21],{},"NoLag keeps the realtime transport you rely on and adds:",[23,24,25,33,39],"ul",{},[26,27,28,32],"li",{},[29,30,31],"strong",{},"Blueprint SDKs"," for chat, notifications, dashboards, tracking, and more.",[26,34,35,38],{},[29,36,37],{},"A coordination layer for AI agents",": dispatch, share state, observe, and approve, on the same platform as your app features.",[26,40,41],{},"One place for human-facing and agent-facing realtime.",[15,43,45],{"id":44},"concept-map","Concept map",[47,48,49,62],"table",{},[50,51,52],"thead",{},[53,54,55,59],"tr",{},[56,57,58],"th",{},"Ably",[56,60,61],{},"NoLag",[63,64,65,74,82,95,107,117],"tbody",{},[53,66,67,71],{},[68,69,70],"td",{},"API key / token auth",[68,72,73],{},"Actor access tokens",[53,75,76,79],{},[68,77,78],{},"Channel",[68,80,81],{},"Room + topic",[53,83,84,90],{},[68,85,86],{},[87,88,89],"code",{},"channel.subscribe('event', cb)",[68,91,92],{},[87,93,94],{},"room.on('topic', cb)",[53,96,97,102],{},[68,98,99],{},[87,100,101],{},"channel.publish('event', data)",[68,103,104],{},[87,105,106],{},"room.emit('topic', data)",[53,108,109,114],{},[68,110,111],{},[87,112,113],{},"channel.presence",[68,115,116],{},"Presence (per room)",[53,118,119,122],{},[68,120,121],{},"History",[68,123,124],{},"Your own store (NoLag is a messaging layer, not a database)",[15,126,128],{"id":127},"before-ably","Before: Ably",[130,131,136],"pre",{"className":132,"code":133,"language":134,"meta":135,"style":135},"language-js shiki shiki-themes github-light github-dark","import * as Ably from 'ably'\n\nconst ably = new Ably.Realtime('API_KEY')\nconst channel = ably.channels.get('chat')\n\nchannel.subscribe('message', (msg) => {\n  console.log('Received:', msg.data)\n})\n\nchannel.publish('message', { text: 'Hello!' })\n","js","",[87,137,138,165,172,203,226,231,261,278,284,289],{"__ignoreMap":135},[139,140,143,147,151,154,158,161],"span",{"class":141,"line":142},"line",1,[139,144,146],{"class":145},"szBVR","import",[139,148,150],{"class":149},"sj4cs"," *",[139,152,153],{"class":145}," as",[139,155,157],{"class":156},"sVt8B"," Ably ",[139,159,160],{"class":145},"from",[139,162,164],{"class":163},"sZZnC"," 'ably'\n",[139,166,168],{"class":141,"line":167},2,[139,169,171],{"emptyLinePlaceholder":170},true,"\n",[139,173,175,178,181,184,187,190,194,197,200],{"class":141,"line":174},3,[139,176,177],{"class":145},"const",[139,179,180],{"class":149}," ably",[139,182,183],{"class":145}," =",[139,185,186],{"class":145}," new",[139,188,189],{"class":156}," Ably.",[139,191,193],{"class":192},"sScJk","Realtime",[139,195,196],{"class":156},"(",[139,198,199],{"class":163},"'API_KEY'",[139,201,202],{"class":156},")\n",[139,204,206,208,211,213,216,219,221,224],{"class":141,"line":205},4,[139,207,177],{"class":145},[139,209,210],{"class":149}," channel",[139,212,183],{"class":145},[139,214,215],{"class":156}," ably.channels.",[139,217,218],{"class":192},"get",[139,220,196],{"class":156},[139,222,223],{"class":163},"'chat'",[139,225,202],{"class":156},[139,227,229],{"class":141,"line":228},5,[139,230,171],{"emptyLinePlaceholder":170},[139,232,234,237,240,242,245,248,252,255,258],{"class":141,"line":233},6,[139,235,236],{"class":156},"channel.",[139,238,239],{"class":192},"subscribe",[139,241,196],{"class":156},[139,243,244],{"class":163},"'message'",[139,246,247],{"class":156},", (",[139,249,251],{"class":250},"s4XuR","msg",[139,253,254],{"class":156},") ",[139,256,257],{"class":145},"=>",[139,259,260],{"class":156}," {\n",[139,262,264,267,270,272,275],{"class":141,"line":263},7,[139,265,266],{"class":156},"  console.",[139,268,269],{"class":192},"log",[139,271,196],{"class":156},[139,273,274],{"class":163},"'Received:'",[139,276,277],{"class":156},", msg.data)\n",[139,279,281],{"class":141,"line":280},8,[139,282,283],{"class":156},"})\n",[139,285,287],{"class":141,"line":286},9,[139,288,171],{"emptyLinePlaceholder":170},[139,290,292,294,297,299,301,304,307],{"class":141,"line":291},10,[139,293,236],{"class":156},[139,295,296],{"class":192},"publish",[139,298,196],{"class":156},[139,300,244],{"class":163},[139,302,303],{"class":156},", { text: ",[139,305,306],{"class":163},"'Hello!'",[139,308,309],{"class":156}," })\n",[15,311,313],{"id":312},"after-nolag","After: NoLag",[130,315,319],{"className":316,"code":317,"language":318,"meta":135,"style":135},"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 subscribes...\nconst receiver = NoLag('receiver_access_token')\nawait receiver.connect()\n\nconst inbox = receiver.setApp(APP_SLUG).setRoom('general')\ninbox.subscribe('message')\ninbox.on('message', (data) => {\n  console.log('Received:', data)\n})\n\n// ...and another publishes. Publishers never receive their own messages,\n// so a single client that subscribes and emits will not log \"Received\".\nconst sender = NoLag('sender_access_token')\nawait sender.connect()\nsender.setApp(APP_SLUG).setRoom('general').emit('message', { text: 'Hello!' })\n","ts",[87,320,321,333,337,343,348,353,365,369,374,393,407,412,445,459,482,496,501,506,512,518,537,549],{"__ignoreMap":135},[139,322,323,325,328,330],{"class":141,"line":142},[139,324,146],{"class":145},[139,326,327],{"class":156}," { NoLag } ",[139,329,160],{"class":145},[139,331,332],{"class":163}," '@nolag/js-sdk'\n",[139,334,335],{"class":141,"line":167},[139,336,171],{"emptyLinePlaceholder":170},[139,338,339],{"class":141,"line":174},[139,340,342],{"class":341},"sJ8bj","// Create the app and its `general` room in the control plane first. The app\n",[139,344,345],{"class":141,"line":205},[139,346,347],{"class":341},"// slug you get back has a random suffix (for example `chat-a3f9`): that is\n",[139,349,350],{"class":141,"line":228},[139,351,352],{"class":341},"// what you pass to setApp(). Room slugs are kept verbatim.\n",[139,354,355,357,360,362],{"class":141,"line":233},[139,356,177],{"class":145},[139,358,359],{"class":149}," APP_SLUG",[139,361,183],{"class":145},[139,363,364],{"class":163}," 'chat-a3f9'\n",[139,366,367],{"class":141,"line":263},[139,368,171],{"emptyLinePlaceholder":170},[139,370,371],{"class":141,"line":280},[139,372,373],{"class":341},"// One actor subscribes...\n",[139,375,376,378,381,383,386,388,391],{"class":141,"line":286},[139,377,177],{"class":145},[139,379,380],{"class":149}," receiver",[139,382,183],{"class":145},[139,384,385],{"class":192}," NoLag",[139,387,196],{"class":156},[139,389,390],{"class":163},"'receiver_access_token'",[139,392,202],{"class":156},[139,394,395,398,401,404],{"class":141,"line":291},[139,396,397],{"class":145},"await",[139,399,400],{"class":156}," receiver.",[139,402,403],{"class":192},"connect",[139,405,406],{"class":156},"()\n",[139,408,410],{"class":141,"line":409},11,[139,411,171],{"emptyLinePlaceholder":170},[139,413,415,417,420,422,424,427,429,432,435,438,440,443],{"class":141,"line":414},12,[139,416,177],{"class":145},[139,418,419],{"class":149}," inbox",[139,421,183],{"class":145},[139,423,400],{"class":156},[139,425,426],{"class":192},"setApp",[139,428,196],{"class":156},[139,430,431],{"class":149},"APP_SLUG",[139,433,434],{"class":156},").",[139,436,437],{"class":192},"setRoom",[139,439,196],{"class":156},[139,441,442],{"class":163},"'general'",[139,444,202],{"class":156},[139,446,448,451,453,455,457],{"class":141,"line":447},13,[139,449,450],{"class":156},"inbox.",[139,452,239],{"class":192},[139,454,196],{"class":156},[139,456,244],{"class":163},[139,458,202],{"class":156},[139,460,462,464,467,469,471,473,476,478,480],{"class":141,"line":461},14,[139,463,450],{"class":156},[139,465,466],{"class":192},"on",[139,468,196],{"class":156},[139,470,244],{"class":163},[139,472,247],{"class":156},[139,474,475],{"class":250},"data",[139,477,254],{"class":156},[139,479,257],{"class":145},[139,481,260],{"class":156},[139,483,485,487,489,491,493],{"class":141,"line":484},15,[139,486,266],{"class":156},[139,488,269],{"class":192},[139,490,196],{"class":156},[139,492,274],{"class":163},[139,494,495],{"class":156},", data)\n",[139,497,499],{"class":141,"line":498},16,[139,500,283],{"class":156},[139,502,504],{"class":141,"line":503},17,[139,505,171],{"emptyLinePlaceholder":170},[139,507,509],{"class":141,"line":508},18,[139,510,511],{"class":341},"// ...and another publishes. Publishers never receive their own messages,\n",[139,513,515],{"class":141,"line":514},19,[139,516,517],{"class":341},"// so a single client that subscribes and emits will not log \"Received\".\n",[139,519,521,523,526,528,530,532,535],{"class":141,"line":520},20,[139,522,177],{"class":145},[139,524,525],{"class":149}," sender",[139,527,183],{"class":145},[139,529,385],{"class":192},[139,531,196],{"class":156},[139,533,534],{"class":163},"'sender_access_token'",[139,536,202],{"class":156},[139,538,540,542,545,547],{"class":141,"line":539},21,[139,541,397],{"class":145},[139,543,544],{"class":156}," sender.",[139,546,403],{"class":192},[139,548,406],{"class":156},[139,550,552,555,557,559,561,563,565,567,569,571,574,576,578,580,582],{"class":141,"line":551},22,[139,553,554],{"class":156},"sender.",[139,556,426],{"class":192},[139,558,196],{"class":156},[139,560,431],{"class":149},[139,562,434],{"class":156},[139,564,437],{"class":192},[139,566,196],{"class":156},[139,568,442],{"class":163},[139,570,434],{"class":156},[139,572,573],{"class":192},"emit",[139,575,196],{"class":156},[139,577,244],{"class":163},[139,579,303],{"class":156},[139,581,306],{"class":163},[139,583,309],{"class":156},[11,585,586],{},"One behavioural difference from Ably is worth flagging before you port a test suite: an Ably client sees its own publishes, a NoLag actor never does. Append the sent message to your UI locally, and use a second actor in tests that expect to receive.",[15,588,590],{"id":589},"presence-and-history","Presence and history",[11,592,593,594,599,600,603,604,607,608,611,612,615],{},"Ably presence maps to NoLag ",[595,596,598],"a",{"href":597},"/docs/concepts/presence","presence",", tracked per room with join and leave events and a live member list. A client sets its presence on the room with ",[87,601,602],{},"room.setPresence(data)"," and listens for ",[87,605,606],{},"presence:join",", ",[87,609,610],{},"presence:leave"," and ",[87,613,614],{},"presence:update"," on the client.",[11,617,618,619,623],{},"History is the one Ably feature that does not have a direct counterpart. NoLag is a messaging layer, not a database: a client that reconnects gets its subscriptions restored and nothing replayed, and a fresh subscription receives no history. If you use Ably history to fill gaps after a disconnect or to render the last N messages on load, keep those messages in your own store and read it on connect. The one place the broker does replay is for load-balanced worker groups with persistent sessions, which is a different job; see ",[595,620,622],{"href":621},"/docs/concepts/replay","Replay and Durable Delivery"," for exactly what that covers.",[15,625,627],{"id":626},"delivery-guarantees","Delivery guarantees",[11,629,630,631,635,636,638,639,641,642,645],{},"Ably is known for its delivery semantics. NoLag offers ",[595,632,634],{"href":633},"/docs/concepts/qos","quality-of-service levels"," 0, 1, and 2 per message, chosen on each ",[87,637,573],{}," rather than paid for everywhere. Be precise about what they cover: the level governs the hop across NoLag's internal broker, and the WebSocket leg between your client and NoLag has a simpler contract. A publish can be acknowledged (pass a callback to ",[87,640,573],{}," and it resolves on the broker's ",[87,643,644],{},"published"," ack), nothing is resent for you, and there is no exactly-once guarantee end to end. Put an idempotency key in the payload of anything that must not be applied twice.",[15,647,649],{"id":648},"authentication","Authentication",[11,651,652,653,656,657,607,660,607,663,607,666,607,669,607,672,675,676,679,680,684],{},"Ably authenticates with API keys and tokens, often carrying capabilities in the token itself. NoLag centres permissions on the ",[29,654,655],{},"actor",": every credential resolves to a typed actor (",[87,658,659],{},"user",[87,661,662],{},"device",[87,664,665],{},"service",[87,667,668],{},"session",[87,670,671],{},"agent",[87,673,674],{},"orchestrator"," or ",[87,677,678],{},"observer","), and its read and write access lives server-side as per-topic ACL rather than in each token. For browser clients, your backend mints a short-lived JWT (",[595,681,683],{"href":682},"/docs/client-tokens","client token",") signed with a project signing key, so long-lived credentials never leave your servers.",[15,686,688],{"id":687},"next-steps","Next steps",[23,690,691,699,707],{},[26,692,693,694,698],{},"Start with the ",[595,695,697],{"href":696},"/docs/getting-started","5-minute quick start",".",[26,700,701,702,706],{},"Read the deeper ",[595,703,705],{"href":704},"/compare/ably","NoLag vs Ably"," comparison.",[26,708,709,710,714],{},"Explore the ",[595,711,713],{"href":712},"/docs/high-level-sdks","high-level SDKs"," if you would rather not rebuild chat or dashboards by hand.",[716,717,718],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .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":135,"searchDepth":167,"depth":167,"links":720},[721,722,723,724,725,726,727,728],{"id":17,"depth":167,"text":18},{"id":44,"depth":167,"text":45},{"id":127,"depth":167,"text":128},{"id":312,"depth":167,"text":313},{"id":589,"depth":167,"text":590},{"id":626,"depth":167,"text":627},{"id":648,"depth":167,"text":649},{"id":687,"depth":167,"text":688},"Migration Guide","2026-07-31","A practical guide to moving a realtime app from Ably to NoLag, with a concept map and before-and-after code for channels, publishing, presence, and history.",null,"md",{},"/blog/migrate-from-ably-to-nolag","6 min read",{"title":5,"description":731},"blog/migrate-from-ably-to-nolag","AE-a8TLTDe-_j0hjVHx1X11NSVCYC_3KNfFgPYM7dEo",1789869417245]