[{"data":1,"prerenderedAt":930},["ShallowReactive",2],{"blog:/blog/add-realtime-to-nextjs":3},{"id":4,"title":5,"author":6,"body":7,"category":919,"date":920,"description":921,"excerpt":922,"extension":923,"meta":924,"navigation":96,"path":925,"readTime":926,"seo":927,"stem":928,"__hash__":929},"blog/blog/add-realtime-to-nextjs.md","Add Realtime to a Next.js App with NoLag","Henco Burger",{"type":8,"value":9,"toc":912},"minimark",[10,19,24,50,54,61,290,299,303,852,859,863,878,882,908],[11,12,13,14,18],"p",{},"Next.js renders on the server and hydrates in the browser, so the one rule for adding realtime is simple: the realtime connection belongs in a ",[15,16,17],"strong",{},"client component",". This guide shows the clean pattern, including a server route that hands the browser a token so your project key never ships to the client.",[20,21,23],"h2",{"id":22},"_1-install-the-sdk","1. Install the SDK",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark","npm install @nolag/js-sdk\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sScJk","npm",[35,44,46],{"class":45},"sZZnC"," install",[35,48,49],{"class":45}," @nolag/js-sdk\n",[20,51,53],{"id":52},"_2-mint-a-token-on-the-server","2. Mint a token on the server",[11,55,56,57,60],{},"Never put a project key in browser code. Add a route handler that creates a token server-side and returns it. The ",[32,58,59],{},"NOLAG_API_KEY"," stays in your server environment.",[25,62,66],{"className":63,"code":64,"language":65,"meta":30,"style":30},"language-ts shiki shiki-themes github-light github-dark","// app/api/nolag-token/route.ts\nimport { NextResponse } from 'next/server'\n\nexport async function GET() {\n  const res = await fetch('https://api.nolag.app/v1/actors', {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${process.env.NOLAG_API_KEY}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({ name: 'Web Client', actorType: 'device' }),\n  })\n\n  const actor = await res.json()\n  return NextResponse.json({ token: actor.accessToken })\n}\n","ts",[32,67,68,74,91,98,116,144,156,162,189,203,209,238,244,249,270,284],{"__ignoreMap":30},[35,69,70],{"class":37,"line":38},[35,71,73],{"class":72},"sJ8bj","// app/api/nolag-token/route.ts\n",[35,75,77,81,85,88],{"class":37,"line":76},2,[35,78,80],{"class":79},"szBVR","import",[35,82,84],{"class":83},"sVt8B"," { NextResponse } ",[35,86,87],{"class":79},"from",[35,89,90],{"class":45}," 'next/server'\n",[35,92,94],{"class":37,"line":93},3,[35,95,97],{"emptyLinePlaceholder":96},true,"\n",[35,99,101,104,107,110,113],{"class":37,"line":100},4,[35,102,103],{"class":79},"export",[35,105,106],{"class":79}," async",[35,108,109],{"class":79}," function",[35,111,112],{"class":41}," GET",[35,114,115],{"class":83},"() {\n",[35,117,119,122,126,129,132,135,138,141],{"class":37,"line":118},5,[35,120,121],{"class":79},"  const",[35,123,125],{"class":124},"sj4cs"," res",[35,127,128],{"class":79}," =",[35,130,131],{"class":79}," await",[35,133,134],{"class":41}," fetch",[35,136,137],{"class":83},"(",[35,139,140],{"class":45},"'https://api.nolag.app/v1/actors'",[35,142,143],{"class":83},", {\n",[35,145,147,150,153],{"class":37,"line":146},6,[35,148,149],{"class":83},"    method: ",[35,151,152],{"class":45},"'POST'",[35,154,155],{"class":83},",\n",[35,157,159],{"class":37,"line":158},7,[35,160,161],{"class":83},"    headers: {\n",[35,163,165,168,171,174,177,180,182,184,187],{"class":37,"line":164},8,[35,166,167],{"class":83},"      Authorization: ",[35,169,170],{"class":45},"`Bearer ${",[35,172,173],{"class":83},"process",[35,175,176],{"class":45},".",[35,178,179],{"class":83},"env",[35,181,176],{"class":45},[35,183,59],{"class":124},[35,185,186],{"class":45},"}`",[35,188,155],{"class":83},[35,190,192,195,198,201],{"class":37,"line":191},9,[35,193,194],{"class":45},"      'Content-Type'",[35,196,197],{"class":83},": ",[35,199,200],{"class":45},"'application/json'",[35,202,155],{"class":83},[35,204,206],{"class":37,"line":205},10,[35,207,208],{"class":83},"    },\n",[35,210,212,215,218,220,223,226,229,232,235],{"class":37,"line":211},11,[35,213,214],{"class":83},"    body: ",[35,216,217],{"class":124},"JSON",[35,219,176],{"class":83},[35,221,222],{"class":41},"stringify",[35,224,225],{"class":83},"({ name: ",[35,227,228],{"class":45},"'Web Client'",[35,230,231],{"class":83},", actorType: ",[35,233,234],{"class":45},"'device'",[35,236,237],{"class":83}," }),\n",[35,239,241],{"class":37,"line":240},12,[35,242,243],{"class":83},"  })\n",[35,245,247],{"class":37,"line":246},13,[35,248,97],{"emptyLinePlaceholder":96},[35,250,252,254,257,259,261,264,267],{"class":37,"line":251},14,[35,253,121],{"class":79},[35,255,256],{"class":124}," actor",[35,258,128],{"class":79},[35,260,131],{"class":79},[35,262,263],{"class":83}," res.",[35,265,266],{"class":41},"json",[35,268,269],{"class":83},"()\n",[35,271,273,276,279,281],{"class":37,"line":272},15,[35,274,275],{"class":79},"  return",[35,277,278],{"class":83}," NextResponse.",[35,280,266],{"class":41},[35,282,283],{"class":83},"({ token: actor.accessToken })\n",[35,285,287],{"class":37,"line":286},16,[35,288,289],{"class":83},"}\n",[11,291,292,293,298],{},"For production, prefer short-lived ",[294,295,297],"a",{"href":296},"/docs/client-tokens","client tokens"," minted from your project signing keys so browser sessions get scoped, expiring credentials.",[20,300,302],{"id":301},"_3-connect-from-a-client-component","3. Connect from a client component",[25,304,308],{"className":305,"code":306,"language":307,"meta":30,"style":30},"language-tsx shiki shiki-themes github-light github-dark","'use client'\nimport { useEffect, useState } from 'react'\nimport { NoLag } from '@nolag/js-sdk'\n\nexport default function Chat() {\n  const [messages, setMessages] = useState\u003Cany[]>([])\n\n  useEffect(() => {\n    let client: ReturnType\u003Ctypeof NoLag> | undefined\n    let active = true\n\n    ;(async () => {\n      const { token } = await fetch('/api/nolag-token').then((r) => r.json())\n      if (!active) return\n\n      client = NoLag(token)\n      await client.connect()\n\n      const room = client.setApp('chat').setRoom('general')\n      room.subscribe('message')\n      room.on('message', (data) => setMessages((m) => [...m, data]))\n    })()\n\n    return () => {\n      active = false\n      client?.disconnect?.()\n    }\n  }, [])\n\n  const send = () => {\n    // room.emit('message', { text: 'Hello!' })\n  }\n\n  return (\n    \u003Cul>\n      {messages.map((m, i) => (\n        \u003Cli key={i}>{m.text}\u003C/li>\n      ))}\n    \u003C/ul>\n  )\n}\n","tsx",[32,309,310,315,327,339,343,357,391,395,409,437,449,453,468,519,536,540,553,567,572,605,621,663,669,674,686,697,709,715,721,726,742,748,754,759,767,780,804,825,831,841,847],{"__ignoreMap":30},[35,311,312],{"class":37,"line":38},[35,313,314],{"class":45},"'use client'\n",[35,316,317,319,322,324],{"class":37,"line":76},[35,318,80],{"class":79},[35,320,321],{"class":83}," { useEffect, useState } ",[35,323,87],{"class":79},[35,325,326],{"class":45}," 'react'\n",[35,328,329,331,334,336],{"class":37,"line":93},[35,330,80],{"class":79},[35,332,333],{"class":83}," { NoLag } ",[35,335,87],{"class":79},[35,337,338],{"class":45}," '@nolag/js-sdk'\n",[35,340,341],{"class":37,"line":100},[35,342,97],{"emptyLinePlaceholder":96},[35,344,345,347,350,352,355],{"class":37,"line":118},[35,346,103],{"class":79},[35,348,349],{"class":79}," default",[35,351,109],{"class":79},[35,353,354],{"class":41}," Chat",[35,356,115],{"class":83},[35,358,359,361,364,367,370,373,376,379,382,385,388],{"class":37,"line":146},[35,360,121],{"class":79},[35,362,363],{"class":83}," [",[35,365,366],{"class":124},"messages",[35,368,369],{"class":83},", ",[35,371,372],{"class":124},"setMessages",[35,374,375],{"class":83},"] ",[35,377,378],{"class":79},"=",[35,380,381],{"class":41}," useState",[35,383,384],{"class":83},"\u003C",[35,386,387],{"class":124},"any",[35,389,390],{"class":83},"[]>([])\n",[35,392,393],{"class":37,"line":158},[35,394,97],{"emptyLinePlaceholder":96},[35,396,397,400,403,406],{"class":37,"line":164},[35,398,399],{"class":41},"  useEffect",[35,401,402],{"class":83},"(() ",[35,404,405],{"class":79},"=>",[35,407,408],{"class":83}," {\n",[35,410,411,414,417,420,423,425,428,431,434],{"class":37,"line":191},[35,412,413],{"class":79},"    let",[35,415,416],{"class":83}," client",[35,418,419],{"class":79},":",[35,421,422],{"class":41}," ReturnType",[35,424,384],{"class":83},[35,426,427],{"class":79},"typeof",[35,429,430],{"class":83}," NoLag> ",[35,432,433],{"class":79},"|",[35,435,436],{"class":124}," undefined\n",[35,438,439,441,444,446],{"class":37,"line":205},[35,440,413],{"class":79},[35,442,443],{"class":83}," active ",[35,445,378],{"class":79},[35,447,448],{"class":124}," true\n",[35,450,451],{"class":37,"line":211},[35,452,97],{"emptyLinePlaceholder":96},[35,454,455,458,461,464,466],{"class":37,"line":240},[35,456,457],{"class":83},"    ;(",[35,459,460],{"class":79},"async",[35,462,463],{"class":83}," () ",[35,465,405],{"class":79},[35,467,408],{"class":83},[35,469,470,473,476,479,482,484,486,488,490,493,496,499,502,506,509,511,514,516],{"class":37,"line":246},[35,471,472],{"class":79},"      const",[35,474,475],{"class":83}," { ",[35,477,478],{"class":124},"token",[35,480,481],{"class":83}," } ",[35,483,378],{"class":79},[35,485,131],{"class":79},[35,487,134],{"class":41},[35,489,137],{"class":83},[35,491,492],{"class":45},"'/api/nolag-token'",[35,494,495],{"class":83},").",[35,497,498],{"class":41},"then",[35,500,501],{"class":83},"((",[35,503,505],{"class":504},"s4XuR","r",[35,507,508],{"class":83},") ",[35,510,405],{"class":79},[35,512,513],{"class":83}," r.",[35,515,266],{"class":41},[35,517,518],{"class":83},"())\n",[35,520,521,524,527,530,533],{"class":37,"line":251},[35,522,523],{"class":79},"      if",[35,525,526],{"class":83}," (",[35,528,529],{"class":79},"!",[35,531,532],{"class":83},"active) ",[35,534,535],{"class":79},"return\n",[35,537,538],{"class":37,"line":272},[35,539,97],{"emptyLinePlaceholder":96},[35,541,542,545,547,550],{"class":37,"line":286},[35,543,544],{"class":83},"      client ",[35,546,378],{"class":79},[35,548,549],{"class":41}," NoLag",[35,551,552],{"class":83},"(token)\n",[35,554,556,559,562,565],{"class":37,"line":555},17,[35,557,558],{"class":79},"      await",[35,560,561],{"class":83}," client.",[35,563,564],{"class":41},"connect",[35,566,269],{"class":83},[35,568,570],{"class":37,"line":569},18,[35,571,97],{"emptyLinePlaceholder":96},[35,573,575,577,580,582,584,587,589,592,594,597,599,602],{"class":37,"line":574},19,[35,576,472],{"class":79},[35,578,579],{"class":124}," room",[35,581,128],{"class":79},[35,583,561],{"class":83},[35,585,586],{"class":41},"setApp",[35,588,137],{"class":83},[35,590,591],{"class":45},"'chat'",[35,593,495],{"class":83},[35,595,596],{"class":41},"setRoom",[35,598,137],{"class":83},[35,600,601],{"class":45},"'general'",[35,603,604],{"class":83},")\n",[35,606,608,611,614,616,619],{"class":37,"line":607},20,[35,609,610],{"class":83},"      room.",[35,612,613],{"class":41},"subscribe",[35,615,137],{"class":83},[35,617,618],{"class":45},"'message'",[35,620,604],{"class":83},[35,622,624,626,629,631,633,636,639,641,643,646,648,651,653,655,657,660],{"class":37,"line":623},21,[35,625,610],{"class":83},[35,627,628],{"class":41},"on",[35,630,137],{"class":83},[35,632,618],{"class":45},[35,634,635],{"class":83},", (",[35,637,638],{"class":504},"data",[35,640,508],{"class":83},[35,642,405],{"class":79},[35,644,645],{"class":41}," setMessages",[35,647,501],{"class":83},[35,649,650],{"class":504},"m",[35,652,508],{"class":83},[35,654,405],{"class":79},[35,656,363],{"class":83},[35,658,659],{"class":79},"...",[35,661,662],{"class":83},"m, data]))\n",[35,664,666],{"class":37,"line":665},22,[35,667,668],{"class":83},"    })()\n",[35,670,672],{"class":37,"line":671},23,[35,673,97],{"emptyLinePlaceholder":96},[35,675,677,680,682,684],{"class":37,"line":676},24,[35,678,679],{"class":79},"    return",[35,681,463],{"class":83},[35,683,405],{"class":79},[35,685,408],{"class":83},[35,687,689,692,694],{"class":37,"line":688},25,[35,690,691],{"class":83},"      active ",[35,693,378],{"class":79},[35,695,696],{"class":124}," false\n",[35,698,700,703,706],{"class":37,"line":699},26,[35,701,702],{"class":83},"      client?.",[35,704,705],{"class":41},"disconnect",[35,707,708],{"class":83},"?.()\n",[35,710,712],{"class":37,"line":711},27,[35,713,714],{"class":83},"    }\n",[35,716,718],{"class":37,"line":717},28,[35,719,720],{"class":83},"  }, [])\n",[35,722,724],{"class":37,"line":723},29,[35,725,97],{"emptyLinePlaceholder":96},[35,727,729,731,734,736,738,740],{"class":37,"line":728},30,[35,730,121],{"class":79},[35,732,733],{"class":41}," send",[35,735,128],{"class":79},[35,737,463],{"class":83},[35,739,405],{"class":79},[35,741,408],{"class":83},[35,743,745],{"class":37,"line":744},31,[35,746,747],{"class":72},"    // room.emit('message', { text: 'Hello!' })\n",[35,749,751],{"class":37,"line":750},32,[35,752,753],{"class":83},"  }\n",[35,755,757],{"class":37,"line":756},33,[35,758,97],{"emptyLinePlaceholder":96},[35,760,762,764],{"class":37,"line":761},34,[35,763,275],{"class":79},[35,765,766],{"class":83}," (\n",[35,768,770,773,777],{"class":37,"line":769},35,[35,771,772],{"class":83},"    \u003C",[35,774,776],{"class":775},"s9eBZ","ul",[35,778,779],{"class":83},">\n",[35,781,783,786,789,791,793,795,798,800,802],{"class":37,"line":782},36,[35,784,785],{"class":83},"      {messages.",[35,787,788],{"class":41},"map",[35,790,501],{"class":83},[35,792,650],{"class":504},[35,794,369],{"class":83},[35,796,797],{"class":504},"i",[35,799,508],{"class":83},[35,801,405],{"class":79},[35,803,766],{"class":83},[35,805,807,810,813,816,818,821,823],{"class":37,"line":806},37,[35,808,809],{"class":83},"        \u003C",[35,811,812],{"class":775},"li",[35,814,815],{"class":41}," key",[35,817,378],{"class":79},[35,819,820],{"class":83},"{i}>{m.text}\u003C/",[35,822,812],{"class":775},[35,824,779],{"class":83},[35,826,828],{"class":37,"line":827},38,[35,829,830],{"class":83},"      ))}\n",[35,832,834,837,839],{"class":37,"line":833},39,[35,835,836],{"class":83},"    \u003C/",[35,838,776],{"class":775},[35,840,779],{"class":83},[35,842,844],{"class":37,"line":843},40,[35,845,846],{"class":83},"  )\n",[35,848,850],{"class":37,"line":849},41,[35,851,289],{"class":83},[11,853,854,855,858],{},"The ",[32,856,857],{},"active"," flag guards against React running effects twice in development and against a connection resolving after the component unmounts.",[20,860,862],{"id":861},"why-the-client-component-rule-matters","Why the client-component rule matters",[11,864,865,866,869,870,873,874,877],{},"The NoLag SDK opens a WebSocket, which only exists in the browser. Importing and connecting it in a server component or during SSR will fail. Keep the ",[32,867,868],{},"NoLag(...)"," call inside ",[32,871,872],{},"useEffect"," (or an event handler) in a ",[32,875,876],{},"'use client'"," file and you avoid the whole class of hydration and \"window is not defined\" errors.",[20,879,881],{"id":880},"next-steps","Next steps",[776,883,884,892,900],{},[812,885,886,887,891],{},"Read the ",[294,888,890],{"href":889},"/docs/getting-started","5-minute quick start"," for the full connect, subscribe, and publish flow.",[812,893,894,895,899],{},"Skip the plumbing with the ",[294,896,898],{"href":897},"/docs/high-level-sdks","chat blueprint",", which wraps rooms, typing, and history.",[812,901,902,903,907],{},"Building agent features into your app? See the ",[294,904,906],{"href":905},"/docs/agents","AI agents"," guide.",[909,910,911],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":30,"searchDepth":76,"depth":76,"links":913},[914,915,916,917,918],{"id":22,"depth":76,"text":23},{"id":52,"depth":76,"text":53},{"id":301,"depth":76,"text":302},{"id":861,"depth":76,"text":862},{"id":880,"depth":76,"text":881},"Integration","2026-07-30","A step-by-step guide to adding realtime messaging to a Next.js app with NoLag, including a server route that mints tokens so your API key never reaches the browser.",null,"md",{},"/blog/add-realtime-to-nextjs","7 min read",{"title":5,"description":921},"blog/add-realtime-to-nextjs","SdconHkiWGBrol7et6eneH0UVPuPjJyGU3cnsynv1Ok",1788160339717]