[{"data":1,"prerenderedAt":9525},["ShallowReactive",2],{"latest-articles":3,"/blog/real-time-vue-apps-part-1-polling-and-server-sent-events":5298,"related-/blog/real-time-vue-apps-part-1-polling-and-server-sent-events":6851,"i-fa6-solid:server":9518,"i-fa6-solid:laptop":9522},[4,1898,3412,5227],{"id":5,"title":6,"body":7,"category":1887,"date":1888,"description":1889,"draft":1890,"extension":1891,"image":1892,"meta":1893,"navigation":211,"path":1894,"seo":1895,"stem":1896,"tighten":1890,"__hash__":1897},"blog/blog/real-time-vue-apps-part-3-webrtc-and-peerjs.md","Real-Time Vue Apps, Part 3: WebRTC and PeerJS",{"type":8,"value":9,"toc":1875},"minimark",[10,25,38,41,46,49,72,76,82,85,88,105,108,111,127,132,135,138,146,149,154,164,260,263,277,281,288,292,299,305,320,324,330,333,342,345,349,355,358,362,365,368,374,381,384,388,391,1030,1033,1041,1045,1057,1060,1069,1072,1075,1082,1108,1111,1216,1222,1291,1300,1497,1500,1504,1507,1824,1827,1831,1834,1857,1860,1868,1871],[11,12,15],"callout",{"color":13,"icon":14},"#ff8a3d","fa6-solid:circle-info",[16,17,18,19,24],"p",{},"Hello there! This article is adapted from my talk at MadVue 2026, the amazing Vue conference that took place in Madrid, Spain. Make sure to check out ",[20,21,23],"a",{"href":22},"/madvue","this page"," for the slides, all the articles and some pics!",[16,26,27,28,32,33,37],{},"This is the third and final article in a three-part series about adding real-time features to your Vue and Nuxt applications. In ",[20,29,31],{"href":30},"/blog/real-time-vue-apps-part-1-polling-and-server-sent-events","Part 1"," we covered Server-Sent Events, and in ",[20,34,36],{"href":35},"/blog/real-time-vue-apps-part-2-websockets-and-real-time-databases","Part 2"," we covered WebSockets and real-time databases.",[16,39,40],{},"We ended Part 2 with a question: WebSockets are great, but you depend on a server. What if you don't want a server in the middle? What if you could connect directly to a peer?",[42,43,45],"h2",{"id":44},"why-peer-to-peer","Why Peer-to-Peer?",[16,47,48],{},"There are some very valid reasons to go for a peer-to-peer solution. Some of these might apply to you:",[50,51,52,60,66],"ul",{},[53,54,55,59],"li",{},[56,57,58],"strong",{},"Latency."," Imagine you're close to your peer, but the server is far away from both of you. Instead of sending data to the server and waiting for the server to relay it to your peer, you exchange the data directly. That can be much quicker in many scenarios.",[53,61,62,65],{},[56,63,64],{},"Privacy."," You share data directly with your peer, with no third party able to read or leak it... accidentally, of course.",[53,67,68,71],{},[56,69,70],{},"Cost."," With no server in the middle, you have no bandwidth bill. This is great for sharing audio and video, at no extra cost.",[42,73,75],{"id":74},"webrtc","WebRTC",[16,77,78,79,81],{},"We have a technology that ticks all those boxes: ",[56,80,75],{}," (Web Real-Time Communication).",[16,83,84],{},"With WebRTC you can send data peer to peer, end to end encrypted by default. And you're not limited to text: you can also share audio and video, which makes it the ideal solution for video calls.",[16,86,87],{},"Now, if you've ever tried to implement WebRTC, you may have stumbled into a wall of lovely acronyms:",[50,89,90,93,96,99,102],{},[53,91,92],{},"SDP (Session Description Protocol)",[53,94,95],{},"STUN (Session Traversal Utilities for NAT)",[53,97,98],{},"TURN (Traversal Using Relays around NAT)",[53,100,101],{},"ICE (Interactive Connectivity Establishment)",[53,103,104],{},"And... trickle ICE?",[16,106,107],{},"If it feels overwhelming... I don't blame you. To be honest, WebRTC is awesome, but it's kind of tricky.",[16,109,110],{},"So I have two things for you in this article:",[50,112,113,120],{},[53,114,115,116,119],{},"A ",[56,117,118],{},"quick explanation"," of all the scary parts.",[53,121,122,123,126],{},"A library that makes working with WebRTC ",[56,124,125],{},"way less complicated",".",[128,129,131],"h3",{"id":130},"how-does-webrtc-work","How Does WebRTC Work?",[16,133,134],{},"Let's start with the confusing part, and make it less confusing. By reviewing the flow, we'll understand what these acronyms mean.",[16,136,137],{},"Think of it this way. If you want to call me using WebRTC, what do you need? Well, you need to know at least two things:",[50,139,140,143],{},[53,141,142],{},"The codecs my browser supports, so we can share audio and video.",[53,144,145],{},"And where to find me: a network address.",[16,147,148],{},"And vice versa, I need to know the same about you. Peers have to exchange that information in a standard, predefined way that browsers can understand.",[150,151,153],"h4",{"id":152},"sdp","SDP",[16,155,156,157,159,160,163],{},"For that, we use an ",[56,158,153],{},", a ",[56,161,162],{},"Session Description Protocol",". It looks something like this:",[165,166,171],"pre",{"className":167,"code":168,"language":169,"meta":170,"style":170},"language-sh shiki shiki-themes monokai","# Metadata\nv=0\no=- 461173140043 2 IN IP4 127.0.0.1\ns=-\nt=0 0\n\n# Codecs\nm=audio 9 UDP/TLS/RTP/SAVPF 111\na=rtpmap:111 opus/48000/2\nm=video 9 UDP/TLS/RTP/SAVPF 96\na=rtpmap:96 VP8/90000\n\n# ICE (Interactive Connectivity Establishment) candidates\na=candidate:1 1 udp 2130706431 abc123.local 54321 typ host\n","sh","",[172,173,174,182,188,194,200,206,213,219,225,231,237,243,248,254],"code",{"__ignoreMap":170},[175,176,179],"span",{"class":177,"line":178},"line",1,[175,180,181],{},"# Metadata\n",[175,183,185],{"class":177,"line":184},2,[175,186,187],{},"v=0\n",[175,189,191],{"class":177,"line":190},3,[175,192,193],{},"o=- 461173140043 2 IN IP4 127.0.0.1\n",[175,195,197],{"class":177,"line":196},4,[175,198,199],{},"s=-\n",[175,201,203],{"class":177,"line":202},5,[175,204,205],{},"t=0 0\n",[175,207,209],{"class":177,"line":208},6,[175,210,212],{"emptyLinePlaceholder":211},true,"\n",[175,214,216],{"class":177,"line":215},7,[175,217,218],{},"# Codecs\n",[175,220,222],{"class":177,"line":221},8,[175,223,224],{},"m=audio 9 UDP/TLS/RTP/SAVPF 111\n",[175,226,228],{"class":177,"line":227},9,[175,229,230],{},"a=rtpmap:111 opus/48000/2\n",[175,232,234],{"class":177,"line":233},10,[175,235,236],{},"m=video 9 UDP/TLS/RTP/SAVPF 96\n",[175,238,240],{"class":177,"line":239},11,[175,241,242],{},"a=rtpmap:96 VP8/90000\n",[175,244,246],{"class":177,"line":245},12,[175,247,212],{"emptyLinePlaceholder":211},[175,249,251],{"class":177,"line":250},13,[175,252,253],{},"# ICE (Interactive Connectivity Establishment) candidates\n",[175,255,257],{"class":177,"line":256},14,[175,258,259],{},"a=candidate:1 1 udp 2130706431 abc123.local 54321 typ host\n",[16,261,262],{},"The SDP has a few parts:",[50,264,265,268,271],{},[53,266,267],{},"The first is just metadata, don't worry too much about that",[53,269,270],{},"Then the codecs: these are the ones my browser supports",[53,272,273,274],{},"Last but not least, the ",[56,275,276],{},"ICE candidates",[150,278,280],{"id":279},"ice","ICE",[16,282,283,284,287],{},"ICE stands for ",[56,285,286],{},"Interactive Connectivity Establishment",". These are all the possible network endpoints that can be used to connect to me. They are called candidates because the browsers pick the one that works best for both sides. For example, if two peers are on the same WiFi, they'll happily use their local IPs and skip the public internet entirely.",[150,289,291],{"id":290},"stun","STUN",[16,293,294,295,298],{},"Now, the browser can list all your local addresses, but it usually doesn't know your public IP. By default, your local address is even masked behind a fake ",[172,296,297],{},".local"," hostname (for privacy, so a random page can't fingerprint you by your network).",[16,300,301,302,304],{},"So how do you get your public IP? You ask someone outside. That's what a ",[56,303,95],{}," server is for: you send it a packet, and it pongs back \"this is the IP and port I see from the outside, that must be your public IP.\" That's it. Thanks to the STUN server, we can add a candidate with our public IP to the list of ICE candidates.",[165,306,308],{"className":167,"code":307,"language":169,"meta":170,"style":170},"# STUN server (stun.l.google.com:19302) gives me my public IP\na=candidate:2 1 udp 1694498815 203.0.113.1 54321 typ srflx raddr 0.0.0.0 rport 0\n",[172,309,310,315],{"__ignoreMap":170},[175,311,312],{"class":177,"line":178},[175,313,314],{},"# STUN server (stun.l.google.com:19302) gives me my public IP\n",[175,316,317],{"class":177,"line":184},[175,318,319],{},"a=candidate:2 1 udp 1694498815 203.0.113.1 54321 typ srflx raddr 0.0.0.0 rport 0\n",[150,321,323],{"id":322},"trickle-ice","Trickle ICE",[16,325,326,327,329],{},"Now, gathering all these candidates can take a few seconds. So you might resort to a technique known as ",[56,328,323],{},". You send the SDP first, and then send the candidates over as you discover them, over time, asynchronously.",[16,331,332],{},"In your SDP, you announce it like this:",[165,334,336],{"className":167,"code":335,"language":169,"meta":170,"style":170},"a=ice-options:trickle\n",[172,337,338],{"__ignoreMap":170},[175,339,340],{"class":177,"line":178},[175,341,335],{},[16,343,344],{},"And then you send each candidate to the peer as soon as the browser hands it to you.",[150,346,348],{"id":347},"turn","TURN",[16,350,351,352,354],{},"Sometimes peers can't reach each other directly because they're behind strict firewalls, symmetric NATs, or corporate networks. In those cases, you can decide to fall back to a ",[56,353,348],{}," server, which actually forwards the data between peers.",[16,356,357],{},"It's slower, it costs bandwidth, and it kind of defeats the \"no server in the middle\" promise. But it's the safety net when a direct connection just isn't possible. It's totally optional, so we can avoid opting in if you don't need it.",[128,359,361],{"id":360},"the-signaling-server","The Signaling Server",[16,363,364],{},"Alright! How do peers actually share their SDP and ICE candidates? Well, WebRTC doesn't care. The spec leaves that completely up to you. Write it on a napkin, send a carrier pigeon, whatever works, as long as the data gets to the other peer.",[16,366,367],{},"The practical answer, of course, is a server.",[369,370,371],"blockquote",{},[16,372,373],{},"Nico, you cheater! You told us we didn't need a server!",[16,375,376,377,380],{},"I hear you! You are right. But this server, which we call ",[56,378,379],{},"the signaling server,"," is only there so the peers can exchange connection data. Once they do, the server is out of the picture. It can go down, get unplugged, catch on fire. The peers keep talking.",[16,382,383],{},"Once the peers are connected, they can start sending each other text, audio, and video directly. No server in the middle!",[42,385,387],{"id":386},"now-the-code","Now, the Code!",[16,389,390],{},"Dealing with the native WebRTC API is, honestly, very tricky. Here's a taste of what setting up a connection looks like:",[165,392,396],{"className":393,"code":394,"language":395,"meta":170,"style":170},"language-js shiki shiki-themes monokai","const SIGNAL_URL = '/api/signal'\nconst POLL_INTERVAL = 1000\n\nconst config = {\n  iceServers: [\n    { urls: 'stun:stun.l.google.com:19302' },\n    { urls: 'turn:your.turn.server:3478', username: 'user', credential: 'pass' }\n  ]\n}\n\nconst peerId = crypto.randomUUID()\nconst roomId = new URLSearchParams(location.search).get('room') || 'default'\nconst isCaller = new URLSearchParams(location.search).get('role') === 'caller'\n\nconst pc = new RTCPeerConnection(config)\nlet channel = null\nlet remoteDescSet = false\nconst pendingCandidates = []\nconst seenMessages = new Set()\n\nfunction wireChannel(dc) {\n  channel = dc\n  dc.onopen = () => {\n    console.log('Data channel open')\n    dc.send(`hello from ${peerId}`)\n  }\n  dc.onclose = () => console.log('Data channel closed')\n  dc.onerror = (e) => console.error('Data channel error:', e)\n  dc.onmessage = (event) => console.log('Received:', event.data)\n}\n\nif (isCaller) {\n  wireChannel(pc.createDataChannel('chat'))\n} else {\n  pc.ondatachannel = (event) => wireChannel(event.channel)\n}\n\npc.onicecandidate = (event) => {\n  if (event.candidate) {\n    sendSignal({\n      type: 'candidate',\n      candidate: event.candidate.toJSON()\n    })\n  }\n}\n\n// And many, many more lines of code...\n","js",[172,397,398,416,429,433,445,450,461,483,488,493,497,516,552,582,586,604,618,631,644,661,666,684,695,715,732,760,766,792,825,856,861,866,875,895,906,930,935,940,961,970,979,991,1002,1008,1013,1018,1023],{"__ignoreMap":170},[175,399,400,404,408,412],{"class":177,"line":178},[175,401,403],{"class":402},"sOx1s","const",[175,405,407],{"class":406},"sCdxs"," SIGNAL_URL ",[175,409,411],{"class":410},"s8I7P","=",[175,413,415],{"class":414},"s_Ekj"," '/api/signal'\n",[175,417,418,420,423,425],{"class":177,"line":184},[175,419,403],{"class":402},[175,421,422],{"class":406}," POLL_INTERVAL ",[175,424,411],{"class":410},[175,426,428],{"class":427},"s7s5_"," 1000\n",[175,430,431],{"class":177,"line":190},[175,432,212],{"emptyLinePlaceholder":211},[175,434,435,437,440,442],{"class":177,"line":196},[175,436,403],{"class":402},[175,438,439],{"class":406}," config ",[175,441,411],{"class":410},[175,443,444],{"class":406}," {\n",[175,446,447],{"class":177,"line":202},[175,448,449],{"class":406},"  iceServers: [\n",[175,451,452,455,458],{"class":177,"line":208},[175,453,454],{"class":406},"    { urls: ",[175,456,457],{"class":414},"'stun:stun.l.google.com:19302'",[175,459,460],{"class":406}," },\n",[175,462,463,465,468,471,474,477,480],{"class":177,"line":215},[175,464,454],{"class":406},[175,466,467],{"class":414},"'turn:your.turn.server:3478'",[175,469,470],{"class":406},", username: ",[175,472,473],{"class":414},"'user'",[175,475,476],{"class":406},", credential: ",[175,478,479],{"class":414},"'pass'",[175,481,482],{"class":406}," }\n",[175,484,485],{"class":177,"line":221},[175,486,487],{"class":406},"  ]\n",[175,489,490],{"class":177,"line":227},[175,491,492],{"class":406},"}\n",[175,494,495],{"class":177,"line":233},[175,496,212],{"emptyLinePlaceholder":211},[175,498,499,501,504,506,509,513],{"class":177,"line":239},[175,500,403],{"class":402},[175,502,503],{"class":406}," peerId ",[175,505,411],{"class":410},[175,507,508],{"class":406}," crypto.",[175,510,512],{"class":511},"sHkqI","randomUUID",[175,514,515],{"class":406},"()\n",[175,517,518,520,523,525,528,531,534,537,540,543,546,549],{"class":177,"line":245},[175,519,403],{"class":402},[175,521,522],{"class":406}," roomId ",[175,524,411],{"class":410},[175,526,527],{"class":410}," new",[175,529,530],{"class":511}," URLSearchParams",[175,532,533],{"class":406},"(location.search).",[175,535,536],{"class":511},"get",[175,538,539],{"class":406},"(",[175,541,542],{"class":414},"'room'",[175,544,545],{"class":406},") ",[175,547,548],{"class":410},"||",[175,550,551],{"class":414}," 'default'\n",[175,553,554,556,559,561,563,565,567,569,571,574,576,579],{"class":177,"line":250},[175,555,403],{"class":402},[175,557,558],{"class":406}," isCaller ",[175,560,411],{"class":410},[175,562,527],{"class":410},[175,564,530],{"class":511},[175,566,533],{"class":406},[175,568,536],{"class":511},[175,570,539],{"class":406},[175,572,573],{"class":414},"'role'",[175,575,545],{"class":406},[175,577,578],{"class":410},"===",[175,580,581],{"class":414}," 'caller'\n",[175,583,584],{"class":177,"line":256},[175,585,212],{"emptyLinePlaceholder":211},[175,587,589,591,594,596,598,601],{"class":177,"line":588},15,[175,590,403],{"class":402},[175,592,593],{"class":406}," pc ",[175,595,411],{"class":410},[175,597,527],{"class":410},[175,599,600],{"class":511}," RTCPeerConnection",[175,602,603],{"class":406},"(config)\n",[175,605,607,610,613,615],{"class":177,"line":606},16,[175,608,609],{"class":402},"let",[175,611,612],{"class":406}," channel ",[175,614,411],{"class":410},[175,616,617],{"class":427}," null\n",[175,619,621,623,626,628],{"class":177,"line":620},17,[175,622,609],{"class":402},[175,624,625],{"class":406}," remoteDescSet ",[175,627,411],{"class":410},[175,629,630],{"class":427}," false\n",[175,632,634,636,639,641],{"class":177,"line":633},18,[175,635,403],{"class":402},[175,637,638],{"class":406}," pendingCandidates ",[175,640,411],{"class":410},[175,642,643],{"class":406}," []\n",[175,645,647,649,652,654,656,659],{"class":177,"line":646},19,[175,648,403],{"class":402},[175,650,651],{"class":406}," seenMessages ",[175,653,411],{"class":410},[175,655,527],{"class":410},[175,657,658],{"class":511}," Set",[175,660,515],{"class":406},[175,662,664],{"class":177,"line":663},20,[175,665,212],{"emptyLinePlaceholder":211},[175,667,669,672,675,677,681],{"class":177,"line":668},21,[175,670,671],{"class":402},"function",[175,673,674],{"class":511}," wireChannel",[175,676,539],{"class":406},[175,678,680],{"class":679},"sW0Xf","dc",[175,682,683],{"class":406},") {\n",[175,685,687,690,692],{"class":177,"line":686},22,[175,688,689],{"class":406},"  channel ",[175,691,411],{"class":410},[175,693,694],{"class":406}," dc\n",[175,696,698,701,704,707,710,713],{"class":177,"line":697},23,[175,699,700],{"class":406},"  dc.",[175,702,703],{"class":511},"onopen",[175,705,706],{"class":410}," =",[175,708,709],{"class":406}," () ",[175,711,712],{"class":402},"=>",[175,714,444],{"class":406},[175,716,718,721,724,726,729],{"class":177,"line":717},24,[175,719,720],{"class":406},"    console.",[175,722,723],{"class":511},"log",[175,725,539],{"class":406},[175,727,728],{"class":414},"'Data channel open'",[175,730,731],{"class":406},")\n",[175,733,735,738,741,743,746,749,752,755,758],{"class":177,"line":734},25,[175,736,737],{"class":406},"    dc.",[175,739,740],{"class":511},"send",[175,742,539],{"class":406},[175,744,745],{"class":414},"`hello from ",[175,747,748],{"class":410},"${",[175,750,751],{"class":406},"peerId",[175,753,754],{"class":410},"}",[175,756,757],{"class":414},"`",[175,759,731],{"class":406},[175,761,763],{"class":177,"line":762},26,[175,764,765],{"class":406},"  }\n",[175,767,769,771,774,776,778,780,783,785,787,790],{"class":177,"line":768},27,[175,770,700],{"class":406},[175,772,773],{"class":511},"onclose",[175,775,706],{"class":410},[175,777,709],{"class":406},[175,779,712],{"class":402},[175,781,782],{"class":406}," console.",[175,784,723],{"class":511},[175,786,539],{"class":406},[175,788,789],{"class":414},"'Data channel closed'",[175,791,731],{"class":406},[175,793,795,797,800,802,805,808,810,812,814,817,819,822],{"class":177,"line":794},28,[175,796,700],{"class":406},[175,798,799],{"class":511},"onerror",[175,801,706],{"class":410},[175,803,804],{"class":406}," (",[175,806,807],{"class":679},"e",[175,809,545],{"class":406},[175,811,712],{"class":402},[175,813,782],{"class":406},[175,815,816],{"class":511},"error",[175,818,539],{"class":406},[175,820,821],{"class":414},"'Data channel error:'",[175,823,824],{"class":406},", e)\n",[175,826,828,830,833,835,837,840,842,844,846,848,850,853],{"class":177,"line":827},29,[175,829,700],{"class":406},[175,831,832],{"class":511},"onmessage",[175,834,706],{"class":410},[175,836,804],{"class":406},[175,838,839],{"class":679},"event",[175,841,545],{"class":406},[175,843,712],{"class":402},[175,845,782],{"class":406},[175,847,723],{"class":511},[175,849,539],{"class":406},[175,851,852],{"class":414},"'Received:'",[175,854,855],{"class":406},", event.data)\n",[175,857,859],{"class":177,"line":858},30,[175,860,492],{"class":406},[175,862,864],{"class":177,"line":863},31,[175,865,212],{"emptyLinePlaceholder":211},[175,867,869,872],{"class":177,"line":868},32,[175,870,871],{"class":410},"if",[175,873,874],{"class":406}," (isCaller) {\n",[175,876,878,881,884,887,889,892],{"class":177,"line":877},33,[175,879,880],{"class":511},"  wireChannel",[175,882,883],{"class":406},"(pc.",[175,885,886],{"class":511},"createDataChannel",[175,888,539],{"class":406},[175,890,891],{"class":414},"'chat'",[175,893,894],{"class":406},"))\n",[175,896,898,901,904],{"class":177,"line":897},34,[175,899,900],{"class":406},"} ",[175,902,903],{"class":410},"else",[175,905,444],{"class":406},[175,907,909,912,915,917,919,921,923,925,927],{"class":177,"line":908},35,[175,910,911],{"class":406},"  pc.",[175,913,914],{"class":511},"ondatachannel",[175,916,706],{"class":410},[175,918,804],{"class":406},[175,920,839],{"class":679},[175,922,545],{"class":406},[175,924,712],{"class":402},[175,926,674],{"class":511},[175,928,929],{"class":406},"(event.channel)\n",[175,931,933],{"class":177,"line":932},36,[175,934,492],{"class":406},[175,936,938],{"class":177,"line":937},37,[175,939,212],{"emptyLinePlaceholder":211},[175,941,943,946,949,951,953,955,957,959],{"class":177,"line":942},38,[175,944,945],{"class":406},"pc.",[175,947,948],{"class":511},"onicecandidate",[175,950,706],{"class":410},[175,952,804],{"class":406},[175,954,839],{"class":679},[175,956,545],{"class":406},[175,958,712],{"class":402},[175,960,444],{"class":406},[175,962,964,967],{"class":177,"line":963},39,[175,965,966],{"class":410},"  if",[175,968,969],{"class":406}," (event.candidate) {\n",[175,971,973,976],{"class":177,"line":972},40,[175,974,975],{"class":511},"    sendSignal",[175,977,978],{"class":406},"({\n",[175,980,982,985,988],{"class":177,"line":981},41,[175,983,984],{"class":406},"      type: ",[175,986,987],{"class":414},"'candidate'",[175,989,990],{"class":406},",\n",[175,992,994,997,1000],{"class":177,"line":993},42,[175,995,996],{"class":406},"      candidate: event.candidate.",[175,998,999],{"class":511},"toJSON",[175,1001,515],{"class":406},[175,1003,1005],{"class":177,"line":1004},43,[175,1006,1007],{"class":406},"    })\n",[175,1009,1011],{"class":177,"line":1010},44,[175,1012,765],{"class":406},[175,1014,1016],{"class":177,"line":1015},45,[175,1017,492],{"class":406},[175,1019,1021],{"class":177,"line":1020},46,[175,1022,212],{"emptyLinePlaceholder":211},[175,1024,1026],{"class":177,"line":1025},47,[175,1027,1029],{"class":1028},"snpHw","// And many, many more lines of code...\n",[16,1031,1032],{},"In this snippet, we've only scratched the surface of the WebRTC API. To handle errors, edge cases and race conditions, you'd need to write a lot more.",[16,1034,1035,1036,1040],{},"However, my intention is ",[1037,1038,1039],"em",{},"not"," for you to remember this snippet, because, as promised, there's a much easier way.",[42,1042,1044],{"id":1043},"peerjs-webrtc-without-the-boilerplate","PeerJS: WebRTC Without the Boilerplate",[16,1046,1047,1048,1056],{},"I promised you a better way to work with WebRTC, and here it is: ",[20,1049,1053],{"href":1050,"rel":1051},"https://peerjs.com/",[1052],"nofollow",[56,1054,1055],{},"PeerJS",". It's a free, open-source library that takes all the complex orchestration off your shoulders.",[16,1058,1059],{},"You can install it like this:",[165,1061,1063],{"className":167,"code":1062,"language":169,"meta":170,"style":170},"npm install peerjs\n",[172,1064,1065],{"__ignoreMap":170},[175,1066,1067],{"class":177,"line":178},[175,1068,1062],{},[16,1070,1071],{},"... and be ready to use it.",[16,1073,1074],{},"Instead of worrying about SDPs, ICE candidates, and all that, you just need to pick an ID. That's the core concept of PeerJS: peers are identified by a unique ID.",[16,1076,1077,1078,1081],{},"To use an ID, you just instantiate a ",[172,1079,1080],{},"Peer"," object passing the ID of choice as a parameter:",[165,1083,1085],{"className":393,"code":1084,"language":395,"meta":170,"style":170},"const peer = new Peer('bob')\n",[172,1086,1087],{"__ignoreMap":170},[175,1088,1089,1091,1094,1096,1098,1101,1103,1106],{"class":177,"line":178},[175,1090,403],{"class":402},[175,1092,1093],{"class":406}," peer ",[175,1095,411],{"class":410},[175,1097,527],{"class":410},[175,1099,1100],{"class":511}," Peer",[175,1102,539],{"class":406},[175,1104,1105],{"class":414},"'bob'",[175,1107,731],{"class":406},[16,1109,1110],{},"Keep in mind that the ID must be unique per server. As server, you can use the free cloud service PeerJS provides. But I strongly recommend self-hosting your own: the server is free, open source and easy to set up.",[165,1112,1116],{"className":1113,"code":1114,"language":1115,"meta":170,"style":170},"language-shell shiki shiki-themes monokai","# Install Node.js (LTS) on Ubuntu\ncurl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -\nsudo apt-get install -y nodejs\n\n# Install the PeerJS server globally\nsudo npm install -g peer\n\n# Run it on port 9000 under the /myapp path\npeerjs --port 9000 --path /myapp\n","shell",[172,1117,1118,1123,1149,1166,1170,1175,1190,1194,1199],{"__ignoreMap":170},[175,1119,1120],{"class":177,"line":178},[175,1121,1122],{"class":1028},"# Install Node.js (LTS) on Ubuntu\n",[175,1124,1125,1128,1131,1134,1137,1140,1143,1146],{"class":177,"line":184},[175,1126,1127],{"class":511},"curl",[175,1129,1130],{"class":427}," -fsSL",[175,1132,1133],{"class":414}," https://deb.nodesource.com/setup_lts.x",[175,1135,1136],{"class":410}," |",[175,1138,1139],{"class":511}," sudo",[175,1141,1142],{"class":427}," -E",[175,1144,1145],{"class":414}," bash",[175,1147,1148],{"class":414}," -\n",[175,1150,1151,1154,1157,1160,1163],{"class":177,"line":190},[175,1152,1153],{"class":511},"sudo",[175,1155,1156],{"class":414}," apt-get",[175,1158,1159],{"class":414}," install",[175,1161,1162],{"class":427}," -y",[175,1164,1165],{"class":414}," nodejs\n",[175,1167,1168],{"class":177,"line":196},[175,1169,212],{"emptyLinePlaceholder":211},[175,1171,1172],{"class":177,"line":202},[175,1173,1174],{"class":1028},"# Install the PeerJS server globally\n",[175,1176,1177,1179,1182,1184,1187],{"class":177,"line":208},[175,1178,1153],{"class":511},[175,1180,1181],{"class":414}," npm",[175,1183,1159],{"class":414},[175,1185,1186],{"class":427}," -g",[175,1188,1189],{"class":414}," peer\n",[175,1191,1192],{"class":177,"line":215},[175,1193,212],{"emptyLinePlaceholder":211},[175,1195,1196],{"class":177,"line":221},[175,1197,1198],{"class":1028},"# Run it on port 9000 under the /myapp path\n",[175,1200,1201,1204,1207,1210,1213],{"class":177,"line":227},[175,1202,1203],{"class":511},"peerjs",[175,1205,1206],{"class":427}," --port",[175,1208,1209],{"class":427}," 9000",[175,1211,1212],{"class":427}," --path",[175,1214,1215],{"class":414}," /myapp\n",[16,1217,1218,1219,1221],{},"Then, on the client, point your ",[172,1220,1080],{}," instance at it:",[165,1223,1225],{"className":393,"code":1224,"language":395,"meta":170,"style":170},"const peer = new Peer('bob', {\n  host: 'example.com',\n  port: 9000,\n  path: '/myapp',\n  secure: true,\n})\n",[172,1226,1227,1246,1256,1266,1276,1286],{"__ignoreMap":170},[175,1228,1229,1231,1233,1235,1237,1239,1241,1243],{"class":177,"line":178},[175,1230,403],{"class":402},[175,1232,1093],{"class":406},[175,1234,411],{"class":410},[175,1236,527],{"class":410},[175,1238,1100],{"class":511},[175,1240,539],{"class":406},[175,1242,1105],{"class":414},[175,1244,1245],{"class":406},", {\n",[175,1247,1248,1251,1254],{"class":177,"line":184},[175,1249,1250],{"class":406},"  host: ",[175,1252,1253],{"class":414},"'example.com'",[175,1255,990],{"class":406},[175,1257,1258,1261,1264],{"class":177,"line":190},[175,1259,1260],{"class":406},"  port: ",[175,1262,1263],{"class":427},"9000",[175,1265,990],{"class":406},[175,1267,1268,1271,1274],{"class":177,"line":196},[175,1269,1270],{"class":406},"  path: ",[175,1272,1273],{"class":414},"'/myapp'",[175,1275,990],{"class":406},[175,1277,1278,1281,1284],{"class":177,"line":202},[175,1279,1280],{"class":406},"  secure: ",[175,1282,1283],{"class":427},"true",[175,1285,990],{"class":406},[175,1287,1288],{"class":177,"line":208},[175,1289,1290],{"class":406},"})\n",[16,1292,1293,1294,1296,1297,1299],{},"Here we create our ",[172,1295,1080],{}," object, passing an ID of our choice, in this case ",[172,1298,1105],{},". Then we connect to Alice, listen to her messages, and send her one:",[165,1301,1303],{"className":393,"code":1302,"language":395,"meta":170,"style":170},"import Peer from 'peerjs'\n\n// Select your ID\nconst peer = new Peer('bob')\n\npeer.on('open', () => {\n\n  // Connect to Alice\n  const conn = peer.connect('alice')\n\n  conn.on('open', () => {\n\n    // Listen to her messages\n    conn.on('data', (data) => console.log('Received', data))\n\n    // Send her a message\n    conn.send('Hello!')\n  })\n})\n",[172,1304,1305,1319,1323,1328,1346,1350,1370,1374,1379,1402,1406,1423,1427,1432,1466,1470,1475,1488,1493],{"__ignoreMap":170},[175,1306,1307,1310,1313,1316],{"class":177,"line":178},[175,1308,1309],{"class":410},"import",[175,1311,1312],{"class":406}," Peer ",[175,1314,1315],{"class":410},"from",[175,1317,1318],{"class":414}," 'peerjs'\n",[175,1320,1321],{"class":177,"line":184},[175,1322,212],{"emptyLinePlaceholder":211},[175,1324,1325],{"class":177,"line":190},[175,1326,1327],{"class":1028},"// Select your ID\n",[175,1329,1330,1332,1334,1336,1338,1340,1342,1344],{"class":177,"line":196},[175,1331,403],{"class":402},[175,1333,1093],{"class":406},[175,1335,411],{"class":410},[175,1337,527],{"class":410},[175,1339,1100],{"class":511},[175,1341,539],{"class":406},[175,1343,1105],{"class":414},[175,1345,731],{"class":406},[175,1347,1348],{"class":177,"line":202},[175,1349,212],{"emptyLinePlaceholder":211},[175,1351,1352,1355,1358,1360,1363,1366,1368],{"class":177,"line":208},[175,1353,1354],{"class":406},"peer.",[175,1356,1357],{"class":511},"on",[175,1359,539],{"class":406},[175,1361,1362],{"class":414},"'open'",[175,1364,1365],{"class":406},", () ",[175,1367,712],{"class":402},[175,1369,444],{"class":406},[175,1371,1372],{"class":177,"line":215},[175,1373,212],{"emptyLinePlaceholder":211},[175,1375,1376],{"class":177,"line":221},[175,1377,1378],{"class":1028},"  // Connect to Alice\n",[175,1380,1381,1384,1387,1389,1392,1395,1397,1400],{"class":177,"line":227},[175,1382,1383],{"class":402},"  const",[175,1385,1386],{"class":406}," conn ",[175,1388,411],{"class":410},[175,1390,1391],{"class":406}," peer.",[175,1393,1394],{"class":511},"connect",[175,1396,539],{"class":406},[175,1398,1399],{"class":414},"'alice'",[175,1401,731],{"class":406},[175,1403,1404],{"class":177,"line":233},[175,1405,212],{"emptyLinePlaceholder":211},[175,1407,1408,1411,1413,1415,1417,1419,1421],{"class":177,"line":239},[175,1409,1410],{"class":406},"  conn.",[175,1412,1357],{"class":511},[175,1414,539],{"class":406},[175,1416,1362],{"class":414},[175,1418,1365],{"class":406},[175,1420,712],{"class":402},[175,1422,444],{"class":406},[175,1424,1425],{"class":177,"line":245},[175,1426,212],{"emptyLinePlaceholder":211},[175,1428,1429],{"class":177,"line":250},[175,1430,1431],{"class":1028},"    // Listen to her messages\n",[175,1433,1434,1437,1439,1441,1444,1447,1450,1452,1454,1456,1458,1460,1463],{"class":177,"line":256},[175,1435,1436],{"class":406},"    conn.",[175,1438,1357],{"class":511},[175,1440,539],{"class":406},[175,1442,1443],{"class":414},"'data'",[175,1445,1446],{"class":406},", (",[175,1448,1449],{"class":679},"data",[175,1451,545],{"class":406},[175,1453,712],{"class":402},[175,1455,782],{"class":406},[175,1457,723],{"class":511},[175,1459,539],{"class":406},[175,1461,1462],{"class":414},"'Received'",[175,1464,1465],{"class":406},", data))\n",[175,1467,1468],{"class":177,"line":588},[175,1469,212],{"emptyLinePlaceholder":211},[175,1471,1472],{"class":177,"line":606},[175,1473,1474],{"class":1028},"    // Send her a message\n",[175,1476,1477,1479,1481,1483,1486],{"class":177,"line":620},[175,1478,1436],{"class":406},[175,1480,740],{"class":511},[175,1482,539],{"class":406},[175,1484,1485],{"class":414},"'Hello!'",[175,1487,731],{"class":406},[175,1489,1490],{"class":177,"line":633},[175,1491,1492],{"class":406},"  })\n",[175,1494,1495],{"class":177,"line":646},[175,1496,1290],{"class":406},[16,1498,1499],{},"It's super easy: get your ID, connect to your peer, start sending and receiving data. You can have your own peer-to-peer, end-to-end encrypted chat working in almost no time.",[128,1501,1503],{"id":1502},"sending-audio-and-video","Sending Audio and Video",[16,1505,1506],{},"The data channel is cool, but remember, we can also make audio and video calls. With PeerJS, sharing a video stream is just as approachable:",[165,1508,1512],{"className":1509,"code":1510,"language":1511,"meta":170,"style":170},"language-vue shiki shiki-themes monokai","\u003Cscript setup>\nimport Peer from 'peerjs'\n\nconst localVideo = useTemplateRef('local')\nconst remoteVideo = useTemplateRef('remote')\n\nonMounted(async () => {\n  const stream = await navigator.mediaDevices.getUserMedia({\n    video: true,\n    audio: true\n  })\n\n  localVideo.value.muted = true\n  localVideo.value.srcObject = stream\n\n  new Peer('bob').on('call', c => {\n    c.answer(stream)\n    c.on('stream', s => remoteVideo.value.srcObject = s)\n  })\n})\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cvideo ref=\"local\" autoplay playsinline />\n  \u003Cvideo ref=\"remote\" autoplay playsinline />\n\u003C/template>\n","vue",[172,1513,1514,1528,1538,1542,1561,1579,1583,1599,1619,1628,1636,1640,1644,1654,1664,1668,1700,1711,1737,1741,1745,1754,1758,1767,1795,1816],{"__ignoreMap":170},[175,1515,1516,1519,1522,1525],{"class":177,"line":178},[175,1517,1518],{"class":406},"\u003C",[175,1520,1521],{"class":410},"script",[175,1523,1524],{"class":511}," setup",[175,1526,1527],{"class":406},">\n",[175,1529,1530,1532,1534,1536],{"class":177,"line":184},[175,1531,1309],{"class":410},[175,1533,1312],{"class":406},[175,1535,1315],{"class":410},[175,1537,1318],{"class":414},[175,1539,1540],{"class":177,"line":190},[175,1541,212],{"emptyLinePlaceholder":211},[175,1543,1544,1546,1549,1551,1554,1556,1559],{"class":177,"line":196},[175,1545,403],{"class":402},[175,1547,1548],{"class":406}," localVideo ",[175,1550,411],{"class":410},[175,1552,1553],{"class":511}," useTemplateRef",[175,1555,539],{"class":406},[175,1557,1558],{"class":414},"'local'",[175,1560,731],{"class":406},[175,1562,1563,1565,1568,1570,1572,1574,1577],{"class":177,"line":202},[175,1564,403],{"class":402},[175,1566,1567],{"class":406}," remoteVideo ",[175,1569,411],{"class":410},[175,1571,1553],{"class":511},[175,1573,539],{"class":406},[175,1575,1576],{"class":414},"'remote'",[175,1578,731],{"class":406},[175,1580,1581],{"class":177,"line":208},[175,1582,212],{"emptyLinePlaceholder":211},[175,1584,1585,1588,1590,1593,1595,1597],{"class":177,"line":215},[175,1586,1587],{"class":511},"onMounted",[175,1589,539],{"class":406},[175,1591,1592],{"class":410},"async",[175,1594,709],{"class":406},[175,1596,712],{"class":402},[175,1598,444],{"class":406},[175,1600,1601,1603,1606,1608,1611,1614,1617],{"class":177,"line":221},[175,1602,1383],{"class":402},[175,1604,1605],{"class":406}," stream ",[175,1607,411],{"class":410},[175,1609,1610],{"class":410}," await",[175,1612,1613],{"class":406}," navigator.mediaDevices.",[175,1615,1616],{"class":511},"getUserMedia",[175,1618,978],{"class":406},[175,1620,1621,1624,1626],{"class":177,"line":227},[175,1622,1623],{"class":406},"    video: ",[175,1625,1283],{"class":427},[175,1627,990],{"class":406},[175,1629,1630,1633],{"class":177,"line":233},[175,1631,1632],{"class":406},"    audio: ",[175,1634,1635],{"class":427},"true\n",[175,1637,1638],{"class":177,"line":239},[175,1639,1492],{"class":406},[175,1641,1642],{"class":177,"line":245},[175,1643,212],{"emptyLinePlaceholder":211},[175,1645,1646,1649,1651],{"class":177,"line":250},[175,1647,1648],{"class":406},"  localVideo.value.muted ",[175,1650,411],{"class":410},[175,1652,1653],{"class":427}," true\n",[175,1655,1656,1659,1661],{"class":177,"line":256},[175,1657,1658],{"class":406},"  localVideo.value.srcObject ",[175,1660,411],{"class":410},[175,1662,1663],{"class":406}," stream\n",[175,1665,1666],{"class":177,"line":588},[175,1667,212],{"emptyLinePlaceholder":211},[175,1669,1670,1673,1675,1677,1679,1682,1684,1686,1689,1692,1695,1698],{"class":177,"line":606},[175,1671,1672],{"class":410},"  new",[175,1674,1100],{"class":511},[175,1676,539],{"class":406},[175,1678,1105],{"class":414},[175,1680,1681],{"class":406},").",[175,1683,1357],{"class":511},[175,1685,539],{"class":406},[175,1687,1688],{"class":414},"'call'",[175,1690,1691],{"class":406},", ",[175,1693,1694],{"class":679},"c",[175,1696,1697],{"class":402}," =>",[175,1699,444],{"class":406},[175,1701,1702,1705,1708],{"class":177,"line":620},[175,1703,1704],{"class":406},"    c.",[175,1706,1707],{"class":511},"answer",[175,1709,1710],{"class":406},"(stream)\n",[175,1712,1713,1715,1717,1719,1722,1724,1727,1729,1732,1734],{"class":177,"line":633},[175,1714,1704],{"class":406},[175,1716,1357],{"class":511},[175,1718,539],{"class":406},[175,1720,1721],{"class":414},"'stream'",[175,1723,1691],{"class":406},[175,1725,1726],{"class":679},"s",[175,1728,1697],{"class":402},[175,1730,1731],{"class":406}," remoteVideo.value.srcObject ",[175,1733,411],{"class":410},[175,1735,1736],{"class":406}," s)\n",[175,1738,1739],{"class":177,"line":646},[175,1740,1492],{"class":406},[175,1742,1743],{"class":177,"line":663},[175,1744,1290],{"class":406},[175,1746,1747,1750,1752],{"class":177,"line":668},[175,1748,1749],{"class":406},"\u003C/",[175,1751,1521],{"class":410},[175,1753,1527],{"class":406},[175,1755,1756],{"class":177,"line":686},[175,1757,212],{"emptyLinePlaceholder":211},[175,1759,1760,1762,1765],{"class":177,"line":697},[175,1761,1518],{"class":406},[175,1763,1764],{"class":410},"template",[175,1766,1527],{"class":406},[175,1768,1769,1772,1775,1778,1780,1783,1786,1789,1793],{"class":177,"line":717},[175,1770,1771],{"class":406},"  \u003C",[175,1773,1774],{"class":410},"video",[175,1776,1777],{"class":511}," ref",[175,1779,411],{"class":406},[175,1781,1782],{"class":414},"\"local\"",[175,1784,1785],{"class":511}," autoplay",[175,1787,1788],{"class":511}," playsinline",[175,1790,1792],{"class":1791},"sHkYM"," /",[175,1794,1527],{"class":406},[175,1796,1797,1799,1801,1803,1805,1808,1810,1812,1814],{"class":177,"line":734},[175,1798,1771],{"class":406},[175,1800,1774],{"class":410},[175,1802,1777],{"class":511},[175,1804,411],{"class":406},[175,1806,1807],{"class":414},"\"remote\"",[175,1809,1785],{"class":511},[175,1811,1788],{"class":511},[175,1813,1792],{"class":1791},[175,1815,1527],{"class":406},[175,1817,1818,1820,1822],{"class":177,"line":762},[175,1819,1749],{"class":406},[175,1821,1764],{"class":410},[175,1823,1527],{"class":406},[16,1825,1826],{},"You can share your webcam, your screen, whatever. Compared to the native API, it's a breeze.",[42,1828,1830],{"id":1829},"wrapping-up-the-series","Wrapping Up the Series",[16,1832,1833],{},"And that's a wrap! Across these three articles, we went from the bad old days of polling all the way to peer-to-peer video calls:",[50,1835,1836,1844,1851],{},[53,1837,1838,1843],{},[56,1839,1840,1842],{},[20,1841,31],{"href":30},":"," Polling and Server-Sent Events, for when the server needs to push to the client.",[53,1845,1846,1850],{},[56,1847,1848,1842],{},[20,1849,36],{"href":35}," WebSockets and real-time databases, for two-way communication.",[53,1852,1853,1856],{},[56,1854,1855],{},"Part 3 (this one):"," WebRTC and PeerJS, for direct, server-less, end-to-end encrypted connections.",[16,1858,1859],{},"My advice? Pick the simplest tool that solves your problem. Need notifications or a live dashboard? SSE. Need a chat or collaborative drawing? WebSockets. Need direct audio and video, or you really don't want a server in the middle? WebRTC with PeerJS.",[16,1861,1862,1863],{},"Did you enjoy the series? ",[20,1864,1867],{"href":1865,"rel":1866},"https://x.com/nicodevs",[1052],"Let me know!",[16,1869,1870],{},"See you next time!",[1872,1873,1874],"style",{},"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 pre.shiki code .sOx1s, html code.shiki .sOx1s{--shiki-default:#66D9EF;--shiki-default-font-style:italic}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s8I7P, html code.shiki .s8I7P{--shiki-default:#F92672}html pre.shiki code .s_Ekj, html code.shiki .s_Ekj{--shiki-default:#E6DB74}html pre.shiki code .s7s5_, html code.shiki .s7s5_{--shiki-default:#AE81FF}html pre.shiki code .sHkqI, html code.shiki .sHkqI{--shiki-default:#A6E22E}html pre.shiki code .sW0Xf, html code.shiki .sW0Xf{--shiki-default:#FD971F;--shiki-default-font-style:italic}html pre.shiki code .snpHw, html code.shiki .snpHw{--shiki-default:#88846F}html pre.shiki code .sHkYM, html code.shiki .sHkYM{--shiki-default:#F44747}",{"title":170,"searchDepth":184,"depth":184,"links":1876},[1877,1878,1882,1883,1886],{"id":44,"depth":184,"text":45},{"id":74,"depth":184,"text":75,"children":1879},[1880,1881],{"id":130,"depth":190,"text":131},{"id":360,"depth":190,"text":361},{"id":386,"depth":184,"text":387},{"id":1043,"depth":184,"text":1044,"children":1884},[1885],{"id":1502,"depth":190,"text":1503},{"id":1829,"depth":184,"text":1830},"Vue","2026-05-28","Direct, peer-to-peer connections with WebRTC: a friendly tour of SDP, ICE, STUN, and TURN, plus how PeerJS makes building chats and video calls actually fun.",false,"md","/img/blog/real-time-vue-apps-part-3-webrtc-and-peerjs.png",{},"/blog/real-time-vue-apps-part-3-webrtc-and-peerjs",{"title":6,"description":1889},"blog/real-time-vue-apps-part-3-webrtc-and-peerjs","Aw4SqQIx0OVgJaQ6l6QhYeAVLecoQbYaYgIZOVOpgqc",{"id":1899,"title":1900,"body":1901,"category":1887,"date":3405,"description":3406,"draft":1890,"extension":1891,"image":3407,"meta":3408,"navigation":211,"path":35,"seo":3409,"stem":3410,"tighten":1890,"__hash__":3411},"blog/blog/real-time-vue-apps-part-2-websockets-and-real-time-databases.md","Real-Time Vue Apps, Part 2: WebSockets and Real-Time Databases",{"type":8,"value":1902,"toc":3396},[1903,1910,1924,1928,1935,1946,1949,1952,1956,1967,1983,1998,2001,2005,2030,2036,2039,2048,2055,2174,2191,2204,2210,2368,2375,2381,2384,2674,2684,2687,2691,2694,2703,2778,2793,2861,2868,2872,2875,2886,2889,2892,3010,3016,3024,3048,3123,3132,3140,3158,3365,3368,3372,3378,3381,3390,3393],[11,1904,1906],{"color":1905,"icon":14},"#7a3cff",[16,1907,18,1908,24],{},[20,1909,23],{"href":22},[16,1911,1912,1913,1915,1916,1919,1920,1923],{},"Let's continue exploring how can we implement real-time features in our Vue and Nuxt apps. In ",[20,1914,31],{"href":30},", we discovered that ",[56,1917,1918],{},"polling"," is not great and that ",[56,1921,1922],{},"SSE (Server-Sent Events)"," are a great alternative when you can just use a classic HTTP connection.",[42,1925,1927],{"id":1926},"websockets","WebSockets",[16,1929,1930,1931,1934],{},"But there's another option for real-time apps, perhaps the most popular one: ",[56,1932,1933],{},"WebSockets."," They allow not only sending data from server to client, but sending data from client to server as well. They are a bi-directional way, and the ideal solution for many apps in need of real-time features:",[50,1936,1937,1940,1943],{},[53,1938,1939],{},"Chats and chatbots",[53,1941,1942],{},"Collaborative apps (like Notion, Figma and more)",[53,1944,1945],{},"Multiplayer games",[16,1947,1948],{},"... and any other scenario where you need a fast way of sending and receiving data from the server.",[16,1950,1951],{},"In this article we'll explore how they work, how can we implement them using Vue and Nuxt, and how they server other solutions like real-time databases. Ready? Let's go!",[42,1953,1955],{"id":1954},"how-do-websockets-work","How Do WebSockets Work?",[16,1957,1958,1959,1962,1963,1966],{},"A WebSocket connection starts as a regular HTTP request. The client sends a special header, ",[172,1960,1961],{},"Upgrade: websocket",", basically asking the server \"hey, can we upgrade this connection to a socket?\". It also sends a random ",[172,1964,1965],{},"Sec-WebSocket-Key"," along with it.",[16,1968,1969,1970,1973,1974,1977,1978,126],{},"If the server supports websockets and accepts, it hashes that key with a magic GUID and returns the result in a ",[172,1971,1972],{},"Sec-WebSocket-Accept"," header, alongside a ",[172,1975,1976],{},"101 Switching Protocols"," response. This little crypto dance proves the server actually understood the request. You don't need to worry about it, the browser and the server ",[20,1979,1982],{"href":1980,"rel":1981},"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#the_websocket_handshake",[1052],"handle it for you",[16,1984,1985,1986,1989,1990,1993,1994,1997],{},"After that, the protocol changes from ",[172,1987,1988],{},"http://"," to ",[172,1991,1992],{},"ws://"," (or ",[172,1995,1996],{},"wss://"," for secure).",[16,1999,2000],{},"Once that's done, the connection is established and both sides can send messages whenever they want.",[42,2002,2004],{"id":2003},"implementing-websockets-with-vue-nuxt","Implementing WebSockets with Vue & Nuxt",[16,2006,2007,2008,1691,2013,2018,2019,2024,2025,126],{},"Many applications use ",[20,2009,2012],{"href":2010,"rel":2011},"https://pusher.com/",[1052],"Pusher",[20,2014,2017],{"href":2015,"rel":2016},"https://socket.io/",[1052],"Socket.IO",", or similar services to support WebSockets. Many others use a dedicated, self-hosted solution like ",[20,2020,2023],{"href":2021,"rel":2022},"https://reverb.laravel.com/",[1052],"Reverb"," or ",[20,2026,2029],{"href":2027,"rel":2028},"https://github.com/websockets/ws",[1052],"WS",[16,2031,2032,2033],{},"The cool thing is that, if you're using Nuxt, you don't need a special service, perhaps not event a dedicated server: ",[56,2034,2035],{},"WebSockets are built into Nuxt thanks to Nitro.",[16,2037,2038],{},"Your Nuxt application can handle them, out of the box. Let's check that out!",[2040,2041,2045],"pill",{"className":2042,"icon":2044},[2043],"bg-sky-500","fa6-solid:server",[16,2046,2047],{},"Server-Side",[16,2049,2050,2051,2054],{},"In a server route, you define a special handler with ",[172,2052,2053],{},"defineWebSocketHandler",". This handler gives you access to a set of hooks:",[165,2056,2058],{"className":393,"code":2057,"language":395,"meta":170,"style":170},"export default defineWebSocketHandler({\n  // Runs when a peer connects\n  open(peer) {\n    // Subscribe the peer to a topic\n    peer.subscribe('room-123')\n  },\n  // Runs when a peer sends a message\n  message(peer, message) {\n    // Broadcast the message to everyone else\n    peer.publish('room-123', message.text())\n  }\n  // Other hooks: upgrade(), close(), error()\n})\n",[172,2059,2060,2073,2078,2090,2095,2110,2115,2120,2136,2141,2161,2165,2170],{"__ignoreMap":170},[175,2061,2062,2065,2068,2071],{"class":177,"line":178},[175,2063,2064],{"class":410},"export",[175,2066,2067],{"class":410}," default",[175,2069,2070],{"class":511}," defineWebSocketHandler",[175,2072,978],{"class":406},[175,2074,2075],{"class":177,"line":184},[175,2076,2077],{"class":1028},"  // Runs when a peer connects\n",[175,2079,2080,2083,2085,2088],{"class":177,"line":190},[175,2081,2082],{"class":511},"  open",[175,2084,539],{"class":406},[175,2086,2087],{"class":679},"peer",[175,2089,683],{"class":406},[175,2091,2092],{"class":177,"line":196},[175,2093,2094],{"class":1028},"    // Subscribe the peer to a topic\n",[175,2096,2097,2100,2103,2105,2108],{"class":177,"line":202},[175,2098,2099],{"class":406},"    peer.",[175,2101,2102],{"class":511},"subscribe",[175,2104,539],{"class":406},[175,2106,2107],{"class":414},"'room-123'",[175,2109,731],{"class":406},[175,2111,2112],{"class":177,"line":208},[175,2113,2114],{"class":406},"  },\n",[175,2116,2117],{"class":177,"line":215},[175,2118,2119],{"class":1028},"  // Runs when a peer sends a message\n",[175,2121,2122,2125,2127,2129,2131,2134],{"class":177,"line":221},[175,2123,2124],{"class":511},"  message",[175,2126,539],{"class":406},[175,2128,2087],{"class":679},[175,2130,1691],{"class":406},[175,2132,2133],{"class":679},"message",[175,2135,683],{"class":406},[175,2137,2138],{"class":177,"line":227},[175,2139,2140],{"class":1028},"    // Broadcast the message to everyone else\n",[175,2142,2143,2145,2148,2150,2152,2155,2158],{"class":177,"line":233},[175,2144,2099],{"class":406},[175,2146,2147],{"class":511},"publish",[175,2149,539],{"class":406},[175,2151,2107],{"class":414},[175,2153,2154],{"class":406},", message.",[175,2156,2157],{"class":511},"text",[175,2159,2160],{"class":406},"())\n",[175,2162,2163],{"class":177,"line":239},[175,2164,765],{"class":406},[175,2166,2167],{"class":177,"line":245},[175,2168,2169],{"class":1028},"  // Other hooks: upgrade(), close(), error()\n",[175,2171,2172],{"class":177,"line":250},[175,2173,1290],{"class":406},[50,2175,2176,2186],{},[53,2177,2178,2179,2182,2183,2185],{},"The ",[172,2180,2181],{},"open"," hook runs whenever a peer connects. In this case, we're subscribing the peer to a topic: the ID of a chat room, ",[172,2184,2107],{},". It can be whatever string you want.",[53,2187,2178,2188,2190],{},[172,2189,2133],{}," hook runs whenever a peer sends a message. Here, we're publishing the text of that message to everyone subscribed to that topic.",[16,2192,2193,2194,1691,2197,2200,2201,2203],{},"You have other hooks too, like ",[172,2195,2196],{},"upgrade",[172,2198,2199],{},"close",", and ",[172,2202,816],{},". You can explore them in the official documentation.",[16,2205,2206,2207,2209],{},"Let's check out the methods of the ",[172,2208,2087],{}," object:",[165,2211,2213],{"className":393,"code":2212,"language":395,"meta":170,"style":170},"// Subscribes the peer to a topic\npeer.subscribe('room-123')\n\n// Sends a message to everyone in the topic (the sender excluded)\npeer.publish('room-123', 'Hello there!')\npeer.publish('room-123', { user: 'Alice', text: 'Hi everyone!' })\n\n// Sends something to that peer only.\n// Great for messages like \"Welcome to the chat room\", notifications, etc.\npeer.send('Welcome!')\npeer.send({ type: 'notification', text: 'You are awesome' })\n\n// Stops listening to the topic\npeer.unsubscribe('room-123')\n\n// Gracefully closes the connection\npeer.close()\n",[172,2214,2215,2220,2232,2236,2241,2258,2283,2287,2292,2297,2310,2329,2333,2338,2351,2355,2360],{"__ignoreMap":170},[175,2216,2217],{"class":177,"line":178},[175,2218,2219],{"class":1028},"// Subscribes the peer to a topic\n",[175,2221,2222,2224,2226,2228,2230],{"class":177,"line":184},[175,2223,1354],{"class":406},[175,2225,2102],{"class":511},[175,2227,539],{"class":406},[175,2229,2107],{"class":414},[175,2231,731],{"class":406},[175,2233,2234],{"class":177,"line":190},[175,2235,212],{"emptyLinePlaceholder":211},[175,2237,2238],{"class":177,"line":196},[175,2239,2240],{"class":1028},"// Sends a message to everyone in the topic (the sender excluded)\n",[175,2242,2243,2245,2247,2249,2251,2253,2256],{"class":177,"line":202},[175,2244,1354],{"class":406},[175,2246,2147],{"class":511},[175,2248,539],{"class":406},[175,2250,2107],{"class":414},[175,2252,1691],{"class":406},[175,2254,2255],{"class":414},"'Hello there!'",[175,2257,731],{"class":406},[175,2259,2260,2262,2264,2266,2268,2271,2274,2277,2280],{"class":177,"line":208},[175,2261,1354],{"class":406},[175,2263,2147],{"class":511},[175,2265,539],{"class":406},[175,2267,2107],{"class":414},[175,2269,2270],{"class":406},", { user: ",[175,2272,2273],{"class":414},"'Alice'",[175,2275,2276],{"class":406},", text: ",[175,2278,2279],{"class":414},"'Hi everyone!'",[175,2281,2282],{"class":406}," })\n",[175,2284,2285],{"class":177,"line":215},[175,2286,212],{"emptyLinePlaceholder":211},[175,2288,2289],{"class":177,"line":221},[175,2290,2291],{"class":1028},"// Sends something to that peer only.\n",[175,2293,2294],{"class":177,"line":227},[175,2295,2296],{"class":1028},"// Great for messages like \"Welcome to the chat room\", notifications, etc.\n",[175,2298,2299,2301,2303,2305,2308],{"class":177,"line":233},[175,2300,1354],{"class":406},[175,2302,740],{"class":511},[175,2304,539],{"class":406},[175,2306,2307],{"class":414},"'Welcome!'",[175,2309,731],{"class":406},[175,2311,2312,2314,2316,2319,2322,2324,2327],{"class":177,"line":239},[175,2313,1354],{"class":406},[175,2315,740],{"class":511},[175,2317,2318],{"class":406},"({ type: ",[175,2320,2321],{"class":414},"'notification'",[175,2323,2276],{"class":406},[175,2325,2326],{"class":414},"'You are awesome'",[175,2328,2282],{"class":406},[175,2330,2331],{"class":177,"line":245},[175,2332,212],{"emptyLinePlaceholder":211},[175,2334,2335],{"class":177,"line":250},[175,2336,2337],{"class":1028},"// Stops listening to the topic\n",[175,2339,2340,2342,2345,2347,2349],{"class":177,"line":256},[175,2341,1354],{"class":406},[175,2343,2344],{"class":511},"unsubscribe",[175,2346,539],{"class":406},[175,2348,2107],{"class":414},[175,2350,731],{"class":406},[175,2352,2353],{"class":177,"line":588},[175,2354,212],{"emptyLinePlaceholder":211},[175,2356,2357],{"class":177,"line":606},[175,2358,2359],{"class":1028},"// Gracefully closes the connection\n",[175,2361,2362,2364,2366],{"class":177,"line":620},[175,2363,1354],{"class":406},[175,2365,2199],{"class":511},[175,2367,515],{"class":406},[2040,2369,2372],{"className":2370,"icon":2371},[2043],"fa6-solid:laptop",[16,2373,2374],{},"Client-Side",[16,2376,2377,2378,126],{},"On the client side, VueUse has us covered again with ",[172,2379,2380],{},"useWebSocket",[16,2382,2383],{},"Take a look at this code. It's a minimal chat component:",[165,2385,2387],{"className":1509,"code":2386,"language":1511,"meta":170,"style":170},"\u003Cscript setup>\nimport { useWebSocket } from '@vueuse/core'\n\nconst messages = ref([])\nconst { send } = useWebSocket('ws://localhost:3000/_ws', {\n  onMessage: (ws, e) => messages.value.push(e.data)\n})\n\nconst input = ref('')\nfunction submit() {\n  send(input.value)\n  messages.value.push(`Me: ${input.value}`)\n  input.value = ''\n}\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv v-for=\"(msg, i) in messages\" :key=\"i\">{{ msg }}\u003C/div>\n  \u003Cinput v-model=\"input\" @keyup.enter=\"submit\" />\n\u003C/template>\n",[172,2388,2389,2399,2411,2415,2429,2448,2476,2480,2484,2502,2512,2520,2543,2553,2557,2565,2569,2577,2625,2666],{"__ignoreMap":170},[175,2390,2391,2393,2395,2397],{"class":177,"line":178},[175,2392,1518],{"class":406},[175,2394,1521],{"class":410},[175,2396,1524],{"class":511},[175,2398,1527],{"class":406},[175,2400,2401,2403,2406,2408],{"class":177,"line":184},[175,2402,1309],{"class":410},[175,2404,2405],{"class":406}," { useWebSocket } ",[175,2407,1315],{"class":410},[175,2409,2410],{"class":414}," '@vueuse/core'\n",[175,2412,2413],{"class":177,"line":190},[175,2414,212],{"emptyLinePlaceholder":211},[175,2416,2417,2419,2422,2424,2426],{"class":177,"line":196},[175,2418,403],{"class":402},[175,2420,2421],{"class":406}," messages ",[175,2423,411],{"class":410},[175,2425,1777],{"class":511},[175,2427,2428],{"class":406},"([])\n",[175,2430,2431,2433,2436,2438,2441,2443,2446],{"class":177,"line":202},[175,2432,403],{"class":402},[175,2434,2435],{"class":406}," { send } ",[175,2437,411],{"class":410},[175,2439,2440],{"class":511}," useWebSocket",[175,2442,539],{"class":406},[175,2444,2445],{"class":414},"'ws://localhost:3000/_ws'",[175,2447,1245],{"class":406},[175,2449,2450,2453,2456,2459,2461,2463,2465,2467,2470,2473],{"class":177,"line":208},[175,2451,2452],{"class":511},"  onMessage",[175,2454,2455],{"class":406},": (",[175,2457,2458],{"class":679},"ws",[175,2460,1691],{"class":406},[175,2462,807],{"class":679},[175,2464,545],{"class":406},[175,2466,712],{"class":402},[175,2468,2469],{"class":406}," messages.value.",[175,2471,2472],{"class":511},"push",[175,2474,2475],{"class":406},"(e.data)\n",[175,2477,2478],{"class":177,"line":215},[175,2479,1290],{"class":406},[175,2481,2482],{"class":177,"line":221},[175,2483,212],{"emptyLinePlaceholder":211},[175,2485,2486,2488,2491,2493,2495,2497,2500],{"class":177,"line":227},[175,2487,403],{"class":402},[175,2489,2490],{"class":406}," input ",[175,2492,411],{"class":410},[175,2494,1777],{"class":511},[175,2496,539],{"class":406},[175,2498,2499],{"class":414},"''",[175,2501,731],{"class":406},[175,2503,2504,2506,2509],{"class":177,"line":233},[175,2505,671],{"class":402},[175,2507,2508],{"class":511}," submit",[175,2510,2511],{"class":406},"() {\n",[175,2513,2514,2517],{"class":177,"line":239},[175,2515,2516],{"class":511},"  send",[175,2518,2519],{"class":406},"(input.value)\n",[175,2521,2522,2525,2527,2529,2532,2534,2537,2539,2541],{"class":177,"line":245},[175,2523,2524],{"class":406},"  messages.value.",[175,2526,2472],{"class":511},[175,2528,539],{"class":406},[175,2530,2531],{"class":414},"`Me: ",[175,2533,748],{"class":410},[175,2535,2536],{"class":406},"input.value",[175,2538,754],{"class":410},[175,2540,757],{"class":414},[175,2542,731],{"class":406},[175,2544,2545,2548,2550],{"class":177,"line":250},[175,2546,2547],{"class":406},"  input.value ",[175,2549,411],{"class":410},[175,2551,2552],{"class":414}," ''\n",[175,2554,2555],{"class":177,"line":256},[175,2556,492],{"class":406},[175,2558,2559,2561,2563],{"class":177,"line":588},[175,2560,1749],{"class":406},[175,2562,1521],{"class":410},[175,2564,1527],{"class":406},[175,2566,2567],{"class":177,"line":606},[175,2568,212],{"emptyLinePlaceholder":211},[175,2570,2571,2573,2575],{"class":177,"line":620},[175,2572,1518],{"class":406},[175,2574,1764],{"class":410},[175,2576,1527],{"class":406},[175,2578,2579,2581,2584,2587,2589,2592,2595,2598,2601,2603,2606,2609,2611,2613,2616,2618,2621,2623],{"class":177,"line":633},[175,2580,1771],{"class":406},[175,2582,2583],{"class":410},"div",[175,2585,2586],{"class":410}," v-for",[175,2588,411],{"class":406},[175,2590,2591],{"class":406},"\"",[175,2593,2594],{"class":406},"(msg, i) ",[175,2596,2597],{"class":410},"in",[175,2599,2600],{"class":406}," messages",[175,2602,2591],{"class":406},[175,2604,2605],{"class":406}," :",[175,2607,2608],{"class":511},"key",[175,2610,411],{"class":406},[175,2612,2591],{"class":406},[175,2614,2615],{"class":406},"i",[175,2617,2591],{"class":406},[175,2619,2620],{"class":406},">{{ msg }}\u003C/",[175,2622,2583],{"class":410},[175,2624,1527],{"class":406},[175,2626,2627,2629,2632,2635,2637,2639,2641,2643,2646,2649,2651,2654,2656,2658,2661,2663],{"class":177,"line":646},[175,2628,1771],{"class":406},[175,2630,2631],{"class":410},"input",[175,2633,2634],{"class":511}," v-model",[175,2636,411],{"class":406},[175,2638,2591],{"class":406},[175,2640,2631],{"class":406},[175,2642,2591],{"class":406},[175,2644,2645],{"class":406}," @",[175,2647,2648],{"class":511},"keyup",[175,2650,126],{"class":406},[175,2652,2653],{"class":511},"enter",[175,2655,411],{"class":406},[175,2657,2591],{"class":406},[175,2659,2660],{"class":406},"submit",[175,2662,2591],{"class":406},[175,2664,2665],{"class":406}," />\n",[175,2667,2668,2670,2672],{"class":177,"line":663},[175,2669,1749],{"class":406},[175,2671,1764],{"class":410},[175,2673,1527],{"class":406},[16,2675,2676,2677,2680,2681,2683],{},"Let's explore what we have. We're connecting to the WebSocket, and we say: every time you get a message, push it into the ",[172,2678,2679],{},"messages"," array. We also destructure the ",[172,2682,740],{}," method, which we use below: whenever the user types something into the input and presses enter, we send the message via the socket and push it into the array of messages.",[16,2685,2686],{},"In just about 20 lines of code, we have a working chat. Of course, in a real-world application you'd need many more features, but this shows how simple it is to work with WebSockets in Vue.",[128,2688,2690],{"id":2689},"two-more-things-auto-reconnect-and-heartbeat","Two More Things: Auto-Reconnect and Heartbeat",[16,2692,2693],{},"Before moving on, two cool things about WebSockets that you'll want to keep in mind.",[16,2695,2696,2699,2700,2702],{},[56,2697,2698],{},"Auto-reconnect"," is pretty similar to SSE. The only difference is that with ",[172,2701,2380],{}," you have to opt in, it's not on by default:",[165,2704,2706],{"className":393,"code":2705,"language":395,"meta":170,"style":170},"// Same idea as SSE, but with WebSockets you have to opt in\nuseWebSocket('ws://localhost:3000/_ws', {\n  autoReconnect: {\n    retries: 5,\n    delay: 2000,\n    onFailed: () => console.log('Could not reconnect'),\n  }\n})\n",[172,2707,2708,2713,2723,2728,2738,2748,2770,2774],{"__ignoreMap":170},[175,2709,2710],{"class":177,"line":178},[175,2711,2712],{"class":1028},"// Same idea as SSE, but with WebSockets you have to opt in\n",[175,2714,2715,2717,2719,2721],{"class":177,"line":184},[175,2716,2380],{"class":511},[175,2718,539],{"class":406},[175,2720,2445],{"class":414},[175,2722,1245],{"class":406},[175,2724,2725],{"class":177,"line":190},[175,2726,2727],{"class":406},"  autoReconnect: {\n",[175,2729,2730,2733,2736],{"class":177,"line":196},[175,2731,2732],{"class":406},"    retries: ",[175,2734,2735],{"class":427},"5",[175,2737,990],{"class":406},[175,2739,2740,2743,2746],{"class":177,"line":202},[175,2741,2742],{"class":406},"    delay: ",[175,2744,2745],{"class":427},"2000",[175,2747,990],{"class":406},[175,2749,2750,2753,2756,2758,2760,2762,2764,2767],{"class":177,"line":208},[175,2751,2752],{"class":511},"    onFailed",[175,2754,2755],{"class":406},": () ",[175,2757,712],{"class":402},[175,2759,782],{"class":406},[175,2761,723],{"class":511},[175,2763,539],{"class":406},[175,2765,2766],{"class":414},"'Could not reconnect'",[175,2768,2769],{"class":406},"),\n",[175,2771,2772],{"class":177,"line":215},[175,2773,765],{"class":406},[175,2775,2776],{"class":177,"line":221},[175,2777,1290],{"class":406},[16,2779,2780,2781,2784,2785,2788,2789,2792],{},"And then there's the ",[56,2782,2783],{},"heartbeat",". WebSockets stay open forever, in theory, but proxies and load balancers love to kill idle connections. So when nothing is being exchanged between server and client, it's recommended to regularly send a ",[1037,2786,2787],{},"ping"," to the server and receive a ",[1037,2790,2791],{},"pong"," back, just to keep the connection alive.",[165,2794,2796],{"className":393,"code":2795,"language":395,"meta":170,"style":170},"// Proxies and load balancers love to kill idle connections,\n// send a ping every 30 seconds to keep them alive\nuseWebSocket('ws://localhost:3000/_ws', {\n  heartbeat: {\n    message: 'ping',\n    interval: 30000,\n    pongTimeout: 1000,\n  }\n})\n",[172,2797,2798,2803,2808,2818,2823,2833,2843,2853,2857],{"__ignoreMap":170},[175,2799,2800],{"class":177,"line":178},[175,2801,2802],{"class":1028},"// Proxies and load balancers love to kill idle connections,\n",[175,2804,2805],{"class":177,"line":184},[175,2806,2807],{"class":1028},"// send a ping every 30 seconds to keep them alive\n",[175,2809,2810,2812,2814,2816],{"class":177,"line":190},[175,2811,2380],{"class":511},[175,2813,539],{"class":406},[175,2815,2445],{"class":414},[175,2817,1245],{"class":406},[175,2819,2820],{"class":177,"line":196},[175,2821,2822],{"class":406},"  heartbeat: {\n",[175,2824,2825,2828,2831],{"class":177,"line":202},[175,2826,2827],{"class":406},"    message: ",[175,2829,2830],{"class":414},"'ping'",[175,2832,990],{"class":406},[175,2834,2835,2838,2841],{"class":177,"line":208},[175,2836,2837],{"class":406},"    interval: ",[175,2839,2840],{"class":427},"30000",[175,2842,990],{"class":406},[175,2844,2845,2848,2851],{"class":177,"line":215},[175,2846,2847],{"class":406},"    pongTimeout: ",[175,2849,2850],{"class":427},"1000",[175,2852,990],{"class":406},[175,2854,2855],{"class":177,"line":221},[175,2856,765],{"class":406},[175,2858,2859],{"class":177,"line":227},[175,2860,1290],{"class":406},[16,2862,2863,2864,2867],{},"If the server doesn't pong back within ",[172,2865,2866],{},"pongTimeout",", the composable considers the connection dead and (if you opted in) reconnects.",[42,2869,2871],{"id":2870},"real-time-databases","Real-Time Databases",[16,2873,2874],{},"This is a great moment to talk about real-time databases, because WebSockets are exactly what they use under the hood to broadcast changes.",[16,2876,2877,2878,2881,2882,2885],{},"What's the idea? In your client-side code, you subscribe to listen for changes on certain ",[1037,2879,2880],{},"collections"," (in NoSQL databases) or ",[1037,2883,2884],{},"tables"," (in relational ones). If a record is created, updated, or deleted, every connected client gets the details in real time.",[16,2887,2888],{},"You don't need to worry about wiring up the socket: the libraries handles that for you. You just pick the changes you want to listen for, and handle what to do with them by defining a callback.",[16,2890,2891],{},"For example, let's say you have a list of tasks, and whenever those tasks change you want this template to automatically re-render:",[165,2893,2895],{"className":1509,"code":2894,"language":1511,"meta":170,"style":170},"\u003Ctemplate>\n  \u003Cul v-if=\"tasks\">\n    \u003Cli v-for=\"task in tasks\" :key=\"task.id\">\n      {{ task.user }}: {{ task.text }}\n    \u003C/li>\n  \u003C/ul>\n  \u003Cp v-else>Loading...\u003C/p>\n\u003C/template>\n",[172,2896,2897,2905,2925,2963,2968,2977,2986,3002],{"__ignoreMap":170},[175,2898,2899,2901,2903],{"class":177,"line":178},[175,2900,1518],{"class":406},[175,2902,1764],{"class":410},[175,2904,1527],{"class":406},[175,2906,2907,2909,2911,2914,2916,2918,2921,2923],{"class":177,"line":184},[175,2908,1771],{"class":406},[175,2910,50],{"class":410},[175,2912,2913],{"class":410}," v-if",[175,2915,411],{"class":406},[175,2917,2591],{"class":406},[175,2919,2920],{"class":406},"tasks",[175,2922,2591],{"class":406},[175,2924,1527],{"class":406},[175,2926,2927,2930,2932,2934,2936,2938,2941,2943,2946,2948,2950,2952,2954,2956,2959,2961],{"class":177,"line":190},[175,2928,2929],{"class":406},"    \u003C",[175,2931,53],{"class":410},[175,2933,2586],{"class":410},[175,2935,411],{"class":406},[175,2937,2591],{"class":406},[175,2939,2940],{"class":406},"task ",[175,2942,2597],{"class":410},[175,2944,2945],{"class":406}," tasks",[175,2947,2591],{"class":406},[175,2949,2605],{"class":406},[175,2951,2608],{"class":511},[175,2953,411],{"class":406},[175,2955,2591],{"class":406},[175,2957,2958],{"class":406},"task.id",[175,2960,2591],{"class":406},[175,2962,1527],{"class":406},[175,2964,2965],{"class":177,"line":196},[175,2966,2967],{"class":406},"      {{ task.user }}: {{ task.text }}\n",[175,2969,2970,2973,2975],{"class":177,"line":202},[175,2971,2972],{"class":406},"    \u003C/",[175,2974,53],{"class":410},[175,2976,1527],{"class":406},[175,2978,2979,2982,2984],{"class":177,"line":208},[175,2980,2981],{"class":406},"  \u003C/",[175,2983,50],{"class":410},[175,2985,1527],{"class":406},[175,2987,2988,2990,2992,2995,2998,3000],{"class":177,"line":215},[175,2989,1771],{"class":406},[175,2991,16],{"class":410},[175,2993,2994],{"class":410}," v-else",[175,2996,2997],{"class":406},">Loading...\u003C/",[175,2999,16],{"class":410},[175,3001,1527],{"class":406},[175,3003,3004,3006,3008],{"class":177,"line":221},[175,3005,1749],{"class":406},[175,3007,1764],{"class":410},[175,3009,1527],{"class":406},[16,3011,3012,3013,3015],{},"How can we keep ",[172,3014,2920],{}," up to date in real time? Let's look at two great options.",[2040,3017,3021],{"className":3018,"icon":3020},[3019],"bg-yellow-200","logos:firebase-icon",[16,3022,3023],{},"Firebase and VueFire",[16,3025,3026,3031,3032,3035,3036,3039,3040,3047],{},[20,3027,3030],{"href":3028,"rel":3029},"https://firebase.google.com/",[1052],"Firebase"," comes in two flavors, the ",[56,3033,3034],{},"Realtime Database"," and ",[56,3037,3038],{},"Firestore",", both NoSQL, both real-time. The best way to use it in Vue is with ",[20,3041,3044],{"href":3042,"rel":3043},"https://vuefire.vuejs.org/",[1052],[56,3045,3046],{},"VueFire",", the amazing library by Eduardo San Martín Morote.",[165,3049,3051],{"className":393,"code":3050,"language":395,"meta":170,"style":170},"// VueFire 🔥\nimport { useCollection } from 'vuefire'\nimport { collection } from 'firebase/firestore'\nimport { db } from '~/firebase'\n\nconst tasks = useCollection(collection(db, 'tasks'))\n",[172,3052,3053,3058,3070,3082,3094,3098],{"__ignoreMap":170},[175,3054,3055],{"class":177,"line":178},[175,3056,3057],{"class":1028},"// VueFire 🔥\n",[175,3059,3060,3062,3065,3067],{"class":177,"line":184},[175,3061,1309],{"class":410},[175,3063,3064],{"class":406}," { useCollection } ",[175,3066,1315],{"class":410},[175,3068,3069],{"class":414}," 'vuefire'\n",[175,3071,3072,3074,3077,3079],{"class":177,"line":190},[175,3073,1309],{"class":410},[175,3075,3076],{"class":406}," { collection } ",[175,3078,1315],{"class":410},[175,3080,3081],{"class":414}," 'firebase/firestore'\n",[175,3083,3084,3086,3089,3091],{"class":177,"line":196},[175,3085,1309],{"class":410},[175,3087,3088],{"class":406}," { db } ",[175,3090,1315],{"class":410},[175,3092,3093],{"class":414}," '~/firebase'\n",[175,3095,3096],{"class":177,"line":202},[175,3097,212],{"emptyLinePlaceholder":211},[175,3099,3100,3102,3105,3107,3110,3112,3115,3118,3121],{"class":177,"line":208},[175,3101,403],{"class":402},[175,3103,3104],{"class":406}," tasks ",[175,3106,411],{"class":410},[175,3108,3109],{"class":511}," useCollection",[175,3111,539],{"class":406},[175,3113,3114],{"class":511},"collection",[175,3116,3117],{"class":406},"(db, ",[175,3119,3120],{"class":414},"'tasks'",[175,3122,894],{"class":406},[16,3124,3125,3126,3128,3129,3131],{},"That's it. Super short. ",[172,3127,2920],{}," is a reactive ref, so whenever the ",[172,3130,2920],{}," collection changes, the template re-renders.",[2040,3133,3137],{"className":3134,"icon":3136},[3135],"bg-green-200","logos:supabase-icon",[16,3138,3139],{},"Supabase and the Nuxt module",[16,3141,3142,3143,3150,3151,126],{},"If you want an open-source alternative, there's ",[20,3144,3147],{"href":3145,"rel":3146},"https://supabase.com/",[1052],[56,3148,3149],{},"Supabase",". It's a Postgres database with real-time updates you can subscribe to. If you're using Nuxt, I recommend the excellent ",[20,3152,3155],{"href":3153,"rel":3154},"https://supabase.nuxtjs.org/",[1052],[56,3156,3157],{},"Nuxt Supabase module",[165,3159,3161],{"className":393,"code":3160,"language":395,"meta":170,"style":170},"// Nuxt Supabase ⚡️\nconst client = useSupabaseClient()\n\nconst { data: tasks, refresh } = await useAsyncData('tasks', async () =>\n  (await client.from('tasks').select('*')).data\n)\n\nlet channel\n\nonMounted(() =>\n  channel = client\n    .channel('public:tasks')\n    .on('postgres_changes', {\n      event: '*', schema: 'public', table: 'tasks'\n    }, refresh)\n    .subscribe()\n)\n\nonUnmounted(() => client.removeChannel(channel))\n",[172,3162,3163,3168,3182,3186,3213,3243,3247,3251,3258,3262,3271,3280,3295,3308,3327,3332,3340,3344,3348],{"__ignoreMap":170},[175,3164,3165],{"class":177,"line":178},[175,3166,3167],{"class":1028},"// Nuxt Supabase ⚡️\n",[175,3169,3170,3172,3175,3177,3180],{"class":177,"line":184},[175,3171,403],{"class":402},[175,3173,3174],{"class":406}," client ",[175,3176,411],{"class":410},[175,3178,3179],{"class":511}," useSupabaseClient",[175,3181,515],{"class":406},[175,3183,3184],{"class":177,"line":190},[175,3185,212],{"emptyLinePlaceholder":211},[175,3187,3188,3190,3193,3195,3197,3200,3202,3204,3206,3208,3210],{"class":177,"line":196},[175,3189,403],{"class":402},[175,3191,3192],{"class":406}," { data: tasks, refresh } ",[175,3194,411],{"class":410},[175,3196,1610],{"class":410},[175,3198,3199],{"class":511}," useAsyncData",[175,3201,539],{"class":406},[175,3203,3120],{"class":414},[175,3205,1691],{"class":406},[175,3207,1592],{"class":410},[175,3209,709],{"class":406},[175,3211,3212],{"class":402},"=>\n",[175,3214,3215,3218,3221,3224,3226,3228,3230,3232,3235,3237,3240],{"class":177,"line":202},[175,3216,3217],{"class":406},"  (",[175,3219,3220],{"class":410},"await",[175,3222,3223],{"class":406}," client.",[175,3225,1315],{"class":511},[175,3227,539],{"class":406},[175,3229,3120],{"class":414},[175,3231,1681],{"class":406},[175,3233,3234],{"class":511},"select",[175,3236,539],{"class":406},[175,3238,3239],{"class":414},"'*'",[175,3241,3242],{"class":406},")).data\n",[175,3244,3245],{"class":177,"line":208},[175,3246,731],{"class":406},[175,3248,3249],{"class":177,"line":215},[175,3250,212],{"emptyLinePlaceholder":211},[175,3252,3253,3255],{"class":177,"line":221},[175,3254,609],{"class":402},[175,3256,3257],{"class":406}," channel\n",[175,3259,3260],{"class":177,"line":227},[175,3261,212],{"emptyLinePlaceholder":211},[175,3263,3264,3266,3269],{"class":177,"line":233},[175,3265,1587],{"class":511},[175,3267,3268],{"class":406},"(() ",[175,3270,3212],{"class":402},[175,3272,3273,3275,3277],{"class":177,"line":239},[175,3274,689],{"class":406},[175,3276,411],{"class":410},[175,3278,3279],{"class":406}," client\n",[175,3281,3282,3285,3288,3290,3293],{"class":177,"line":245},[175,3283,3284],{"class":406},"    .",[175,3286,3287],{"class":511},"channel",[175,3289,539],{"class":406},[175,3291,3292],{"class":414},"'public:tasks'",[175,3294,731],{"class":406},[175,3296,3297,3299,3301,3303,3306],{"class":177,"line":250},[175,3298,3284],{"class":406},[175,3300,1357],{"class":511},[175,3302,539],{"class":406},[175,3304,3305],{"class":414},"'postgres_changes'",[175,3307,1245],{"class":406},[175,3309,3310,3313,3315,3318,3321,3324],{"class":177,"line":256},[175,3311,3312],{"class":406},"      event: ",[175,3314,3239],{"class":414},[175,3316,3317],{"class":406},", schema: ",[175,3319,3320],{"class":414},"'public'",[175,3322,3323],{"class":406},", table: ",[175,3325,3326],{"class":414},"'tasks'\n",[175,3328,3329],{"class":177,"line":588},[175,3330,3331],{"class":406},"    }, refresh)\n",[175,3333,3334,3336,3338],{"class":177,"line":606},[175,3335,3284],{"class":406},[175,3337,2102],{"class":511},[175,3339,515],{"class":406},[175,3341,3342],{"class":177,"line":620},[175,3343,731],{"class":406},[175,3345,3346],{"class":177,"line":633},[175,3347,212],{"emptyLinePlaceholder":211},[175,3349,3350,3353,3355,3357,3359,3362],{"class":177,"line":646},[175,3351,3352],{"class":511},"onUnmounted",[175,3354,3268],{"class":406},[175,3356,712],{"class":402},[175,3358,3223],{"class":406},[175,3360,3361],{"class":511},"removeChannel",[175,3363,3364],{"class":406},"(channel))\n",[16,3366,3367],{},"Same idea: you write to your database, and every subscribed client gets the change in real time.",[42,3369,3371],{"id":3370},"websockets-are-great-but","WebSockets Are Great! But...",[16,3373,3374,3375],{},"WebSockets are great, and real-time databases make them even more convenient. But there's a catch: ",[56,3376,3377],{},"you depend on a server.",[16,3379,3380],{},"What if you don't want a server in the middle? What if you could connect directly to your peer, browser to browser?",[16,3382,3383,3384,3386,3387,126],{},"Well, that's possible thanks to ",[56,3385,75],{},", and that's exactly what we'll explore in ",[20,3388,3389],{"href":1894},"Part 3: WebRTC and PeerJS",[16,3391,3392],{},"See you there!",[1872,3394,3395],{},"html pre.shiki code .s8I7P, html code.shiki .s8I7P{--shiki-default:#F92672}html pre.shiki code .sHkqI, html code.shiki .sHkqI{--shiki-default:#A6E22E}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .snpHw, html code.shiki .snpHw{--shiki-default:#88846F}html pre.shiki code .sW0Xf, html code.shiki .sW0Xf{--shiki-default:#FD971F;--shiki-default-font-style:italic}html pre.shiki code .s_Ekj, html code.shiki .s_Ekj{--shiki-default:#E6DB74}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 pre.shiki code .sOx1s, html code.shiki .sOx1s{--shiki-default:#66D9EF;--shiki-default-font-style:italic}html pre.shiki code .s7s5_, html code.shiki .s7s5_{--shiki-default:#AE81FF}",{"title":170,"searchDepth":184,"depth":184,"links":3397},[3398,3399,3400,3403,3404],{"id":1926,"depth":184,"text":1927},{"id":1954,"depth":184,"text":1955},{"id":2003,"depth":184,"text":2004,"children":3401},[3402],{"id":2689,"depth":190,"text":2690},{"id":2870,"depth":184,"text":2871},{"id":3370,"depth":184,"text":3371},"2026-05-27","Bidirectional real-time with WebSockets, built right into Nuxt thanks to Nitro. Plus a tour of real-time databases like Firebase and Supabase, and how they use WebSockets under the hood.","/img/blog/real-time-vue-apps-part-2-websockets-and-real-time-databases.png",{},{"title":1900,"description":3406},"blog/real-time-vue-apps-part-2-websockets-and-real-time-databases","BsdtbVTT8kIu7XOjW6rCwQJn2CfBctCNcBxZfckLCdA",{"id":3413,"title":3414,"body":3415,"category":1887,"date":5220,"description":5221,"draft":1890,"extension":1891,"image":5222,"meta":5223,"navigation":211,"path":30,"seo":5224,"stem":5225,"tighten":1890,"__hash__":5226},"blog/blog/real-time-vue-apps-part-1-polling-and-server-sent-events.md","Real-Time Vue Apps, Part 1: Polling and Server-Sent Events",{"type":8,"value":3416,"toc":5209},[3417,3424,3427,3443,3446,3449,3455,3459,3462,3465,3470,3473,3478,3481,3486,3489,3494,3498,3501,3504,3511,3514,3583,3586,3617,3620,3626,3629,3634,3637,3648,3651,3654,3659,3675,3681,3702,3705,3746,3765,3769,3776,3783,3786,3790,3795,3798,3815,3822,3920,3923,3935,3938,4055,4069,4214,4217,4259,4266,4457,4462,4467,4530,4547,4559,4648,4657,4668,4674,4690,4805,4819,4947,4951,4954,4964,4969,4972,5097,5102,5109,5183,5187,5190,5193,5198,5204,5206],[11,3418,3420],{"color":3419,"icon":14},"#057ccd",[16,3421,18,3422,24],{},[20,3423,23],{"href":22},[16,3425,3426],{},"Today we'll explore how to power up your Nuxt and Vue applications with real-time features, using:",[50,3428,3429,3434,3438],{},[53,3430,3431],{},[56,3432,3433],{},"Server-Sent Events",[53,3435,3436],{},[56,3437,1927],{},[53,3439,3440,3441],{},"and ",[56,3442,75],{},[16,3444,3445],{},"By the end of these three articles, you'll know when to implement each one and which composables, libraries, and services can help you do so.",[16,3447,3448],{},"Plus, we'll take a peek at real-time databases and local-first solutions.",[16,3450,3451,3452],{},"There's a lot to cover! But allow me to start with a little story... from the ",[56,3453,3454],{},"dark ages of jQuery.",[42,3456,3458],{"id":3457},"the-dashboard-that-didnt-move","The Dashboard That Didn't Move",[16,3460,3461],{},"A long, long time ago in a hosting company far away, the boss needed a dashboard: he wanted to see the number of visits, support tickets, and sales, all in one place. Easy enough, right?",[16,3463,3464],{},"So we go ahead, build the dashboard, and show it to him. He looked at the screen for what it felt like an eternity, in complete silence, with a face of scrutiny. Then, he finally said:",[369,3466,3467],{},[16,3468,3469],{},"Nice! I like it. Yeah, I like the charts, all the numbers are there but... there's a problem. It's not moving!",[16,3471,3472],{},"He was expecting live updates! That wasn't explicit in the spec, but it was so obvious in hindsight. So young me said:",[369,3474,3475],{},[16,3476,3477],{},"Well, yeah, it's not real-time yet.",[16,3479,3480],{},"And immediately my co-worker added:",[369,3482,3483],{},[16,3484,3485],{},"Unless... you hit refresh really fast over and over again!",[16,3487,3488],{},"And I know, this sounds ridiculous. If your users ask for real-time features please don't ask them to just press F5 over and over again. Your boss? Maybe... but not your users!",[16,3490,3491,3492,126],{},"However, that idea isn't too far from what we ended up implementing. Because what we implemented back then was ",[56,3493,1918],{},[42,3495,3497],{"id":3496},"polling-real-time-not-really","Polling: Real-Time? Not Really",[16,3499,3500],{},"What is polling? Well, the idea is simple.",[16,3502,3503],{},"You need to update some data on the screen. In this case, numbers on the dashboard. So you fire a request to the API, in the background. Your server gets the request and returns fresh data, so you can use it to re-render parts of the UI.",[16,3505,3506,3507,3510],{},"And you do that ",[56,3508,3509],{},"over and over again,"," at regular intervals. For example, every five seconds. The server always returns data, even if nothing changed from the last time you asked.",[16,3512,3513],{},"This is the code I used back in the day. jQuery, the memories!",[165,3515,3517],{"className":393,"code":3516,"language":395,"meta":170,"style":170},"function poll() {\n  $.get('/api/stats', function (data) {\n    updateDashboard(data)\n  })\n}\n\nsetInterval(poll, 5000)\n",[172,3518,3519,3528,3550,3558,3562,3566,3570],{"__ignoreMap":170},[175,3520,3521,3523,3526],{"class":177,"line":178},[175,3522,671],{"class":402},[175,3524,3525],{"class":511}," poll",[175,3527,2511],{"class":406},[175,3529,3530,3533,3535,3537,3540,3542,3544,3546,3548],{"class":177,"line":184},[175,3531,3532],{"class":406},"  $.",[175,3534,536],{"class":511},[175,3536,539],{"class":406},[175,3538,3539],{"class":414},"'/api/stats'",[175,3541,1691],{"class":406},[175,3543,671],{"class":402},[175,3545,804],{"class":406},[175,3547,1449],{"class":679},[175,3549,683],{"class":406},[175,3551,3552,3555],{"class":177,"line":190},[175,3553,3554],{"class":511},"    updateDashboard",[175,3556,3557],{"class":406},"(data)\n",[175,3559,3560],{"class":177,"line":196},[175,3561,1492],{"class":406},[175,3563,3564],{"class":177,"line":202},[175,3565,492],{"class":406},[175,3567,3568],{"class":177,"line":208},[175,3569,212],{"emptyLinePlaceholder":211},[175,3571,3572,3575,3578,3581],{"class":177,"line":215},[175,3573,3574],{"class":511},"setInterval",[175,3576,3577],{"class":406},"(poll, ",[175,3579,3580],{"class":427},"5000",[175,3582,731],{"class":406},[16,3584,3585],{},"Polling was fine back then, and even today it is acceptable for certain scenarios, but... it's definitely not great in general. What are the problems with polling? Why is it an inefficient technique? Well, three main reasons.",[50,3587,3588,3594,3608],{},[53,3589,3590,3593],{},[56,3591,3592],{},"You never achieve true real-time."," If you poll every five seconds and something happens on second two (a new sale, for example), you need to wait until the next poll round to get that fresh data to the client. Most of the time, you are behind. Of course, that's not real-time.",[53,3595,3596,3599,3600,3603,3604,3607],{},[56,3597,3598],{},"Nothing guarantees the order of the responses."," Let's say that, to prevent that delay, you decide to poll every second. Well, you know how it goes with async requests: if you make requests ",[1037,3601,3602],{},"A, B, C"," nothing stops the server from receiving responses ",[1037,3605,3606],{},"A, C, B."," And that can glitch your UI, especially if you're expecting numbers that always go up, like the number of visits.",[53,3609,3610,3613,3614],{},[56,3611,3612],{},"The strain on the server."," But more importantly, there's this reason, the work you are forcing the server to do every time a request is made. Think about the lifecycle of a single API request: the server has to accept the connection, perhaps run authentication and verify permissions, control caches or query the database... and more. With polling, you're doing all of that for every single request, for every single connected user, ",[1037,3615,3616],{},"even if there's nothing new to report.",[16,3618,3619],{},"Scale that to thousands of users and your server might struggle. Your app goes viral? Well, you got yourself a mini self-inflicted DDoS attack.",[16,3621,3622,3625],{},[56,3623,3624],{},"Polling is like a kid in the backseat of a car asking, \"Are we there yet? Are we there yet? Are we there yet?\""," What do you do in those situations? You say: \"I will let you know when we arrive, Timmy.\"",[16,3627,3628],{},"Well, the same applies here. We need to flip the dynamic: instead of the client asking for data over and over again, we can trust the server to send it data whenever it has something new to say.",[16,3630,3631,3632,126],{},"And that's the idea behind ",[56,3633,3433],{},[42,3635,3433],{"id":3636},"server-sent-events",[16,3638,3639,3640,3643,3644,3647],{},"Server-Sent Events (SSE for short) work with a classic HTTP request, but with one big difference: ",[56,3641,3642],{},"the connection stays open."," It's a pipe through which the server can send ",[56,3645,3646],{},"events"," over time, whenever it has something new to report.",[16,3649,3650],{},"Need to send a notification? Send an event. New stats for the dashboard? Send an event. Need to display the positions of vehicles in a map, in real-time? Again, you can send events to do so.",[16,3652,3653],{},"But wait, what are these \"events\"? Well, they're just little pieces of text.",[16,3655,3656,3657,1842],{},"The format is key-value pairs, and the only required key is ",[172,3658,1449],{},[165,3660,3662],{"className":1113,"code":3661,"language":1115,"meta":170,"style":170},"data: Hello, MadVue!\n",[172,3663,3664],{"__ignoreMap":170},[175,3665,3666,3669,3672],{"class":177,"line":178},[175,3667,3668],{"class":511},"data:",[175,3670,3671],{"class":414}," Hello,",[175,3673,3674],{"class":414}," MadVue!\n",[16,3676,3677,3678,3680],{},"The value of ",[172,3679,1449],{}," can be any string, so we can send stringified JSON:",[165,3682,3684],{"className":1113,"code":3683,"language":1115,"meta":170,"style":170},"data: {\"city\": \"Madrid\", \"conference\": \"MadVue\"}\n",[172,3685,3686],{"__ignoreMap":170},[175,3687,3688,3690,3693,3696,3699],{"class":177,"line":178},[175,3689,3668],{"class":511},[175,3691,3692],{"class":414}," {\"city\":",[175,3694,3695],{"class":414}," \"Madrid\",",[175,3697,3698],{"class":414}," \"conference\":",[175,3700,3701],{"class":414}," \"MadVue\"}\n",[16,3703,3704],{},"Plus, we have a couple more standard keys:",[165,3706,3708],{"className":1113,"code":3707,"language":1115,"meta":170,"style":170},"event: greeting\nid: 123\nretry: 5000\ndata: {\"city\": \"Madrid\", \"conference\": \"MadVue\"}\n",[172,3709,3710,3718,3726,3734],{"__ignoreMap":170},[175,3711,3712,3715],{"class":177,"line":178},[175,3713,3714],{"class":511},"event:",[175,3716,3717],{"class":414}," greeting\n",[175,3719,3720,3723],{"class":177,"line":184},[175,3721,3722],{"class":511},"id:",[175,3724,3725],{"class":427}," 123\n",[175,3727,3728,3731],{"class":177,"line":190},[175,3729,3730],{"class":511},"retry:",[175,3732,3733],{"class":427}," 5000\n",[175,3735,3736,3738,3740,3742,3744],{"class":177,"line":196},[175,3737,3668],{"class":511},[175,3739,3692],{"class":414},[175,3741,3695],{"class":414},[175,3743,3698],{"class":414},[175,3745,3701],{"class":414},[50,3747,3748,3753,3759],{},[53,3749,3750,3752],{},[172,3751,839],{}," is the name of the event. Think of it like a channel, such as \"notifications\" or \"purchases\". In the client code, you can decide which channels to listen to, or whether to listen to all of them.",[53,3754,3755,3758],{},[172,3756,3757],{},"id"," can be whatever you want, you can handle it on your own. This will be useful for the auto-reconnect feature that we'll explore shortly.",[53,3760,3761,3764],{},[172,3762,3763],{},"retry"," is a number in milliseconds that indicates how long to wait before reconnecting if the connection drops. Again, we'll check auto-reconnect in a moment.",[128,3766,3768],{"id":3767},"supported-by-every-browser","Supported by Every Browser",[16,3770,3771,3772,3775],{},"All browsers support SSE, they know how to handle these long-lived connections and offer the ",[172,3773,3774],{},"EventSource"," native method to listen to the incoming events.",[16,3777,3778,3779,3782],{},"In fact, Chrome, Firefox and Safari have supported SSE since 2010-2011... a long time ago! So why didn't SSE become more popular? Probably because Internet Explorer ",[1037,3780,3781],{},"never"," added support, and neither did the original Edge that shipped in 2015 on Microsoft's own EdgeHTML engine. It wasn't until January 2020, when Edge switched to Chromium, that we could finally say all major browsers supported SSE.",[16,3784,3785],{},"Thankfully, we can go ahead and use them today without any worries!",[42,3787,3789],{"id":3788},"how-to-implement-server-sent-events","How to Implement Server-Sent Events",[2040,3791,3793],{"className":3792,"icon":2044},[2043],[16,3794,2047],{},[16,3796,3797],{},"Let's take a look at how we can implement SSE in our application, starting with the server-side code. If you are using Nuxt (you should, it's fantastic!) you are lucky, because it comes with SSE support thanks to Nitro.",[16,3799,3800,3801,3804,3805,3808,3809,3811,3812,3814],{},"To create an SSE endpoint, define an event handler in a server route as you always do. Then, use the ",[172,3802,3803],{},"createEventStream"," composable to create a ",[56,3806,3807],{},"stream."," The resulting object will have two important methods: ",[172,3810,740],{}," will open the pipe, and ",[172,3813,2472],{}," will send events through it.",[16,3816,3817,3818,3821],{},"For example, here we're pushing the string ",[172,3819,3820],{},"\"Hello!\""," every second:",[165,3823,3825],{"className":393,"code":3824,"language":395,"meta":170,"style":170},"export default defineEventHandler((event) => {\n  const stream = createEventStream(event)\n\n  setInterval(\n    () => stream.push('Hello!'),\n    1000\n  )\n\n  return stream.send()\n})\n",[172,3826,3827,3847,3861,3865,3873,3891,3896,3901,3905,3916],{"__ignoreMap":170},[175,3828,3829,3831,3833,3836,3839,3841,3843,3845],{"class":177,"line":178},[175,3830,2064],{"class":410},[175,3832,2067],{"class":410},[175,3834,3835],{"class":511}," defineEventHandler",[175,3837,3838],{"class":406},"((",[175,3840,839],{"class":679},[175,3842,545],{"class":406},[175,3844,712],{"class":402},[175,3846,444],{"class":406},[175,3848,3849,3851,3853,3855,3858],{"class":177,"line":184},[175,3850,1383],{"class":402},[175,3852,1605],{"class":406},[175,3854,411],{"class":410},[175,3856,3857],{"class":511}," createEventStream",[175,3859,3860],{"class":406},"(event)\n",[175,3862,3863],{"class":177,"line":190},[175,3864,212],{"emptyLinePlaceholder":211},[175,3866,3867,3870],{"class":177,"line":196},[175,3868,3869],{"class":511},"  setInterval",[175,3871,3872],{"class":406},"(\n",[175,3874,3875,3878,3880,3883,3885,3887,3889],{"class":177,"line":202},[175,3876,3877],{"class":406},"    () ",[175,3879,712],{"class":402},[175,3881,3882],{"class":406}," stream.",[175,3884,2472],{"class":511},[175,3886,539],{"class":406},[175,3888,1485],{"class":414},[175,3890,2769],{"class":406},[175,3892,3893],{"class":177,"line":208},[175,3894,3895],{"class":427},"    1000\n",[175,3897,3898],{"class":177,"line":215},[175,3899,3900],{"class":406},"  )\n",[175,3902,3903],{"class":177,"line":221},[175,3904,212],{"emptyLinePlaceholder":211},[175,3906,3907,3910,3912,3914],{"class":177,"line":227},[175,3908,3909],{"class":410},"  return",[175,3911,3882],{"class":406},[175,3913,740],{"class":511},[175,3915,515],{"class":406},[175,3917,3918],{"class":177,"line":233},[175,3919,1290],{"class":406},[16,3921,3922],{},"In other words, every second we're sending this event:",[165,3924,3926],{"className":1113,"code":3925,"language":1115,"meta":170,"style":170},"data: Hello!\n",[172,3927,3928],{"__ignoreMap":170},[175,3929,3930,3932],{"class":177,"line":178},[175,3931,3668],{"class":511},[175,3933,3934],{"class":414}," Hello!\n",[16,3936,3937],{},"But, as we mentioned before, we're not limited to regular intervals. We can push events whenever we need. For example, every time there's a purchase, we can push an event with the purchase information. A clean way to do that is with an event bus:",[165,3939,3941],{"className":393,"code":3940,"language":395,"meta":170,"style":170},"import bus from '~/server/utils/bus'\n\nexport default defineEventHandler((event) => {\n  const stream = createEventStream(event)\n\n  bus.hook('purchase', (purchase) => {\n    stream.push(JSON.stringify(purchase))\n  })\n\n  return stream.send()\n})\n",[172,3942,3943,3955,3959,3977,3989,3993,4017,4033,4037,4041,4051],{"__ignoreMap":170},[175,3944,3945,3947,3950,3952],{"class":177,"line":178},[175,3946,1309],{"class":410},[175,3948,3949],{"class":406}," bus ",[175,3951,1315],{"class":410},[175,3953,3954],{"class":414}," '~/server/utils/bus'\n",[175,3956,3957],{"class":177,"line":184},[175,3958,212],{"emptyLinePlaceholder":211},[175,3960,3961,3963,3965,3967,3969,3971,3973,3975],{"class":177,"line":190},[175,3962,2064],{"class":410},[175,3964,2067],{"class":410},[175,3966,3835],{"class":511},[175,3968,3838],{"class":406},[175,3970,839],{"class":679},[175,3972,545],{"class":406},[175,3974,712],{"class":402},[175,3976,444],{"class":406},[175,3978,3979,3981,3983,3985,3987],{"class":177,"line":196},[175,3980,1383],{"class":402},[175,3982,1605],{"class":406},[175,3984,411],{"class":410},[175,3986,3857],{"class":511},[175,3988,3860],{"class":406},[175,3990,3991],{"class":177,"line":202},[175,3992,212],{"emptyLinePlaceholder":211},[175,3994,3995,3998,4001,4003,4006,4008,4011,4013,4015],{"class":177,"line":208},[175,3996,3997],{"class":406},"  bus.",[175,3999,4000],{"class":511},"hook",[175,4002,539],{"class":406},[175,4004,4005],{"class":414},"'purchase'",[175,4007,1446],{"class":406},[175,4009,4010],{"class":679},"purchase",[175,4012,545],{"class":406},[175,4014,712],{"class":402},[175,4016,444],{"class":406},[175,4018,4019,4022,4024,4027,4030],{"class":177,"line":215},[175,4020,4021],{"class":406},"    stream.",[175,4023,2472],{"class":511},[175,4025,4026],{"class":406},"(JSON.",[175,4028,4029],{"class":511},"stringify",[175,4031,4032],{"class":406},"(purchase))\n",[175,4034,4035],{"class":177,"line":221},[175,4036,1492],{"class":406},[175,4038,4039],{"class":177,"line":227},[175,4040,212],{"emptyLinePlaceholder":211},[175,4042,4043,4045,4047,4049],{"class":177,"line":233},[175,4044,3909],{"class":410},[175,4046,3882],{"class":406},[175,4048,740],{"class":511},[175,4050,515],{"class":406},[175,4052,4053],{"class":177,"line":239},[175,4054,1290],{"class":406},[16,4056,4057,4058,4060,4061,4063,4064,2200,4066,4068],{},"If you want more control, ",[172,4059,2472],{}," accepts an object where you can define the ",[172,4062,839],{}," name, ",[172,4065,3757],{},[172,4067,3763],{},", all those keys we mentioned earlier:",[165,4070,4072],{"className":393,"code":4071,"language":395,"meta":170,"style":170},"import bus from '~/server/utils/bus'\n\nexport default defineEventHandler((event) => {\n  const stream = createEventStream(event)\n\n  bus.hook('purchase', (purchase) => {\n    stream.push({\n      event: 'purchase',\n      id: 42,\n      retry: 3000,\n      data: JSON.stringify(purchase)\n    })\n  })\n\n  return stream.send()\n})\n",[172,4073,4074,4084,4088,4106,4118,4122,4142,4150,4158,4168,4178,4188,4192,4196,4200,4210],{"__ignoreMap":170},[175,4075,4076,4078,4080,4082],{"class":177,"line":178},[175,4077,1309],{"class":410},[175,4079,3949],{"class":406},[175,4081,1315],{"class":410},[175,4083,3954],{"class":414},[175,4085,4086],{"class":177,"line":184},[175,4087,212],{"emptyLinePlaceholder":211},[175,4089,4090,4092,4094,4096,4098,4100,4102,4104],{"class":177,"line":190},[175,4091,2064],{"class":410},[175,4093,2067],{"class":410},[175,4095,3835],{"class":511},[175,4097,3838],{"class":406},[175,4099,839],{"class":679},[175,4101,545],{"class":406},[175,4103,712],{"class":402},[175,4105,444],{"class":406},[175,4107,4108,4110,4112,4114,4116],{"class":177,"line":196},[175,4109,1383],{"class":402},[175,4111,1605],{"class":406},[175,4113,411],{"class":410},[175,4115,3857],{"class":511},[175,4117,3860],{"class":406},[175,4119,4120],{"class":177,"line":202},[175,4121,212],{"emptyLinePlaceholder":211},[175,4123,4124,4126,4128,4130,4132,4134,4136,4138,4140],{"class":177,"line":208},[175,4125,3997],{"class":406},[175,4127,4000],{"class":511},[175,4129,539],{"class":406},[175,4131,4005],{"class":414},[175,4133,1446],{"class":406},[175,4135,4010],{"class":679},[175,4137,545],{"class":406},[175,4139,712],{"class":402},[175,4141,444],{"class":406},[175,4143,4144,4146,4148],{"class":177,"line":215},[175,4145,4021],{"class":406},[175,4147,2472],{"class":511},[175,4149,978],{"class":406},[175,4151,4152,4154,4156],{"class":177,"line":221},[175,4153,3312],{"class":406},[175,4155,4005],{"class":414},[175,4157,990],{"class":406},[175,4159,4160,4163,4166],{"class":177,"line":227},[175,4161,4162],{"class":406},"      id: ",[175,4164,4165],{"class":427},"42",[175,4167,990],{"class":406},[175,4169,4170,4173,4176],{"class":177,"line":233},[175,4171,4172],{"class":406},"      retry: ",[175,4174,4175],{"class":427},"3000",[175,4177,990],{"class":406},[175,4179,4180,4183,4185],{"class":177,"line":239},[175,4181,4182],{"class":406},"      data: JSON.",[175,4184,4029],{"class":511},[175,4186,4187],{"class":406},"(purchase)\n",[175,4189,4190],{"class":177,"line":245},[175,4191,1007],{"class":406},[175,4193,4194],{"class":177,"line":250},[175,4195,1492],{"class":406},[175,4197,4198],{"class":177,"line":256},[175,4199,212],{"emptyLinePlaceholder":211},[175,4201,4202,4204,4206,4208],{"class":177,"line":588},[175,4203,3909],{"class":410},[175,4205,3882],{"class":406},[175,4207,740],{"class":511},[175,4209,515],{"class":406},[175,4211,4212],{"class":177,"line":606},[175,4213,1290],{"class":406},[16,4215,4216],{},"As you can imagine, this sends down the pipe something like this:",[165,4218,4220],{"className":1113,"code":4219,"language":1115,"meta":170,"style":170},"event: purchase\nid: 42\nretry: 3000\ndata: {\"total\": 14, \"product\": \"Doom Eternal\"}\n",[172,4221,4222,4229,4236,4243],{"__ignoreMap":170},[175,4223,4224,4226],{"class":177,"line":178},[175,4225,3714],{"class":511},[175,4227,4228],{"class":414}," purchase\n",[175,4230,4231,4233],{"class":177,"line":184},[175,4232,3722],{"class":511},[175,4234,4235],{"class":427}," 42\n",[175,4237,4238,4240],{"class":177,"line":190},[175,4239,3730],{"class":511},[175,4241,4242],{"class":427}," 3000\n",[175,4244,4245,4247,4250,4253,4256],{"class":177,"line":196},[175,4246,3668],{"class":511},[175,4248,4249],{"class":414}," {\"total\":",[175,4251,4252],{"class":414}," 14,",[175,4254,4255],{"class":414}," \"product\":",[175,4257,4258],{"class":414}," \"Doom Eternal\"}\n",[16,4260,4261,4262,4265],{},"Last but not least, ",[56,4263,4264],{},"don't forget to close the connection"," to prevent memory leaks. When the client disconnects, clean up your listeners:",[165,4267,4269],{"className":393,"code":4268,"language":395,"meta":170,"style":170},"import bus from '~/server/utils/bus'\n\nexport default defineEventHandler((event) => {\n  const stream = createEventStream(event)\n\n  const off = bus.hook('purchase', (purchase) => {\n    stream.push({\n      event: 'purchase',\n      id: 42,\n      retry: 3000,\n      data: JSON.stringify(purchase)\n    })\n  })\n\n  stream.onClosed(async () => {\n    off()\n    await stream.close()\n  })\n\n  return stream.send()\n})\n",[172,4270,4271,4281,4285,4303,4315,4319,4347,4355,4363,4371,4379,4387,4391,4395,4399,4417,4424,4435,4439,4443,4453],{"__ignoreMap":170},[175,4272,4273,4275,4277,4279],{"class":177,"line":178},[175,4274,1309],{"class":410},[175,4276,3949],{"class":406},[175,4278,1315],{"class":410},[175,4280,3954],{"class":414},[175,4282,4283],{"class":177,"line":184},[175,4284,212],{"emptyLinePlaceholder":211},[175,4286,4287,4289,4291,4293,4295,4297,4299,4301],{"class":177,"line":190},[175,4288,2064],{"class":410},[175,4290,2067],{"class":410},[175,4292,3835],{"class":511},[175,4294,3838],{"class":406},[175,4296,839],{"class":679},[175,4298,545],{"class":406},[175,4300,712],{"class":402},[175,4302,444],{"class":406},[175,4304,4305,4307,4309,4311,4313],{"class":177,"line":196},[175,4306,1383],{"class":402},[175,4308,1605],{"class":406},[175,4310,411],{"class":410},[175,4312,3857],{"class":511},[175,4314,3860],{"class":406},[175,4316,4317],{"class":177,"line":202},[175,4318,212],{"emptyLinePlaceholder":211},[175,4320,4321,4323,4326,4328,4331,4333,4335,4337,4339,4341,4343,4345],{"class":177,"line":208},[175,4322,1383],{"class":402},[175,4324,4325],{"class":406}," off ",[175,4327,411],{"class":410},[175,4329,4330],{"class":406}," bus.",[175,4332,4000],{"class":511},[175,4334,539],{"class":406},[175,4336,4005],{"class":414},[175,4338,1446],{"class":406},[175,4340,4010],{"class":679},[175,4342,545],{"class":406},[175,4344,712],{"class":402},[175,4346,444],{"class":406},[175,4348,4349,4351,4353],{"class":177,"line":215},[175,4350,4021],{"class":406},[175,4352,2472],{"class":511},[175,4354,978],{"class":406},[175,4356,4357,4359,4361],{"class":177,"line":221},[175,4358,3312],{"class":406},[175,4360,4005],{"class":414},[175,4362,990],{"class":406},[175,4364,4365,4367,4369],{"class":177,"line":227},[175,4366,4162],{"class":406},[175,4368,4165],{"class":427},[175,4370,990],{"class":406},[175,4372,4373,4375,4377],{"class":177,"line":233},[175,4374,4172],{"class":406},[175,4376,4175],{"class":427},[175,4378,990],{"class":406},[175,4380,4381,4383,4385],{"class":177,"line":239},[175,4382,4182],{"class":406},[175,4384,4029],{"class":511},[175,4386,4187],{"class":406},[175,4388,4389],{"class":177,"line":245},[175,4390,1007],{"class":406},[175,4392,4393],{"class":177,"line":250},[175,4394,1492],{"class":406},[175,4396,4397],{"class":177,"line":256},[175,4398,212],{"emptyLinePlaceholder":211},[175,4400,4401,4404,4407,4409,4411,4413,4415],{"class":177,"line":588},[175,4402,4403],{"class":406},"  stream.",[175,4405,4406],{"class":511},"onClosed",[175,4408,539],{"class":406},[175,4410,1592],{"class":410},[175,4412,709],{"class":406},[175,4414,712],{"class":402},[175,4416,444],{"class":406},[175,4418,4419,4422],{"class":177,"line":606},[175,4420,4421],{"class":511},"    off",[175,4423,515],{"class":406},[175,4425,4426,4429,4431,4433],{"class":177,"line":620},[175,4427,4428],{"class":410},"    await",[175,4430,3882],{"class":406},[175,4432,2199],{"class":511},[175,4434,515],{"class":406},[175,4436,4437],{"class":177,"line":633},[175,4438,1492],{"class":406},[175,4440,4441],{"class":177,"line":646},[175,4442,212],{"emptyLinePlaceholder":211},[175,4444,4445,4447,4449,4451],{"class":177,"line":663},[175,4446,3909],{"class":410},[175,4448,3882],{"class":406},[175,4450,740],{"class":511},[175,4452,515],{"class":406},[175,4454,4455],{"class":177,"line":668},[175,4456,1290],{"class":406},[2040,4458,4460],{"className":4459,"icon":2371},[2043],[16,4461,2374],{},[16,4463,4464,4465,126],{},"On the client side, the browser gives you a built-in interface to handle Server-Sent Events: ",[172,4466,3774],{},[165,4468,4470],{"className":393,"code":4469,"language":395,"meta":170,"style":170},"// Built-in browser interface\nconst eventSource = new EventSource('/sse')\n\neventSource.onmessage = (event) => {\n  // Do something...\n}\n",[172,4471,4472,4477,4498,4502,4521,4526],{"__ignoreMap":170},[175,4473,4474],{"class":177,"line":178},[175,4475,4476],{"class":1028},"// Built-in browser interface\n",[175,4478,4479,4481,4484,4486,4488,4491,4493,4496],{"class":177,"line":184},[175,4480,403],{"class":402},[175,4482,4483],{"class":406}," eventSource ",[175,4485,411],{"class":410},[175,4487,527],{"class":410},[175,4489,4490],{"class":511}," EventSource",[175,4492,539],{"class":406},[175,4494,4495],{"class":414},"'/sse'",[175,4497,731],{"class":406},[175,4499,4500],{"class":177,"line":190},[175,4501,212],{"emptyLinePlaceholder":211},[175,4503,4504,4507,4509,4511,4513,4515,4517,4519],{"class":177,"line":196},[175,4505,4506],{"class":406},"eventSource.",[175,4508,832],{"class":511},[175,4510,706],{"class":410},[175,4512,804],{"class":406},[175,4514,839],{"class":679},[175,4516,545],{"class":406},[175,4518,712],{"class":402},[175,4520,444],{"class":406},[175,4522,4523],{"class":177,"line":202},[175,4524,4525],{"class":1028},"  // Do something...\n",[175,4527,4528],{"class":177,"line":208},[175,4529,492],{"class":406},[16,4531,4532,4533,4535,4536,4538,4539,4542,4543,4546],{},"Heads up: ",[172,4534,832],{}," only fires for unnamed ",[172,4537,2133],{}," events. If the server sends a named event like ",[172,4540,4541],{},"event: purchase",", you'll need ",[172,4544,4545],{},"eventSource.addEventListener('purchase', ...)"," instead.",[16,4548,4549,4550,4555,4556,126],{},"But to make things much simpler in Vue, we can use a composable from the fantastic ",[20,4551,4554],{"href":4552,"rel":4553},"https://vueuse.org/",[1052],"VueUse",": ",[172,4557,4558],{},"useEventSource",[165,4560,4562],{"className":1509,"code":4561,"language":1511,"meta":170,"style":170},"\u003Cscript setup>\nimport { useEventSource } from '@vueuse/core'\n\nconst { data } = useEventSource('/sse')\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>{{ data }}\u003C/div>\n\u003C/template>\n",[172,4563,4564,4574,4585,4589,4607,4615,4619,4627,4640],{"__ignoreMap":170},[175,4565,4566,4568,4570,4572],{"class":177,"line":178},[175,4567,1518],{"class":406},[175,4569,1521],{"class":410},[175,4571,1524],{"class":511},[175,4573,1527],{"class":406},[175,4575,4576,4578,4581,4583],{"class":177,"line":184},[175,4577,1309],{"class":410},[175,4579,4580],{"class":406}," { useEventSource } ",[175,4582,1315],{"class":410},[175,4584,2410],{"class":414},[175,4586,4587],{"class":177,"line":190},[175,4588,212],{"emptyLinePlaceholder":211},[175,4590,4591,4593,4596,4598,4601,4603,4605],{"class":177,"line":196},[175,4592,403],{"class":402},[175,4594,4595],{"class":406}," { data } ",[175,4597,411],{"class":410},[175,4599,4600],{"class":511}," useEventSource",[175,4602,539],{"class":406},[175,4604,4495],{"class":414},[175,4606,731],{"class":406},[175,4608,4609,4611,4613],{"class":177,"line":202},[175,4610,1749],{"class":406},[175,4612,1521],{"class":410},[175,4614,1527],{"class":406},[175,4616,4617],{"class":177,"line":208},[175,4618,212],{"emptyLinePlaceholder":211},[175,4620,4621,4623,4625],{"class":177,"line":215},[175,4622,1518],{"class":406},[175,4624,1764],{"class":410},[175,4626,1527],{"class":406},[175,4628,4629,4631,4633,4636,4638],{"class":177,"line":221},[175,4630,1771],{"class":406},[175,4632,2583],{"class":410},[175,4634,4635],{"class":406},">{{ data }}\u003C/",[175,4637,2583],{"class":410},[175,4639,1527],{"class":406},[175,4641,4642,4644,4646],{"class":177,"line":227},[175,4643,1749],{"class":406},[175,4645,1764],{"class":410},[175,4647,1527],{"class":406},[16,4649,4650,4651,4653,4654,4656],{},"As you can see, it takes the URL as a parameter, and we can destructure ",[172,4652,1449],{}," from it. ",[172,4655,1449],{}," is a reactive shallow ref. That means:",[50,4658,4659,4662,4665],{},[53,4660,4661],{},"If you print it in your template, the component will automatically re-render whenever a new event comes in.",[53,4663,4664],{},"The same applies to computed properties: they'll be re-computed whenever data changes.",[53,4666,4667],{},"Of course, you can watch it, and use it as you would with any other ref.",[16,4669,4670,4671,4673],{},"For more control, ",[172,4672,4558],{}," accepts a couple of optional params:",[50,4675,4676,4683],{},[53,4677,4678,4679,4682],{},"We can pass an ",[56,4680,4681],{},"array of event names"," we want to listen to.",[53,4684,4685,4686,4689],{},"And an ",[56,4687,4688],{},"options object."," In this case, we're setting a deserializer: if the server returns stringified JSON, this is how you automatically parse it.",[165,4691,4693],{"className":1509,"code":4692,"language":1511,"meta":170,"style":170},"\u003Cscript setup>\nimport { useEventSource } from '@vueuse/core'\n\nconst { data } = useEventSource('/sse',\n  // Subscribe to this array of events\n  ['purchase'],\n  // Use these options\n  { serializer: { read: JSON.parse } }\n)\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>{{ data?.total }}\u003C/div>\n\u003C/template>\n",[172,4694,4695,4705,4715,4719,4735,4740,4750,4755,4760,4764,4772,4776,4784,4797],{"__ignoreMap":170},[175,4696,4697,4699,4701,4703],{"class":177,"line":178},[175,4698,1518],{"class":406},[175,4700,1521],{"class":410},[175,4702,1524],{"class":511},[175,4704,1527],{"class":406},[175,4706,4707,4709,4711,4713],{"class":177,"line":184},[175,4708,1309],{"class":410},[175,4710,4580],{"class":406},[175,4712,1315],{"class":410},[175,4714,2410],{"class":414},[175,4716,4717],{"class":177,"line":190},[175,4718,212],{"emptyLinePlaceholder":211},[175,4720,4721,4723,4725,4727,4729,4731,4733],{"class":177,"line":196},[175,4722,403],{"class":402},[175,4724,4595],{"class":406},[175,4726,411],{"class":410},[175,4728,4600],{"class":511},[175,4730,539],{"class":406},[175,4732,4495],{"class":414},[175,4734,990],{"class":406},[175,4736,4737],{"class":177,"line":202},[175,4738,4739],{"class":1028},"  // Subscribe to this array of events\n",[175,4741,4742,4745,4747],{"class":177,"line":208},[175,4743,4744],{"class":406},"  [",[175,4746,4005],{"class":414},[175,4748,4749],{"class":406},"],\n",[175,4751,4752],{"class":177,"line":215},[175,4753,4754],{"class":1028},"  // Use these options\n",[175,4756,4757],{"class":177,"line":221},[175,4758,4759],{"class":406},"  { serializer: { read: JSON.parse } }\n",[175,4761,4762],{"class":177,"line":227},[175,4763,731],{"class":406},[175,4765,4766,4768,4770],{"class":177,"line":233},[175,4767,1749],{"class":406},[175,4769,1521],{"class":410},[175,4771,1527],{"class":406},[175,4773,4774],{"class":177,"line":239},[175,4775,212],{"emptyLinePlaceholder":211},[175,4777,4778,4780,4782],{"class":177,"line":245},[175,4779,1518],{"class":406},[175,4781,1764],{"class":410},[175,4783,1527],{"class":406},[175,4785,4786,4788,4790,4793,4795],{"class":177,"line":250},[175,4787,1771],{"class":406},[175,4789,2583],{"class":410},[175,4791,4792],{"class":406},">{{ data?.total }}\u003C/",[175,4794,2583],{"class":410},[175,4796,1527],{"class":406},[175,4798,4799,4801,4803],{"class":177,"line":256},[175,4800,1749],{"class":406},[175,4802,1764],{"class":410},[175,4804,1527],{"class":406},[16,4806,4807,4808,1691,4811,1691,4813,2200,4815,4818],{},"And there are a few more things we can destructure: ",[172,4809,4810],{},"status",[172,4812,839],{},[172,4814,816],{},[172,4816,4817],{},"lastEventId",", which are pretty handy:",[165,4820,4822],{"className":1509,"code":4821,"language":1511,"meta":170,"style":170},"\u003Cscript setup>\nimport { useEventSource } from '@vueuse/core'\n\nconst { data, status, event, error, lastEventId } = useEventSource('/sse',\n  ['purchase'],\n  { serializer: { read: JSON.parse } }\n)\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv>Status: {{ status }}\u003C/div>\n  \u003Cdiv>Event: {{ event }} - Last Event ID: {{ lastEventId }}\u003C/div>\n  \u003Cdiv>{{ data?.total }}\u003C/div>\n\u003C/template>\n",[172,4823,4824,4834,4844,4848,4865,4873,4877,4881,4889,4893,4901,4914,4927,4939],{"__ignoreMap":170},[175,4825,4826,4828,4830,4832],{"class":177,"line":178},[175,4827,1518],{"class":406},[175,4829,1521],{"class":410},[175,4831,1524],{"class":511},[175,4833,1527],{"class":406},[175,4835,4836,4838,4840,4842],{"class":177,"line":184},[175,4837,1309],{"class":410},[175,4839,4580],{"class":406},[175,4841,1315],{"class":410},[175,4843,2410],{"class":414},[175,4845,4846],{"class":177,"line":190},[175,4847,212],{"emptyLinePlaceholder":211},[175,4849,4850,4852,4855,4857,4859,4861,4863],{"class":177,"line":196},[175,4851,403],{"class":402},[175,4853,4854],{"class":406}," { data, status, event, error, lastEventId } ",[175,4856,411],{"class":410},[175,4858,4600],{"class":511},[175,4860,539],{"class":406},[175,4862,4495],{"class":414},[175,4864,990],{"class":406},[175,4866,4867,4869,4871],{"class":177,"line":202},[175,4868,4744],{"class":406},[175,4870,4005],{"class":414},[175,4872,4749],{"class":406},[175,4874,4875],{"class":177,"line":208},[175,4876,4759],{"class":406},[175,4878,4879],{"class":177,"line":215},[175,4880,731],{"class":406},[175,4882,4883,4885,4887],{"class":177,"line":221},[175,4884,1749],{"class":406},[175,4886,1521],{"class":410},[175,4888,1527],{"class":406},[175,4890,4891],{"class":177,"line":227},[175,4892,212],{"emptyLinePlaceholder":211},[175,4894,4895,4897,4899],{"class":177,"line":233},[175,4896,1518],{"class":406},[175,4898,1764],{"class":410},[175,4900,1527],{"class":406},[175,4902,4903,4905,4907,4910,4912],{"class":177,"line":239},[175,4904,1771],{"class":406},[175,4906,2583],{"class":410},[175,4908,4909],{"class":406},">Status: {{ status }}\u003C/",[175,4911,2583],{"class":410},[175,4913,1527],{"class":406},[175,4915,4916,4918,4920,4923,4925],{"class":177,"line":245},[175,4917,1771],{"class":406},[175,4919,2583],{"class":410},[175,4921,4922],{"class":406},">Event: {{ event }} - Last Event ID: {{ lastEventId }}\u003C/",[175,4924,2583],{"class":410},[175,4926,1527],{"class":406},[175,4928,4929,4931,4933,4935,4937],{"class":177,"line":250},[175,4930,1771],{"class":406},[175,4932,2583],{"class":410},[175,4934,4792],{"class":406},[175,4936,2583],{"class":410},[175,4938,1527],{"class":406},[175,4940,4941,4943,4945],{"class":177,"line":256},[175,4942,1749],{"class":406},[175,4944,1764],{"class":410},[175,4946,1527],{"class":406},[128,4948,4950],{"id":4949},"what-happens-when-the-connection-drops","What Happens When the Connection Drops?",[16,4952,4953],{},"Now, one more thing about SSE. What if you're checking a live feed and you lose access to the internet?",[16,4955,4956,4957,4959,4960,4963],{},"Well, the client attempts to reconnect after ",[172,4958,3763],{}," milliseconds, automatically. And when it does, it sends a special ",[172,4961,4962],{},"Last-Event-ID"," header with the last ID it received.",[2040,4965,4967],{"className":4966,"icon":2371},[2043],[16,4968,2047],{},[16,4970,4971],{},"From the server, you can read that header and replay the events the client missed, or handle the situation however you see fit.",[165,4973,4975],{"className":393,"code":4974,"language":395,"meta":170,"style":170},"export default defineEventHandler((event) => {\n  const stream = createEventStream(event)\n  const lastEventId = getHeader(event, 'Last-Event-ID')\n\n  if (lastEventId) {\n    const missed = getEventsSince(lastEventId)\n    missed.forEach(e => stream.push(e))\n  }\n\n  return stream.send()\n})\n",[172,4976,4977,4995,5007,5027,5031,5038,5054,5075,5079,5083,5093],{"__ignoreMap":170},[175,4978,4979,4981,4983,4985,4987,4989,4991,4993],{"class":177,"line":178},[175,4980,2064],{"class":410},[175,4982,2067],{"class":410},[175,4984,3835],{"class":511},[175,4986,3838],{"class":406},[175,4988,839],{"class":679},[175,4990,545],{"class":406},[175,4992,712],{"class":402},[175,4994,444],{"class":406},[175,4996,4997,4999,5001,5003,5005],{"class":177,"line":184},[175,4998,1383],{"class":402},[175,5000,1605],{"class":406},[175,5002,411],{"class":410},[175,5004,3857],{"class":511},[175,5006,3860],{"class":406},[175,5008,5009,5011,5014,5016,5019,5022,5025],{"class":177,"line":190},[175,5010,1383],{"class":402},[175,5012,5013],{"class":406}," lastEventId ",[175,5015,411],{"class":410},[175,5017,5018],{"class":511}," getHeader",[175,5020,5021],{"class":406},"(event, ",[175,5023,5024],{"class":414},"'Last-Event-ID'",[175,5026,731],{"class":406},[175,5028,5029],{"class":177,"line":196},[175,5030,212],{"emptyLinePlaceholder":211},[175,5032,5033,5035],{"class":177,"line":202},[175,5034,966],{"class":410},[175,5036,5037],{"class":406}," (lastEventId) {\n",[175,5039,5040,5043,5046,5048,5051],{"class":177,"line":208},[175,5041,5042],{"class":402},"    const",[175,5044,5045],{"class":406}," missed ",[175,5047,411],{"class":410},[175,5049,5050],{"class":511}," getEventsSince",[175,5052,5053],{"class":406},"(lastEventId)\n",[175,5055,5056,5059,5062,5064,5066,5068,5070,5072],{"class":177,"line":215},[175,5057,5058],{"class":406},"    missed.",[175,5060,5061],{"class":511},"forEach",[175,5063,539],{"class":406},[175,5065,807],{"class":679},[175,5067,1697],{"class":402},[175,5069,3882],{"class":406},[175,5071,2472],{"class":511},[175,5073,5074],{"class":406},"(e))\n",[175,5076,5077],{"class":177,"line":221},[175,5078,765],{"class":406},[175,5080,5081],{"class":177,"line":227},[175,5082,212],{"emptyLinePlaceholder":211},[175,5084,5085,5087,5089,5091],{"class":177,"line":233},[175,5086,3909],{"class":410},[175,5088,3882],{"class":406},[175,5090,740],{"class":511},[175,5092,515],{"class":406},[175,5094,5095],{"class":177,"line":239},[175,5096,1290],{"class":406},[2040,5098,5100],{"className":5099,"icon":2371},[2043],[16,5101,2374],{},[16,5103,5104,5105,5108],{},"On the client side, you can customize the reconnection behavior with the ",[172,5106,5107],{},"autoReconnect"," option:",[165,5110,5112],{"className":393,"code":5111,"language":395,"meta":170,"style":170},"// The client can customize the reconnection behavior\nconst { data } = useEventSource('/api/sse', [], {\n  autoReconnect: {\n    retries: 5,\n    delay: 2000,\n    onFailed: () => console.log('Could not reconnect'),\n  }\n})\n",[172,5113,5114,5119,5137,5141,5149,5157,5175,5179],{"__ignoreMap":170},[175,5115,5116],{"class":177,"line":178},[175,5117,5118],{"class":1028},"// The client can customize the reconnection behavior\n",[175,5120,5121,5123,5125,5127,5129,5131,5134],{"class":177,"line":184},[175,5122,403],{"class":402},[175,5124,4595],{"class":406},[175,5126,411],{"class":410},[175,5128,4600],{"class":511},[175,5130,539],{"class":406},[175,5132,5133],{"class":414},"'/api/sse'",[175,5135,5136],{"class":406},", [], {\n",[175,5138,5139],{"class":177,"line":190},[175,5140,2727],{"class":406},[175,5142,5143,5145,5147],{"class":177,"line":196},[175,5144,2732],{"class":406},[175,5146,2735],{"class":427},[175,5148,990],{"class":406},[175,5150,5151,5153,5155],{"class":177,"line":202},[175,5152,2742],{"class":406},[175,5154,2745],{"class":427},[175,5156,990],{"class":406},[175,5158,5159,5161,5163,5165,5167,5169,5171,5173],{"class":177,"line":208},[175,5160,2752],{"class":511},[175,5162,2755],{"class":406},[175,5164,712],{"class":402},[175,5166,782],{"class":406},[175,5168,723],{"class":511},[175,5170,539],{"class":406},[175,5172,2766],{"class":414},[175,5174,2769],{"class":406},[175,5176,5177],{"class":177,"line":215},[175,5178,765],{"class":406},[175,5180,5181],{"class":177,"line":221},[175,5182,1290],{"class":406},[42,5184,5186],{"id":5185},"sse-are-great-but","SSE Are Great! But...",[16,5188,5189],{},"SSE are great. Perhaps not as popular as other solutions, but fantastic for notifications, live dashboards, live tickers, and more.",[16,5191,5192],{},"But they are a one-way street. It's like a radio: you tune to a station, you receive the signal, but you can't send anything back.",[16,5194,5195,5196,126],{},"What if you need to? What if you want the client to send data to the server using the same pipe? Well, in that case, you can use ",[56,5197,1927],{},[16,5199,5200,5201,126],{},"And that's exactly what we'll cover in ",[20,5202,5203],{"href":35},"Part 2: WebSockets and Real-Time Databases",[16,5205,3392],{},[1872,5207,5208],{},"html pre.shiki code .sOx1s, html code.shiki .sOx1s{--shiki-default:#66D9EF;--shiki-default-font-style:italic}html pre.shiki code .sHkqI, html code.shiki .sHkqI{--shiki-default:#A6E22E}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s_Ekj, html code.shiki .s_Ekj{--shiki-default:#E6DB74}html pre.shiki code .sW0Xf, html code.shiki .sW0Xf{--shiki-default:#FD971F;--shiki-default-font-style:italic}html pre.shiki code .s7s5_, html code.shiki .s7s5_{--shiki-default:#AE81FF}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 pre.shiki code .s8I7P, html code.shiki .s8I7P{--shiki-default:#F92672}html pre.shiki code .snpHw, html code.shiki .snpHw{--shiki-default:#88846F}",{"title":170,"searchDepth":184,"depth":184,"links":5210},[5211,5212,5213,5216,5219],{"id":3457,"depth":184,"text":3458},{"id":3496,"depth":184,"text":3497},{"id":3636,"depth":184,"text":3433,"children":5214},[5215],{"id":3767,"depth":190,"text":3768},{"id":3788,"depth":184,"text":3789,"children":5217},[5218],{"id":4949,"depth":190,"text":4950},{"id":5185,"depth":184,"text":5186},"2026-05-26","A series on adding real-time features to your Vue and Nuxt apps. We kick things off with the bad old days of polling, and then move on to Server-Sent Events, the simplest way to push from server to client.","/img/blog/real-time-vue-apps-part-1-polling-and-server-sent-events.png",{},{"title":3414,"description":5221},"blog/real-time-vue-apps-part-1-polling-and-server-sent-events","lEk20ChuPnDOTzzhwCfPTo39TIvOHHvO9dRsSa5VAAU",{"id":5228,"title":5229,"body":5230,"category":5289,"date":5290,"description":5291,"draft":1890,"extension":1891,"image":5292,"meta":5293,"navigation":211,"path":5294,"seo":5295,"stem":5296,"tighten":211,"__hash__":5297},"blog/blog/chat-with-your-documents-a-practical-guide-to-rag-using-the-new-laravel-ai-sdk.md","Chat with Your Documents: A Practical Guide to RAG Using the New Laravel AI SDK",{"type":8,"value":5231,"toc":5287},[5232,5241,5248,5251,5256,5264,5284],[16,5233,5234,5235,5240],{},"The new ",[20,5236,5239],{"href":5237,"rel":5238},"https://laravel.com/docs/12.x/ai-sdk",[1052],"Laravel AI SDK"," makes it significantly easier to integrate AI into your Laravel applications. In this article I wrote for Tighten, I walk through how to use it to build a RAG system.",[16,5242,5243,5244,5247],{},"What is RAG? ",[1037,5245,5246],{},"Retrieval-Augmented Generation."," Don't let the name intimidate you: it's simply about providing the AI with the right context at the right time, so it can accurately answer your users' questions about your documents.",[16,5249,5250],{},"Check it out at the link below.",[5252,5253],"article-preview",{"image":5254,"link":5255,"title":5229},"https://tighten.com/assets/images/insights/chat-with-your-documents-a-practical-guide-to-rag-using-the-new-laravel-ai-sdk-preview.jpg","https://tighten.com/insights/chat-with-your-documents-a-practical-guide-to-rag-using-the-new-laravel-ai-sdk/",[16,5257,5258,5259,126],{},"This article received ",[20,5260,5263],{"href":5261,"rel":5262},"https://x.com/TightenCo/status/2024162335141536210",[1052],"great feedback on Twitter",[16,5265,5266,5267,5272,5273,2200,5278,5283],{},"It was ",[20,5268,5271],{"href":5269,"rel":5270},"https://x.com/DanielNewns/status/2024786111776624734",[1052],"featured"," in Jump 24's weekly top ten. The original post was ",[20,5274,5277],{"href":5275,"rel":5276},"https://x.com/TightenCo/status/2024162335141536210/retweets",[1052],"retweeted by Laracasts",[20,5279,5282],{"href":5280,"rel":5281},"https://x.com/mattstauffer/status/2024497575517114664",[1052],"this recommendation from Matt Stauffer"," was retweeted by Taylor Otwell.",[16,5285,5286],{},"I'm really happy that so many Laravel developers found it useful!",{"title":170,"searchDepth":184,"depth":184,"links":5288},[],"AI","2026-02-18","The new Laravel AI SDK makes it significantly easier to integrate AI into your Laravel applications. In this article I wrote for Tighten, I walk through how to use it to build a RAG system.","/img/blog/chat-with-your-documents-a-practical-guide-to-rag-using-the-new-laravel-ai-sdk.png",{},"/blog/chat-with-your-documents-a-practical-guide-to-rag-using-the-new-laravel-ai-sdk",{"title":5229,"description":5291},"blog/chat-with-your-documents-a-practical-guide-to-rag-using-the-new-laravel-ai-sdk","5A0CvwUV_OzNgVr-pZKlY0k7FEsdHsG0aCcEw-w2KLE",{"id":3413,"title":3414,"body":5299,"category":1887,"date":5220,"description":5221,"draft":1890,"extension":1891,"image":5222,"meta":6849,"navigation":211,"path":30,"seo":6850,"stem":5225,"tighten":1890,"__hash__":5226},{"type":8,"value":5300,"toc":6838},[5301,5307,5309,5323,5325,5327,5331,5333,5335,5337,5341,5343,5347,5349,5353,5355,5359,5361,5363,5365,5369,5371,5431,5433,5453,5455,5459,5461,5465,5467,5473,5475,5477,5481,5493,5497,5513,5515,5549,5563,5565,5569,5573,5575,5577,5582,5584,5594,5598,5684,5686,5696,5698,5804,5814,5952,5954,5988,5992,6176,6181,6185,6241,6251,6258,6342,6348,6356,6360,6370,6478,6488,6612,6614,6616,6622,6627,6629,6741,6746,6750,6820,6822,6824,6826,6830,6834,6836],[11,5302,5303],{"color":3419,"icon":14},[16,5304,18,5305,24],{},[20,5306,23],{"href":22},[16,5308,3426],{},[50,5310,5311,5315,5319],{},[53,5312,5313],{},[56,5314,3433],{},[53,5316,5317],{},[56,5318,1927],{},[53,5320,3440,5321],{},[56,5322,75],{},[16,5324,3445],{},[16,5326,3448],{},[16,5328,3451,5329],{},[56,5330,3454],{},[42,5332,3458],{"id":3457},[16,5334,3461],{},[16,5336,3464],{},[369,5338,5339],{},[16,5340,3469],{},[16,5342,3472],{},[369,5344,5345],{},[16,5346,3477],{},[16,5348,3480],{},[369,5350,5351],{},[16,5352,3485],{},[16,5354,3488],{},[16,5356,3491,5357,126],{},[56,5358,1918],{},[42,5360,3497],{"id":3496},[16,5362,3500],{},[16,5364,3503],{},[16,5366,3506,5367,3510],{},[56,5368,3509],{},[16,5370,3513],{},[165,5372,5373],{"className":393,"code":3516,"language":395,"meta":170,"style":170},[172,5374,5375,5383,5403,5409,5413,5417,5421],{"__ignoreMap":170},[175,5376,5377,5379,5381],{"class":177,"line":178},[175,5378,671],{"class":402},[175,5380,3525],{"class":511},[175,5382,2511],{"class":406},[175,5384,5385,5387,5389,5391,5393,5395,5397,5399,5401],{"class":177,"line":184},[175,5386,3532],{"class":406},[175,5388,536],{"class":511},[175,5390,539],{"class":406},[175,5392,3539],{"class":414},[175,5394,1691],{"class":406},[175,5396,671],{"class":402},[175,5398,804],{"class":406},[175,5400,1449],{"class":679},[175,5402,683],{"class":406},[175,5404,5405,5407],{"class":177,"line":190},[175,5406,3554],{"class":511},[175,5408,3557],{"class":406},[175,5410,5411],{"class":177,"line":196},[175,5412,1492],{"class":406},[175,5414,5415],{"class":177,"line":202},[175,5416,492],{"class":406},[175,5418,5419],{"class":177,"line":208},[175,5420,212],{"emptyLinePlaceholder":211},[175,5422,5423,5425,5427,5429],{"class":177,"line":215},[175,5424,3574],{"class":511},[175,5426,3577],{"class":406},[175,5428,3580],{"class":427},[175,5430,731],{"class":406},[16,5432,3585],{},[50,5434,5435,5439,5447],{},[53,5436,5437,3593],{},[56,5438,3592],{},[53,5440,5441,3599,5443,3603,5445,3607],{},[56,5442,3598],{},[1037,5444,3602],{},[1037,5446,3606],{},[53,5448,5449,3613,5451],{},[56,5450,3612],{},[1037,5452,3616],{},[16,5454,3619],{},[16,5456,5457,3625],{},[56,5458,3624],{},[16,5460,3628],{},[16,5462,3631,5463,126],{},[56,5464,3433],{},[42,5466,3433],{"id":3636},[16,5468,3639,5469,3643,5471,3647],{},[56,5470,3642],{},[56,5472,3646],{},[16,5474,3650],{},[16,5476,3653],{},[16,5478,3656,5479,1842],{},[172,5480,1449],{},[165,5482,5483],{"className":1113,"code":3661,"language":1115,"meta":170,"style":170},[172,5484,5485],{"__ignoreMap":170},[175,5486,5487,5489,5491],{"class":177,"line":178},[175,5488,3668],{"class":511},[175,5490,3671],{"class":414},[175,5492,3674],{"class":414},[16,5494,3677,5495,3680],{},[172,5496,1449],{},[165,5498,5499],{"className":1113,"code":3683,"language":1115,"meta":170,"style":170},[172,5500,5501],{"__ignoreMap":170},[175,5502,5503,5505,5507,5509,5511],{"class":177,"line":178},[175,5504,3668],{"class":511},[175,5506,3692],{"class":414},[175,5508,3695],{"class":414},[175,5510,3698],{"class":414},[175,5512,3701],{"class":414},[16,5514,3704],{},[165,5516,5517],{"className":1113,"code":3707,"language":1115,"meta":170,"style":170},[172,5518,5519,5525,5531,5537],{"__ignoreMap":170},[175,5520,5521,5523],{"class":177,"line":178},[175,5522,3714],{"class":511},[175,5524,3717],{"class":414},[175,5526,5527,5529],{"class":177,"line":184},[175,5528,3722],{"class":511},[175,5530,3725],{"class":427},[175,5532,5533,5535],{"class":177,"line":190},[175,5534,3730],{"class":511},[175,5536,3733],{"class":427},[175,5538,5539,5541,5543,5545,5547],{"class":177,"line":196},[175,5540,3668],{"class":511},[175,5542,3692],{"class":414},[175,5544,3695],{"class":414},[175,5546,3698],{"class":414},[175,5548,3701],{"class":414},[50,5550,5551,5555,5559],{},[53,5552,5553,3752],{},[172,5554,839],{},[53,5556,5557,3758],{},[172,5558,3757],{},[53,5560,5561,3764],{},[172,5562,3763],{},[128,5564,3768],{"id":3767},[16,5566,3771,5567,3775],{},[172,5568,3774],{},[16,5570,3778,5571,3782],{},[1037,5572,3781],{},[16,5574,3785],{},[42,5576,3789],{"id":3788},[2040,5578,5580],{"className":5579,"icon":2044},[2043],[16,5581,2047],{},[16,5583,3797],{},[16,5585,3800,5586,3804,5588,3808,5590,3811,5592,3814],{},[172,5587,3803],{},[56,5589,3807],{},[172,5591,740],{},[172,5593,2472],{},[16,5595,3817,5596,3821],{},[172,5597,3820],{},[165,5599,5600],{"className":393,"code":3824,"language":395,"meta":170,"style":170},[172,5601,5602,5620,5632,5636,5642,5658,5662,5666,5670,5680],{"__ignoreMap":170},[175,5603,5604,5606,5608,5610,5612,5614,5616,5618],{"class":177,"line":178},[175,5605,2064],{"class":410},[175,5607,2067],{"class":410},[175,5609,3835],{"class":511},[175,5611,3838],{"class":406},[175,5613,839],{"class":679},[175,5615,545],{"class":406},[175,5617,712],{"class":402},[175,5619,444],{"class":406},[175,5621,5622,5624,5626,5628,5630],{"class":177,"line":184},[175,5623,1383],{"class":402},[175,5625,1605],{"class":406},[175,5627,411],{"class":410},[175,5629,3857],{"class":511},[175,5631,3860],{"class":406},[175,5633,5634],{"class":177,"line":190},[175,5635,212],{"emptyLinePlaceholder":211},[175,5637,5638,5640],{"class":177,"line":196},[175,5639,3869],{"class":511},[175,5641,3872],{"class":406},[175,5643,5644,5646,5648,5650,5652,5654,5656],{"class":177,"line":202},[175,5645,3877],{"class":406},[175,5647,712],{"class":402},[175,5649,3882],{"class":406},[175,5651,2472],{"class":511},[175,5653,539],{"class":406},[175,5655,1485],{"class":414},[175,5657,2769],{"class":406},[175,5659,5660],{"class":177,"line":208},[175,5661,3895],{"class":427},[175,5663,5664],{"class":177,"line":215},[175,5665,3900],{"class":406},[175,5667,5668],{"class":177,"line":221},[175,5669,212],{"emptyLinePlaceholder":211},[175,5671,5672,5674,5676,5678],{"class":177,"line":227},[175,5673,3909],{"class":410},[175,5675,3882],{"class":406},[175,5677,740],{"class":511},[175,5679,515],{"class":406},[175,5681,5682],{"class":177,"line":233},[175,5683,1290],{"class":406},[16,5685,3922],{},[165,5687,5688],{"className":1113,"code":3925,"language":1115,"meta":170,"style":170},[172,5689,5690],{"__ignoreMap":170},[175,5691,5692,5694],{"class":177,"line":178},[175,5693,3668],{"class":511},[175,5695,3934],{"class":414},[16,5697,3937],{},[165,5699,5700],{"className":393,"code":3940,"language":395,"meta":170,"style":170},[172,5701,5702,5712,5716,5734,5746,5750,5770,5782,5786,5790,5800],{"__ignoreMap":170},[175,5703,5704,5706,5708,5710],{"class":177,"line":178},[175,5705,1309],{"class":410},[175,5707,3949],{"class":406},[175,5709,1315],{"class":410},[175,5711,3954],{"class":414},[175,5713,5714],{"class":177,"line":184},[175,5715,212],{"emptyLinePlaceholder":211},[175,5717,5718,5720,5722,5724,5726,5728,5730,5732],{"class":177,"line":190},[175,5719,2064],{"class":410},[175,5721,2067],{"class":410},[175,5723,3835],{"class":511},[175,5725,3838],{"class":406},[175,5727,839],{"class":679},[175,5729,545],{"class":406},[175,5731,712],{"class":402},[175,5733,444],{"class":406},[175,5735,5736,5738,5740,5742,5744],{"class":177,"line":196},[175,5737,1383],{"class":402},[175,5739,1605],{"class":406},[175,5741,411],{"class":410},[175,5743,3857],{"class":511},[175,5745,3860],{"class":406},[175,5747,5748],{"class":177,"line":202},[175,5749,212],{"emptyLinePlaceholder":211},[175,5751,5752,5754,5756,5758,5760,5762,5764,5766,5768],{"class":177,"line":208},[175,5753,3997],{"class":406},[175,5755,4000],{"class":511},[175,5757,539],{"class":406},[175,5759,4005],{"class":414},[175,5761,1446],{"class":406},[175,5763,4010],{"class":679},[175,5765,545],{"class":406},[175,5767,712],{"class":402},[175,5769,444],{"class":406},[175,5771,5772,5774,5776,5778,5780],{"class":177,"line":215},[175,5773,4021],{"class":406},[175,5775,2472],{"class":511},[175,5777,4026],{"class":406},[175,5779,4029],{"class":511},[175,5781,4032],{"class":406},[175,5783,5784],{"class":177,"line":221},[175,5785,1492],{"class":406},[175,5787,5788],{"class":177,"line":227},[175,5789,212],{"emptyLinePlaceholder":211},[175,5791,5792,5794,5796,5798],{"class":177,"line":233},[175,5793,3909],{"class":410},[175,5795,3882],{"class":406},[175,5797,740],{"class":511},[175,5799,515],{"class":406},[175,5801,5802],{"class":177,"line":239},[175,5803,1290],{"class":406},[16,5805,4057,5806,4060,5808,4063,5810,2200,5812,4068],{},[172,5807,2472],{},[172,5809,839],{},[172,5811,3757],{},[172,5813,3763],{},[165,5815,5816],{"className":393,"code":4071,"language":395,"meta":170,"style":170},[172,5817,5818,5828,5832,5850,5862,5866,5886,5894,5902,5910,5918,5926,5930,5934,5938,5948],{"__ignoreMap":170},[175,5819,5820,5822,5824,5826],{"class":177,"line":178},[175,5821,1309],{"class":410},[175,5823,3949],{"class":406},[175,5825,1315],{"class":410},[175,5827,3954],{"class":414},[175,5829,5830],{"class":177,"line":184},[175,5831,212],{"emptyLinePlaceholder":211},[175,5833,5834,5836,5838,5840,5842,5844,5846,5848],{"class":177,"line":190},[175,5835,2064],{"class":410},[175,5837,2067],{"class":410},[175,5839,3835],{"class":511},[175,5841,3838],{"class":406},[175,5843,839],{"class":679},[175,5845,545],{"class":406},[175,5847,712],{"class":402},[175,5849,444],{"class":406},[175,5851,5852,5854,5856,5858,5860],{"class":177,"line":196},[175,5853,1383],{"class":402},[175,5855,1605],{"class":406},[175,5857,411],{"class":410},[175,5859,3857],{"class":511},[175,5861,3860],{"class":406},[175,5863,5864],{"class":177,"line":202},[175,5865,212],{"emptyLinePlaceholder":211},[175,5867,5868,5870,5872,5874,5876,5878,5880,5882,5884],{"class":177,"line":208},[175,5869,3997],{"class":406},[175,5871,4000],{"class":511},[175,5873,539],{"class":406},[175,5875,4005],{"class":414},[175,5877,1446],{"class":406},[175,5879,4010],{"class":679},[175,5881,545],{"class":406},[175,5883,712],{"class":402},[175,5885,444],{"class":406},[175,5887,5888,5890,5892],{"class":177,"line":215},[175,5889,4021],{"class":406},[175,5891,2472],{"class":511},[175,5893,978],{"class":406},[175,5895,5896,5898,5900],{"class":177,"line":221},[175,5897,3312],{"class":406},[175,5899,4005],{"class":414},[175,5901,990],{"class":406},[175,5903,5904,5906,5908],{"class":177,"line":227},[175,5905,4162],{"class":406},[175,5907,4165],{"class":427},[175,5909,990],{"class":406},[175,5911,5912,5914,5916],{"class":177,"line":233},[175,5913,4172],{"class":406},[175,5915,4175],{"class":427},[175,5917,990],{"class":406},[175,5919,5920,5922,5924],{"class":177,"line":239},[175,5921,4182],{"class":406},[175,5923,4029],{"class":511},[175,5925,4187],{"class":406},[175,5927,5928],{"class":177,"line":245},[175,5929,1007],{"class":406},[175,5931,5932],{"class":177,"line":250},[175,5933,1492],{"class":406},[175,5935,5936],{"class":177,"line":256},[175,5937,212],{"emptyLinePlaceholder":211},[175,5939,5940,5942,5944,5946],{"class":177,"line":588},[175,5941,3909],{"class":410},[175,5943,3882],{"class":406},[175,5945,740],{"class":511},[175,5947,515],{"class":406},[175,5949,5950],{"class":177,"line":606},[175,5951,1290],{"class":406},[16,5953,4216],{},[165,5955,5956],{"className":1113,"code":4219,"language":1115,"meta":170,"style":170},[172,5957,5958,5964,5970,5976],{"__ignoreMap":170},[175,5959,5960,5962],{"class":177,"line":178},[175,5961,3714],{"class":511},[175,5963,4228],{"class":414},[175,5965,5966,5968],{"class":177,"line":184},[175,5967,3722],{"class":511},[175,5969,4235],{"class":427},[175,5971,5972,5974],{"class":177,"line":190},[175,5973,3730],{"class":511},[175,5975,4242],{"class":427},[175,5977,5978,5980,5982,5984,5986],{"class":177,"line":196},[175,5979,3668],{"class":511},[175,5981,4249],{"class":414},[175,5983,4252],{"class":414},[175,5985,4255],{"class":414},[175,5987,4258],{"class":414},[16,5989,4261,5990,4265],{},[56,5991,4264],{},[165,5993,5994],{"className":393,"code":4268,"language":395,"meta":170,"style":170},[172,5995,5996,6006,6010,6028,6040,6044,6070,6078,6086,6094,6102,6110,6114,6118,6122,6138,6144,6154,6158,6162,6172],{"__ignoreMap":170},[175,5997,5998,6000,6002,6004],{"class":177,"line":178},[175,5999,1309],{"class":410},[175,6001,3949],{"class":406},[175,6003,1315],{"class":410},[175,6005,3954],{"class":414},[175,6007,6008],{"class":177,"line":184},[175,6009,212],{"emptyLinePlaceholder":211},[175,6011,6012,6014,6016,6018,6020,6022,6024,6026],{"class":177,"line":190},[175,6013,2064],{"class":410},[175,6015,2067],{"class":410},[175,6017,3835],{"class":511},[175,6019,3838],{"class":406},[175,6021,839],{"class":679},[175,6023,545],{"class":406},[175,6025,712],{"class":402},[175,6027,444],{"class":406},[175,6029,6030,6032,6034,6036,6038],{"class":177,"line":196},[175,6031,1383],{"class":402},[175,6033,1605],{"class":406},[175,6035,411],{"class":410},[175,6037,3857],{"class":511},[175,6039,3860],{"class":406},[175,6041,6042],{"class":177,"line":202},[175,6043,212],{"emptyLinePlaceholder":211},[175,6045,6046,6048,6050,6052,6054,6056,6058,6060,6062,6064,6066,6068],{"class":177,"line":208},[175,6047,1383],{"class":402},[175,6049,4325],{"class":406},[175,6051,411],{"class":410},[175,6053,4330],{"class":406},[175,6055,4000],{"class":511},[175,6057,539],{"class":406},[175,6059,4005],{"class":414},[175,6061,1446],{"class":406},[175,6063,4010],{"class":679},[175,6065,545],{"class":406},[175,6067,712],{"class":402},[175,6069,444],{"class":406},[175,6071,6072,6074,6076],{"class":177,"line":215},[175,6073,4021],{"class":406},[175,6075,2472],{"class":511},[175,6077,978],{"class":406},[175,6079,6080,6082,6084],{"class":177,"line":221},[175,6081,3312],{"class":406},[175,6083,4005],{"class":414},[175,6085,990],{"class":406},[175,6087,6088,6090,6092],{"class":177,"line":227},[175,6089,4162],{"class":406},[175,6091,4165],{"class":427},[175,6093,990],{"class":406},[175,6095,6096,6098,6100],{"class":177,"line":233},[175,6097,4172],{"class":406},[175,6099,4175],{"class":427},[175,6101,990],{"class":406},[175,6103,6104,6106,6108],{"class":177,"line":239},[175,6105,4182],{"class":406},[175,6107,4029],{"class":511},[175,6109,4187],{"class":406},[175,6111,6112],{"class":177,"line":245},[175,6113,1007],{"class":406},[175,6115,6116],{"class":177,"line":250},[175,6117,1492],{"class":406},[175,6119,6120],{"class":177,"line":256},[175,6121,212],{"emptyLinePlaceholder":211},[175,6123,6124,6126,6128,6130,6132,6134,6136],{"class":177,"line":588},[175,6125,4403],{"class":406},[175,6127,4406],{"class":511},[175,6129,539],{"class":406},[175,6131,1592],{"class":410},[175,6133,709],{"class":406},[175,6135,712],{"class":402},[175,6137,444],{"class":406},[175,6139,6140,6142],{"class":177,"line":606},[175,6141,4421],{"class":511},[175,6143,515],{"class":406},[175,6145,6146,6148,6150,6152],{"class":177,"line":620},[175,6147,4428],{"class":410},[175,6149,3882],{"class":406},[175,6151,2199],{"class":511},[175,6153,515],{"class":406},[175,6155,6156],{"class":177,"line":633},[175,6157,1492],{"class":406},[175,6159,6160],{"class":177,"line":646},[175,6161,212],{"emptyLinePlaceholder":211},[175,6163,6164,6166,6168,6170],{"class":177,"line":663},[175,6165,3909],{"class":410},[175,6167,3882],{"class":406},[175,6169,740],{"class":511},[175,6171,515],{"class":406},[175,6173,6174],{"class":177,"line":668},[175,6175,1290],{"class":406},[2040,6177,6179],{"className":6178,"icon":2371},[2043],[16,6180,2374],{},[16,6182,4464,6183,126],{},[172,6184,3774],{},[165,6186,6187],{"className":393,"code":4469,"language":395,"meta":170,"style":170},[172,6188,6189,6193,6211,6215,6233,6237],{"__ignoreMap":170},[175,6190,6191],{"class":177,"line":178},[175,6192,4476],{"class":1028},[175,6194,6195,6197,6199,6201,6203,6205,6207,6209],{"class":177,"line":184},[175,6196,403],{"class":402},[175,6198,4483],{"class":406},[175,6200,411],{"class":410},[175,6202,527],{"class":410},[175,6204,4490],{"class":511},[175,6206,539],{"class":406},[175,6208,4495],{"class":414},[175,6210,731],{"class":406},[175,6212,6213],{"class":177,"line":190},[175,6214,212],{"emptyLinePlaceholder":211},[175,6216,6217,6219,6221,6223,6225,6227,6229,6231],{"class":177,"line":196},[175,6218,4506],{"class":406},[175,6220,832],{"class":511},[175,6222,706],{"class":410},[175,6224,804],{"class":406},[175,6226,839],{"class":679},[175,6228,545],{"class":406},[175,6230,712],{"class":402},[175,6232,444],{"class":406},[175,6234,6235],{"class":177,"line":202},[175,6236,4525],{"class":1028},[175,6238,6239],{"class":177,"line":208},[175,6240,492],{"class":406},[16,6242,4532,6243,4535,6245,4538,6247,4542,6249,4546],{},[172,6244,832],{},[172,6246,2133],{},[172,6248,4541],{},[172,6250,4545],{},[16,6252,4549,6253,4555,6256,126],{},[20,6254,4554],{"href":4552,"rel":6255},[1052],[172,6257,4558],{},[165,6259,6260],{"className":1509,"code":4561,"language":1511,"meta":170,"style":170},[172,6261,6262,6272,6282,6286,6302,6310,6314,6322,6334],{"__ignoreMap":170},[175,6263,6264,6266,6268,6270],{"class":177,"line":178},[175,6265,1518],{"class":406},[175,6267,1521],{"class":410},[175,6269,1524],{"class":511},[175,6271,1527],{"class":406},[175,6273,6274,6276,6278,6280],{"class":177,"line":184},[175,6275,1309],{"class":410},[175,6277,4580],{"class":406},[175,6279,1315],{"class":410},[175,6281,2410],{"class":414},[175,6283,6284],{"class":177,"line":190},[175,6285,212],{"emptyLinePlaceholder":211},[175,6287,6288,6290,6292,6294,6296,6298,6300],{"class":177,"line":196},[175,6289,403],{"class":402},[175,6291,4595],{"class":406},[175,6293,411],{"class":410},[175,6295,4600],{"class":511},[175,6297,539],{"class":406},[175,6299,4495],{"class":414},[175,6301,731],{"class":406},[175,6303,6304,6306,6308],{"class":177,"line":202},[175,6305,1749],{"class":406},[175,6307,1521],{"class":410},[175,6309,1527],{"class":406},[175,6311,6312],{"class":177,"line":208},[175,6313,212],{"emptyLinePlaceholder":211},[175,6315,6316,6318,6320],{"class":177,"line":215},[175,6317,1518],{"class":406},[175,6319,1764],{"class":410},[175,6321,1527],{"class":406},[175,6323,6324,6326,6328,6330,6332],{"class":177,"line":221},[175,6325,1771],{"class":406},[175,6327,2583],{"class":410},[175,6329,4635],{"class":406},[175,6331,2583],{"class":410},[175,6333,1527],{"class":406},[175,6335,6336,6338,6340],{"class":177,"line":227},[175,6337,1749],{"class":406},[175,6339,1764],{"class":410},[175,6341,1527],{"class":406},[16,6343,4650,6344,4653,6346,4656],{},[172,6345,1449],{},[172,6347,1449],{},[50,6349,6350,6352,6354],{},[53,6351,4661],{},[53,6353,4664],{},[53,6355,4667],{},[16,6357,4670,6358,4673],{},[172,6359,4558],{},[50,6361,6362,6366],{},[53,6363,4678,6364,4682],{},[56,6365,4681],{},[53,6367,4685,6368,4689],{},[56,6369,4688],{},[165,6371,6372],{"className":1509,"code":4692,"language":1511,"meta":170,"style":170},[172,6373,6374,6384,6394,6398,6414,6418,6426,6430,6434,6438,6446,6450,6458,6470],{"__ignoreMap":170},[175,6375,6376,6378,6380,6382],{"class":177,"line":178},[175,6377,1518],{"class":406},[175,6379,1521],{"class":410},[175,6381,1524],{"class":511},[175,6383,1527],{"class":406},[175,6385,6386,6388,6390,6392],{"class":177,"line":184},[175,6387,1309],{"class":410},[175,6389,4580],{"class":406},[175,6391,1315],{"class":410},[175,6393,2410],{"class":414},[175,6395,6396],{"class":177,"line":190},[175,6397,212],{"emptyLinePlaceholder":211},[175,6399,6400,6402,6404,6406,6408,6410,6412],{"class":177,"line":196},[175,6401,403],{"class":402},[175,6403,4595],{"class":406},[175,6405,411],{"class":410},[175,6407,4600],{"class":511},[175,6409,539],{"class":406},[175,6411,4495],{"class":414},[175,6413,990],{"class":406},[175,6415,6416],{"class":177,"line":202},[175,6417,4739],{"class":1028},[175,6419,6420,6422,6424],{"class":177,"line":208},[175,6421,4744],{"class":406},[175,6423,4005],{"class":414},[175,6425,4749],{"class":406},[175,6427,6428],{"class":177,"line":215},[175,6429,4754],{"class":1028},[175,6431,6432],{"class":177,"line":221},[175,6433,4759],{"class":406},[175,6435,6436],{"class":177,"line":227},[175,6437,731],{"class":406},[175,6439,6440,6442,6444],{"class":177,"line":233},[175,6441,1749],{"class":406},[175,6443,1521],{"class":410},[175,6445,1527],{"class":406},[175,6447,6448],{"class":177,"line":239},[175,6449,212],{"emptyLinePlaceholder":211},[175,6451,6452,6454,6456],{"class":177,"line":245},[175,6453,1518],{"class":406},[175,6455,1764],{"class":410},[175,6457,1527],{"class":406},[175,6459,6460,6462,6464,6466,6468],{"class":177,"line":250},[175,6461,1771],{"class":406},[175,6463,2583],{"class":410},[175,6465,4792],{"class":406},[175,6467,2583],{"class":410},[175,6469,1527],{"class":406},[175,6471,6472,6474,6476],{"class":177,"line":256},[175,6473,1749],{"class":406},[175,6475,1764],{"class":410},[175,6477,1527],{"class":406},[16,6479,4807,6480,1691,6482,1691,6484,2200,6486,4818],{},[172,6481,4810],{},[172,6483,839],{},[172,6485,816],{},[172,6487,4817],{},[165,6489,6490],{"className":1509,"code":4821,"language":1511,"meta":170,"style":170},[172,6491,6492,6502,6512,6516,6532,6540,6544,6548,6556,6560,6568,6580,6592,6604],{"__ignoreMap":170},[175,6493,6494,6496,6498,6500],{"class":177,"line":178},[175,6495,1518],{"class":406},[175,6497,1521],{"class":410},[175,6499,1524],{"class":511},[175,6501,1527],{"class":406},[175,6503,6504,6506,6508,6510],{"class":177,"line":184},[175,6505,1309],{"class":410},[175,6507,4580],{"class":406},[175,6509,1315],{"class":410},[175,6511,2410],{"class":414},[175,6513,6514],{"class":177,"line":190},[175,6515,212],{"emptyLinePlaceholder":211},[175,6517,6518,6520,6522,6524,6526,6528,6530],{"class":177,"line":196},[175,6519,403],{"class":402},[175,6521,4854],{"class":406},[175,6523,411],{"class":410},[175,6525,4600],{"class":511},[175,6527,539],{"class":406},[175,6529,4495],{"class":414},[175,6531,990],{"class":406},[175,6533,6534,6536,6538],{"class":177,"line":202},[175,6535,4744],{"class":406},[175,6537,4005],{"class":414},[175,6539,4749],{"class":406},[175,6541,6542],{"class":177,"line":208},[175,6543,4759],{"class":406},[175,6545,6546],{"class":177,"line":215},[175,6547,731],{"class":406},[175,6549,6550,6552,6554],{"class":177,"line":221},[175,6551,1749],{"class":406},[175,6553,1521],{"class":410},[175,6555,1527],{"class":406},[175,6557,6558],{"class":177,"line":227},[175,6559,212],{"emptyLinePlaceholder":211},[175,6561,6562,6564,6566],{"class":177,"line":233},[175,6563,1518],{"class":406},[175,6565,1764],{"class":410},[175,6567,1527],{"class":406},[175,6569,6570,6572,6574,6576,6578],{"class":177,"line":239},[175,6571,1771],{"class":406},[175,6573,2583],{"class":410},[175,6575,4909],{"class":406},[175,6577,2583],{"class":410},[175,6579,1527],{"class":406},[175,6581,6582,6584,6586,6588,6590],{"class":177,"line":245},[175,6583,1771],{"class":406},[175,6585,2583],{"class":410},[175,6587,4922],{"class":406},[175,6589,2583],{"class":410},[175,6591,1527],{"class":406},[175,6593,6594,6596,6598,6600,6602],{"class":177,"line":250},[175,6595,1771],{"class":406},[175,6597,2583],{"class":410},[175,6599,4792],{"class":406},[175,6601,2583],{"class":410},[175,6603,1527],{"class":406},[175,6605,6606,6608,6610],{"class":177,"line":256},[175,6607,1749],{"class":406},[175,6609,1764],{"class":410},[175,6611,1527],{"class":406},[128,6613,4950],{"id":4949},[16,6615,4953],{},[16,6617,4956,6618,4959,6620,4963],{},[172,6619,3763],{},[172,6621,4962],{},[2040,6623,6625],{"className":6624,"icon":2371},[2043],[16,6626,2047],{},[16,6628,4971],{},[165,6630,6631],{"className":393,"code":4974,"language":395,"meta":170,"style":170},[172,6632,6633,6651,6663,6679,6683,6689,6701,6719,6723,6727,6737],{"__ignoreMap":170},[175,6634,6635,6637,6639,6641,6643,6645,6647,6649],{"class":177,"line":178},[175,6636,2064],{"class":410},[175,6638,2067],{"class":410},[175,6640,3835],{"class":511},[175,6642,3838],{"class":406},[175,6644,839],{"class":679},[175,6646,545],{"class":406},[175,6648,712],{"class":402},[175,6650,444],{"class":406},[175,6652,6653,6655,6657,6659,6661],{"class":177,"line":184},[175,6654,1383],{"class":402},[175,6656,1605],{"class":406},[175,6658,411],{"class":410},[175,6660,3857],{"class":511},[175,6662,3860],{"class":406},[175,6664,6665,6667,6669,6671,6673,6675,6677],{"class":177,"line":190},[175,6666,1383],{"class":402},[175,6668,5013],{"class":406},[175,6670,411],{"class":410},[175,6672,5018],{"class":511},[175,6674,5021],{"class":406},[175,6676,5024],{"class":414},[175,6678,731],{"class":406},[175,6680,6681],{"class":177,"line":196},[175,6682,212],{"emptyLinePlaceholder":211},[175,6684,6685,6687],{"class":177,"line":202},[175,6686,966],{"class":410},[175,6688,5037],{"class":406},[175,6690,6691,6693,6695,6697,6699],{"class":177,"line":208},[175,6692,5042],{"class":402},[175,6694,5045],{"class":406},[175,6696,411],{"class":410},[175,6698,5050],{"class":511},[175,6700,5053],{"class":406},[175,6702,6703,6705,6707,6709,6711,6713,6715,6717],{"class":177,"line":215},[175,6704,5058],{"class":406},[175,6706,5061],{"class":511},[175,6708,539],{"class":406},[175,6710,807],{"class":679},[175,6712,1697],{"class":402},[175,6714,3882],{"class":406},[175,6716,2472],{"class":511},[175,6718,5074],{"class":406},[175,6720,6721],{"class":177,"line":221},[175,6722,765],{"class":406},[175,6724,6725],{"class":177,"line":227},[175,6726,212],{"emptyLinePlaceholder":211},[175,6728,6729,6731,6733,6735],{"class":177,"line":233},[175,6730,3909],{"class":410},[175,6732,3882],{"class":406},[175,6734,740],{"class":511},[175,6736,515],{"class":406},[175,6738,6739],{"class":177,"line":239},[175,6740,1290],{"class":406},[2040,6742,6744],{"className":6743,"icon":2371},[2043],[16,6745,2374],{},[16,6747,5104,6748,5108],{},[172,6749,5107],{},[165,6751,6752],{"className":393,"code":5111,"language":395,"meta":170,"style":170},[172,6753,6754,6758,6774,6778,6786,6794,6812,6816],{"__ignoreMap":170},[175,6755,6756],{"class":177,"line":178},[175,6757,5118],{"class":1028},[175,6759,6760,6762,6764,6766,6768,6770,6772],{"class":177,"line":184},[175,6761,403],{"class":402},[175,6763,4595],{"class":406},[175,6765,411],{"class":410},[175,6767,4600],{"class":511},[175,6769,539],{"class":406},[175,6771,5133],{"class":414},[175,6773,5136],{"class":406},[175,6775,6776],{"class":177,"line":190},[175,6777,2727],{"class":406},[175,6779,6780,6782,6784],{"class":177,"line":196},[175,6781,2732],{"class":406},[175,6783,2735],{"class":427},[175,6785,990],{"class":406},[175,6787,6788,6790,6792],{"class":177,"line":202},[175,6789,2742],{"class":406},[175,6791,2745],{"class":427},[175,6793,990],{"class":406},[175,6795,6796,6798,6800,6802,6804,6806,6808,6810],{"class":177,"line":208},[175,6797,2752],{"class":511},[175,6799,2755],{"class":406},[175,6801,712],{"class":402},[175,6803,782],{"class":406},[175,6805,723],{"class":511},[175,6807,539],{"class":406},[175,6809,2766],{"class":414},[175,6811,2769],{"class":406},[175,6813,6814],{"class":177,"line":215},[175,6815,765],{"class":406},[175,6817,6818],{"class":177,"line":221},[175,6819,1290],{"class":406},[42,6821,5186],{"id":5185},[16,6823,5189],{},[16,6825,5192],{},[16,6827,5195,6828,126],{},[56,6829,1927],{},[16,6831,5200,6832,126],{},[20,6833,5203],{"href":35},[16,6835,3392],{},[1872,6837,5208],{},{"title":170,"searchDepth":184,"depth":184,"links":6839},[6840,6841,6842,6845,6848],{"id":3457,"depth":184,"text":3458},{"id":3496,"depth":184,"text":3497},{"id":3636,"depth":184,"text":3433,"children":6843},[6844],{"id":3767,"depth":190,"text":3768},{"id":3788,"depth":184,"text":3789,"children":6846},[6847],{"id":4949,"depth":190,"text":4950},{"id":5185,"depth":184,"text":5186},{},{"title":3414,"description":5221},[6852,8271,9474],{"id":5,"title":6,"body":6853,"category":1887,"date":1888,"description":1889,"draft":1890,"extension":1891,"image":1892,"meta":8269,"navigation":211,"path":1894,"seo":8270,"stem":1896,"tighten":1890,"__hash__":1897},{"type":8,"value":6854,"toc":8257},[6855,6861,6867,6869,6871,6873,6887,6889,6893,6895,6897,6909,6911,6913,6923,6925,6927,6929,6935,6937,6939,6945,7005,7007,7017,7019,7023,7025,7029,7033,7045,7047,7051,7053,7061,7063,7065,7069,7071,7073,7075,7077,7081,7085,7087,7089,7091,7575,7577,7581,7583,7590,7592,7600,7602,7604,7608,7630,7632,7710,7714,7772,7778,7948,7950,7952,7954,8222,8224,8226,8228,8246,8248,8253,8255],[11,6856,6857],{"color":13,"icon":14},[16,6858,18,6859,24],{},[20,6860,23],{"href":22},[16,6862,27,6863,32,6865,37],{},[20,6864,31],{"href":30},[20,6866,36],{"href":35},[16,6868,40],{},[42,6870,45],{"id":44},[16,6872,48],{},[50,6874,6875,6879,6883],{},[53,6876,6877,59],{},[56,6878,58],{},[53,6880,6881,65],{},[56,6882,64],{},[53,6884,6885,71],{},[56,6886,70],{},[42,6888,75],{"id":74},[16,6890,78,6891,81],{},[56,6892,75],{},[16,6894,84],{},[16,6896,87],{},[50,6898,6899,6901,6903,6905,6907],{},[53,6900,92],{},[53,6902,95],{},[53,6904,98],{},[53,6906,101],{},[53,6908,104],{},[16,6910,107],{},[16,6912,110],{},[50,6914,6915,6919],{},[53,6916,115,6917,119],{},[56,6918,118],{},[53,6920,122,6921,126],{},[56,6922,125],{},[128,6924,131],{"id":130},[16,6926,134],{},[16,6928,137],{},[50,6930,6931,6933],{},[53,6932,142],{},[53,6934,145],{},[16,6936,148],{},[150,6938,153],{"id":152},[16,6940,156,6941,159,6943,163],{},[56,6942,153],{},[56,6944,162],{},[165,6946,6947],{"className":167,"code":168,"language":169,"meta":170,"style":170},[172,6948,6949,6953,6957,6961,6965,6969,6973,6977,6981,6985,6989,6993,6997,7001],{"__ignoreMap":170},[175,6950,6951],{"class":177,"line":178},[175,6952,181],{},[175,6954,6955],{"class":177,"line":184},[175,6956,187],{},[175,6958,6959],{"class":177,"line":190},[175,6960,193],{},[175,6962,6963],{"class":177,"line":196},[175,6964,199],{},[175,6966,6967],{"class":177,"line":202},[175,6968,205],{},[175,6970,6971],{"class":177,"line":208},[175,6972,212],{"emptyLinePlaceholder":211},[175,6974,6975],{"class":177,"line":215},[175,6976,218],{},[175,6978,6979],{"class":177,"line":221},[175,6980,224],{},[175,6982,6983],{"class":177,"line":227},[175,6984,230],{},[175,6986,6987],{"class":177,"line":233},[175,6988,236],{},[175,6990,6991],{"class":177,"line":239},[175,6992,242],{},[175,6994,6995],{"class":177,"line":245},[175,6996,212],{"emptyLinePlaceholder":211},[175,6998,6999],{"class":177,"line":250},[175,7000,253],{},[175,7002,7003],{"class":177,"line":256},[175,7004,259],{},[16,7006,262],{},[50,7008,7009,7011,7013],{},[53,7010,267],{},[53,7012,270],{},[53,7014,273,7015],{},[56,7016,276],{},[150,7018,280],{"id":279},[16,7020,283,7021,287],{},[56,7022,286],{},[150,7024,291],{"id":290},[16,7026,294,7027,298],{},[172,7028,297],{},[16,7030,301,7031,304],{},[56,7032,95],{},[165,7034,7035],{"className":167,"code":307,"language":169,"meta":170,"style":170},[172,7036,7037,7041],{"__ignoreMap":170},[175,7038,7039],{"class":177,"line":178},[175,7040,314],{},[175,7042,7043],{"class":177,"line":184},[175,7044,319],{},[150,7046,323],{"id":322},[16,7048,326,7049,329],{},[56,7050,323],{},[16,7052,332],{},[165,7054,7055],{"className":167,"code":335,"language":169,"meta":170,"style":170},[172,7056,7057],{"__ignoreMap":170},[175,7058,7059],{"class":177,"line":178},[175,7060,335],{},[16,7062,344],{},[150,7064,348],{"id":347},[16,7066,351,7067,354],{},[56,7068,348],{},[16,7070,357],{},[128,7072,361],{"id":360},[16,7074,364],{},[16,7076,367],{},[369,7078,7079],{},[16,7080,373],{},[16,7082,376,7083,380],{},[56,7084,379],{},[16,7086,383],{},[42,7088,387],{"id":386},[16,7090,390],{},[165,7092,7093],{"className":393,"code":394,"language":395,"meta":170,"style":170},[172,7094,7095,7105,7115,7119,7129,7133,7141,7157,7161,7165,7169,7183,7209,7235,7239,7253,7263,7273,7283,7297,7301,7313,7321,7335,7347,7367,7371,7393,7419,7445,7449,7453,7459,7473,7481,7501,7505,7509,7527,7533,7539,7547,7555,7559,7563,7567,7571],{"__ignoreMap":170},[175,7096,7097,7099,7101,7103],{"class":177,"line":178},[175,7098,403],{"class":402},[175,7100,407],{"class":406},[175,7102,411],{"class":410},[175,7104,415],{"class":414},[175,7106,7107,7109,7111,7113],{"class":177,"line":184},[175,7108,403],{"class":402},[175,7110,422],{"class":406},[175,7112,411],{"class":410},[175,7114,428],{"class":427},[175,7116,7117],{"class":177,"line":190},[175,7118,212],{"emptyLinePlaceholder":211},[175,7120,7121,7123,7125,7127],{"class":177,"line":196},[175,7122,403],{"class":402},[175,7124,439],{"class":406},[175,7126,411],{"class":410},[175,7128,444],{"class":406},[175,7130,7131],{"class":177,"line":202},[175,7132,449],{"class":406},[175,7134,7135,7137,7139],{"class":177,"line":208},[175,7136,454],{"class":406},[175,7138,457],{"class":414},[175,7140,460],{"class":406},[175,7142,7143,7145,7147,7149,7151,7153,7155],{"class":177,"line":215},[175,7144,454],{"class":406},[175,7146,467],{"class":414},[175,7148,470],{"class":406},[175,7150,473],{"class":414},[175,7152,476],{"class":406},[175,7154,479],{"class":414},[175,7156,482],{"class":406},[175,7158,7159],{"class":177,"line":221},[175,7160,487],{"class":406},[175,7162,7163],{"class":177,"line":227},[175,7164,492],{"class":406},[175,7166,7167],{"class":177,"line":233},[175,7168,212],{"emptyLinePlaceholder":211},[175,7170,7171,7173,7175,7177,7179,7181],{"class":177,"line":239},[175,7172,403],{"class":402},[175,7174,503],{"class":406},[175,7176,411],{"class":410},[175,7178,508],{"class":406},[175,7180,512],{"class":511},[175,7182,515],{"class":406},[175,7184,7185,7187,7189,7191,7193,7195,7197,7199,7201,7203,7205,7207],{"class":177,"line":245},[175,7186,403],{"class":402},[175,7188,522],{"class":406},[175,7190,411],{"class":410},[175,7192,527],{"class":410},[175,7194,530],{"class":511},[175,7196,533],{"class":406},[175,7198,536],{"class":511},[175,7200,539],{"class":406},[175,7202,542],{"class":414},[175,7204,545],{"class":406},[175,7206,548],{"class":410},[175,7208,551],{"class":414},[175,7210,7211,7213,7215,7217,7219,7221,7223,7225,7227,7229,7231,7233],{"class":177,"line":250},[175,7212,403],{"class":402},[175,7214,558],{"class":406},[175,7216,411],{"class":410},[175,7218,527],{"class":410},[175,7220,530],{"class":511},[175,7222,533],{"class":406},[175,7224,536],{"class":511},[175,7226,539],{"class":406},[175,7228,573],{"class":414},[175,7230,545],{"class":406},[175,7232,578],{"class":410},[175,7234,581],{"class":414},[175,7236,7237],{"class":177,"line":256},[175,7238,212],{"emptyLinePlaceholder":211},[175,7240,7241,7243,7245,7247,7249,7251],{"class":177,"line":588},[175,7242,403],{"class":402},[175,7244,593],{"class":406},[175,7246,411],{"class":410},[175,7248,527],{"class":410},[175,7250,600],{"class":511},[175,7252,603],{"class":406},[175,7254,7255,7257,7259,7261],{"class":177,"line":606},[175,7256,609],{"class":402},[175,7258,612],{"class":406},[175,7260,411],{"class":410},[175,7262,617],{"class":427},[175,7264,7265,7267,7269,7271],{"class":177,"line":620},[175,7266,609],{"class":402},[175,7268,625],{"class":406},[175,7270,411],{"class":410},[175,7272,630],{"class":427},[175,7274,7275,7277,7279,7281],{"class":177,"line":633},[175,7276,403],{"class":402},[175,7278,638],{"class":406},[175,7280,411],{"class":410},[175,7282,643],{"class":406},[175,7284,7285,7287,7289,7291,7293,7295],{"class":177,"line":646},[175,7286,403],{"class":402},[175,7288,651],{"class":406},[175,7290,411],{"class":410},[175,7292,527],{"class":410},[175,7294,658],{"class":511},[175,7296,515],{"class":406},[175,7298,7299],{"class":177,"line":663},[175,7300,212],{"emptyLinePlaceholder":211},[175,7302,7303,7305,7307,7309,7311],{"class":177,"line":668},[175,7304,671],{"class":402},[175,7306,674],{"class":511},[175,7308,539],{"class":406},[175,7310,680],{"class":679},[175,7312,683],{"class":406},[175,7314,7315,7317,7319],{"class":177,"line":686},[175,7316,689],{"class":406},[175,7318,411],{"class":410},[175,7320,694],{"class":406},[175,7322,7323,7325,7327,7329,7331,7333],{"class":177,"line":697},[175,7324,700],{"class":406},[175,7326,703],{"class":511},[175,7328,706],{"class":410},[175,7330,709],{"class":406},[175,7332,712],{"class":402},[175,7334,444],{"class":406},[175,7336,7337,7339,7341,7343,7345],{"class":177,"line":717},[175,7338,720],{"class":406},[175,7340,723],{"class":511},[175,7342,539],{"class":406},[175,7344,728],{"class":414},[175,7346,731],{"class":406},[175,7348,7349,7351,7353,7355,7357,7359,7361,7363,7365],{"class":177,"line":734},[175,7350,737],{"class":406},[175,7352,740],{"class":511},[175,7354,539],{"class":406},[175,7356,745],{"class":414},[175,7358,748],{"class":410},[175,7360,751],{"class":406},[175,7362,754],{"class":410},[175,7364,757],{"class":414},[175,7366,731],{"class":406},[175,7368,7369],{"class":177,"line":762},[175,7370,765],{"class":406},[175,7372,7373,7375,7377,7379,7381,7383,7385,7387,7389,7391],{"class":177,"line":768},[175,7374,700],{"class":406},[175,7376,773],{"class":511},[175,7378,706],{"class":410},[175,7380,709],{"class":406},[175,7382,712],{"class":402},[175,7384,782],{"class":406},[175,7386,723],{"class":511},[175,7388,539],{"class":406},[175,7390,789],{"class":414},[175,7392,731],{"class":406},[175,7394,7395,7397,7399,7401,7403,7405,7407,7409,7411,7413,7415,7417],{"class":177,"line":794},[175,7396,700],{"class":406},[175,7398,799],{"class":511},[175,7400,706],{"class":410},[175,7402,804],{"class":406},[175,7404,807],{"class":679},[175,7406,545],{"class":406},[175,7408,712],{"class":402},[175,7410,782],{"class":406},[175,7412,816],{"class":511},[175,7414,539],{"class":406},[175,7416,821],{"class":414},[175,7418,824],{"class":406},[175,7420,7421,7423,7425,7427,7429,7431,7433,7435,7437,7439,7441,7443],{"class":177,"line":827},[175,7422,700],{"class":406},[175,7424,832],{"class":511},[175,7426,706],{"class":410},[175,7428,804],{"class":406},[175,7430,839],{"class":679},[175,7432,545],{"class":406},[175,7434,712],{"class":402},[175,7436,782],{"class":406},[175,7438,723],{"class":511},[175,7440,539],{"class":406},[175,7442,852],{"class":414},[175,7444,855],{"class":406},[175,7446,7447],{"class":177,"line":858},[175,7448,492],{"class":406},[175,7450,7451],{"class":177,"line":863},[175,7452,212],{"emptyLinePlaceholder":211},[175,7454,7455,7457],{"class":177,"line":868},[175,7456,871],{"class":410},[175,7458,874],{"class":406},[175,7460,7461,7463,7465,7467,7469,7471],{"class":177,"line":877},[175,7462,880],{"class":511},[175,7464,883],{"class":406},[175,7466,886],{"class":511},[175,7468,539],{"class":406},[175,7470,891],{"class":414},[175,7472,894],{"class":406},[175,7474,7475,7477,7479],{"class":177,"line":897},[175,7476,900],{"class":406},[175,7478,903],{"class":410},[175,7480,444],{"class":406},[175,7482,7483,7485,7487,7489,7491,7493,7495,7497,7499],{"class":177,"line":908},[175,7484,911],{"class":406},[175,7486,914],{"class":511},[175,7488,706],{"class":410},[175,7490,804],{"class":406},[175,7492,839],{"class":679},[175,7494,545],{"class":406},[175,7496,712],{"class":402},[175,7498,674],{"class":511},[175,7500,929],{"class":406},[175,7502,7503],{"class":177,"line":932},[175,7504,492],{"class":406},[175,7506,7507],{"class":177,"line":937},[175,7508,212],{"emptyLinePlaceholder":211},[175,7510,7511,7513,7515,7517,7519,7521,7523,7525],{"class":177,"line":942},[175,7512,945],{"class":406},[175,7514,948],{"class":511},[175,7516,706],{"class":410},[175,7518,804],{"class":406},[175,7520,839],{"class":679},[175,7522,545],{"class":406},[175,7524,712],{"class":402},[175,7526,444],{"class":406},[175,7528,7529,7531],{"class":177,"line":963},[175,7530,966],{"class":410},[175,7532,969],{"class":406},[175,7534,7535,7537],{"class":177,"line":972},[175,7536,975],{"class":511},[175,7538,978],{"class":406},[175,7540,7541,7543,7545],{"class":177,"line":981},[175,7542,984],{"class":406},[175,7544,987],{"class":414},[175,7546,990],{"class":406},[175,7548,7549,7551,7553],{"class":177,"line":993},[175,7550,996],{"class":406},[175,7552,999],{"class":511},[175,7554,515],{"class":406},[175,7556,7557],{"class":177,"line":1004},[175,7558,1007],{"class":406},[175,7560,7561],{"class":177,"line":1010},[175,7562,765],{"class":406},[175,7564,7565],{"class":177,"line":1015},[175,7566,492],{"class":406},[175,7568,7569],{"class":177,"line":1020},[175,7570,212],{"emptyLinePlaceholder":211},[175,7572,7573],{"class":177,"line":1025},[175,7574,1029],{"class":1028},[16,7576,1032],{},[16,7578,1035,7579,1040],{},[1037,7580,1039],{},[42,7582,1044],{"id":1043},[16,7584,1047,7585,1056],{},[20,7586,7588],{"href":1050,"rel":7587},[1052],[56,7589,1055],{},[16,7591,1059],{},[165,7593,7594],{"className":167,"code":1062,"language":169,"meta":170,"style":170},[172,7595,7596],{"__ignoreMap":170},[175,7597,7598],{"class":177,"line":178},[175,7599,1062],{},[16,7601,1071],{},[16,7603,1074],{},[16,7605,1077,7606,1081],{},[172,7607,1080],{},[165,7609,7610],{"className":393,"code":1084,"language":395,"meta":170,"style":170},[172,7611,7612],{"__ignoreMap":170},[175,7613,7614,7616,7618,7620,7622,7624,7626,7628],{"class":177,"line":178},[175,7615,403],{"class":402},[175,7617,1093],{"class":406},[175,7619,411],{"class":410},[175,7621,527],{"class":410},[175,7623,1100],{"class":511},[175,7625,539],{"class":406},[175,7627,1105],{"class":414},[175,7629,731],{"class":406},[16,7631,1110],{},[165,7633,7634],{"className":1113,"code":1114,"language":1115,"meta":170,"style":170},[172,7635,7636,7640,7658,7670,7674,7678,7690,7694,7698],{"__ignoreMap":170},[175,7637,7638],{"class":177,"line":178},[175,7639,1122],{"class":1028},[175,7641,7642,7644,7646,7648,7650,7652,7654,7656],{"class":177,"line":184},[175,7643,1127],{"class":511},[175,7645,1130],{"class":427},[175,7647,1133],{"class":414},[175,7649,1136],{"class":410},[175,7651,1139],{"class":511},[175,7653,1142],{"class":427},[175,7655,1145],{"class":414},[175,7657,1148],{"class":414},[175,7659,7660,7662,7664,7666,7668],{"class":177,"line":190},[175,7661,1153],{"class":511},[175,7663,1156],{"class":414},[175,7665,1159],{"class":414},[175,7667,1162],{"class":427},[175,7669,1165],{"class":414},[175,7671,7672],{"class":177,"line":196},[175,7673,212],{"emptyLinePlaceholder":211},[175,7675,7676],{"class":177,"line":202},[175,7677,1174],{"class":1028},[175,7679,7680,7682,7684,7686,7688],{"class":177,"line":208},[175,7681,1153],{"class":511},[175,7683,1181],{"class":414},[175,7685,1159],{"class":414},[175,7687,1186],{"class":427},[175,7689,1189],{"class":414},[175,7691,7692],{"class":177,"line":215},[175,7693,212],{"emptyLinePlaceholder":211},[175,7695,7696],{"class":177,"line":221},[175,7697,1198],{"class":1028},[175,7699,7700,7702,7704,7706,7708],{"class":177,"line":227},[175,7701,1203],{"class":511},[175,7703,1206],{"class":427},[175,7705,1209],{"class":427},[175,7707,1212],{"class":427},[175,7709,1215],{"class":414},[16,7711,1218,7712,1221],{},[172,7713,1080],{},[165,7715,7716],{"className":393,"code":1224,"language":395,"meta":170,"style":170},[172,7717,7718,7736,7744,7752,7760,7768],{"__ignoreMap":170},[175,7719,7720,7722,7724,7726,7728,7730,7732,7734],{"class":177,"line":178},[175,7721,403],{"class":402},[175,7723,1093],{"class":406},[175,7725,411],{"class":410},[175,7727,527],{"class":410},[175,7729,1100],{"class":511},[175,7731,539],{"class":406},[175,7733,1105],{"class":414},[175,7735,1245],{"class":406},[175,7737,7738,7740,7742],{"class":177,"line":184},[175,7739,1250],{"class":406},[175,7741,1253],{"class":414},[175,7743,990],{"class":406},[175,7745,7746,7748,7750],{"class":177,"line":190},[175,7747,1260],{"class":406},[175,7749,1263],{"class":427},[175,7751,990],{"class":406},[175,7753,7754,7756,7758],{"class":177,"line":196},[175,7755,1270],{"class":406},[175,7757,1273],{"class":414},[175,7759,990],{"class":406},[175,7761,7762,7764,7766],{"class":177,"line":202},[175,7763,1280],{"class":406},[175,7765,1283],{"class":427},[175,7767,990],{"class":406},[175,7769,7770],{"class":177,"line":208},[175,7771,1290],{"class":406},[16,7773,1293,7774,1296,7776,1299],{},[172,7775,1080],{},[172,7777,1105],{},[165,7779,7780],{"className":393,"code":1302,"language":395,"meta":170,"style":170},[172,7781,7782,7792,7796,7800,7818,7822,7838,7842,7846,7864,7868,7884,7888,7892,7920,7924,7928,7940,7944],{"__ignoreMap":170},[175,7783,7784,7786,7788,7790],{"class":177,"line":178},[175,7785,1309],{"class":410},[175,7787,1312],{"class":406},[175,7789,1315],{"class":410},[175,7791,1318],{"class":414},[175,7793,7794],{"class":177,"line":184},[175,7795,212],{"emptyLinePlaceholder":211},[175,7797,7798],{"class":177,"line":190},[175,7799,1327],{"class":1028},[175,7801,7802,7804,7806,7808,7810,7812,7814,7816],{"class":177,"line":196},[175,7803,403],{"class":402},[175,7805,1093],{"class":406},[175,7807,411],{"class":410},[175,7809,527],{"class":410},[175,7811,1100],{"class":511},[175,7813,539],{"class":406},[175,7815,1105],{"class":414},[175,7817,731],{"class":406},[175,7819,7820],{"class":177,"line":202},[175,7821,212],{"emptyLinePlaceholder":211},[175,7823,7824,7826,7828,7830,7832,7834,7836],{"class":177,"line":208},[175,7825,1354],{"class":406},[175,7827,1357],{"class":511},[175,7829,539],{"class":406},[175,7831,1362],{"class":414},[175,7833,1365],{"class":406},[175,7835,712],{"class":402},[175,7837,444],{"class":406},[175,7839,7840],{"class":177,"line":215},[175,7841,212],{"emptyLinePlaceholder":211},[175,7843,7844],{"class":177,"line":221},[175,7845,1378],{"class":1028},[175,7847,7848,7850,7852,7854,7856,7858,7860,7862],{"class":177,"line":227},[175,7849,1383],{"class":402},[175,7851,1386],{"class":406},[175,7853,411],{"class":410},[175,7855,1391],{"class":406},[175,7857,1394],{"class":511},[175,7859,539],{"class":406},[175,7861,1399],{"class":414},[175,7863,731],{"class":406},[175,7865,7866],{"class":177,"line":233},[175,7867,212],{"emptyLinePlaceholder":211},[175,7869,7870,7872,7874,7876,7878,7880,7882],{"class":177,"line":239},[175,7871,1410],{"class":406},[175,7873,1357],{"class":511},[175,7875,539],{"class":406},[175,7877,1362],{"class":414},[175,7879,1365],{"class":406},[175,7881,712],{"class":402},[175,7883,444],{"class":406},[175,7885,7886],{"class":177,"line":245},[175,7887,212],{"emptyLinePlaceholder":211},[175,7889,7890],{"class":177,"line":250},[175,7891,1431],{"class":1028},[175,7893,7894,7896,7898,7900,7902,7904,7906,7908,7910,7912,7914,7916,7918],{"class":177,"line":256},[175,7895,1436],{"class":406},[175,7897,1357],{"class":511},[175,7899,539],{"class":406},[175,7901,1443],{"class":414},[175,7903,1446],{"class":406},[175,7905,1449],{"class":679},[175,7907,545],{"class":406},[175,7909,712],{"class":402},[175,7911,782],{"class":406},[175,7913,723],{"class":511},[175,7915,539],{"class":406},[175,7917,1462],{"class":414},[175,7919,1465],{"class":406},[175,7921,7922],{"class":177,"line":588},[175,7923,212],{"emptyLinePlaceholder":211},[175,7925,7926],{"class":177,"line":606},[175,7927,1474],{"class":1028},[175,7929,7930,7932,7934,7936,7938],{"class":177,"line":620},[175,7931,1436],{"class":406},[175,7933,740],{"class":511},[175,7935,539],{"class":406},[175,7937,1485],{"class":414},[175,7939,731],{"class":406},[175,7941,7942],{"class":177,"line":633},[175,7943,1492],{"class":406},[175,7945,7946],{"class":177,"line":646},[175,7947,1290],{"class":406},[16,7949,1499],{},[128,7951,1503],{"id":1502},[16,7953,1506],{},[165,7955,7956],{"className":1509,"code":1510,"language":1511,"meta":170,"style":170},[172,7957,7958,7968,7978,7982,7998,8014,8018,8032,8048,8056,8062,8066,8070,8078,8086,8090,8116,8124,8146,8150,8154,8162,8166,8174,8194,8214],{"__ignoreMap":170},[175,7959,7960,7962,7964,7966],{"class":177,"line":178},[175,7961,1518],{"class":406},[175,7963,1521],{"class":410},[175,7965,1524],{"class":511},[175,7967,1527],{"class":406},[175,7969,7970,7972,7974,7976],{"class":177,"line":184},[175,7971,1309],{"class":410},[175,7973,1312],{"class":406},[175,7975,1315],{"class":410},[175,7977,1318],{"class":414},[175,7979,7980],{"class":177,"line":190},[175,7981,212],{"emptyLinePlaceholder":211},[175,7983,7984,7986,7988,7990,7992,7994,7996],{"class":177,"line":196},[175,7985,403],{"class":402},[175,7987,1548],{"class":406},[175,7989,411],{"class":410},[175,7991,1553],{"class":511},[175,7993,539],{"class":406},[175,7995,1558],{"class":414},[175,7997,731],{"class":406},[175,7999,8000,8002,8004,8006,8008,8010,8012],{"class":177,"line":202},[175,8001,403],{"class":402},[175,8003,1567],{"class":406},[175,8005,411],{"class":410},[175,8007,1553],{"class":511},[175,8009,539],{"class":406},[175,8011,1576],{"class":414},[175,8013,731],{"class":406},[175,8015,8016],{"class":177,"line":208},[175,8017,212],{"emptyLinePlaceholder":211},[175,8019,8020,8022,8024,8026,8028,8030],{"class":177,"line":215},[175,8021,1587],{"class":511},[175,8023,539],{"class":406},[175,8025,1592],{"class":410},[175,8027,709],{"class":406},[175,8029,712],{"class":402},[175,8031,444],{"class":406},[175,8033,8034,8036,8038,8040,8042,8044,8046],{"class":177,"line":221},[175,8035,1383],{"class":402},[175,8037,1605],{"class":406},[175,8039,411],{"class":410},[175,8041,1610],{"class":410},[175,8043,1613],{"class":406},[175,8045,1616],{"class":511},[175,8047,978],{"class":406},[175,8049,8050,8052,8054],{"class":177,"line":227},[175,8051,1623],{"class":406},[175,8053,1283],{"class":427},[175,8055,990],{"class":406},[175,8057,8058,8060],{"class":177,"line":233},[175,8059,1632],{"class":406},[175,8061,1635],{"class":427},[175,8063,8064],{"class":177,"line":239},[175,8065,1492],{"class":406},[175,8067,8068],{"class":177,"line":245},[175,8069,212],{"emptyLinePlaceholder":211},[175,8071,8072,8074,8076],{"class":177,"line":250},[175,8073,1648],{"class":406},[175,8075,411],{"class":410},[175,8077,1653],{"class":427},[175,8079,8080,8082,8084],{"class":177,"line":256},[175,8081,1658],{"class":406},[175,8083,411],{"class":410},[175,8085,1663],{"class":406},[175,8087,8088],{"class":177,"line":588},[175,8089,212],{"emptyLinePlaceholder":211},[175,8091,8092,8094,8096,8098,8100,8102,8104,8106,8108,8110,8112,8114],{"class":177,"line":606},[175,8093,1672],{"class":410},[175,8095,1100],{"class":511},[175,8097,539],{"class":406},[175,8099,1105],{"class":414},[175,8101,1681],{"class":406},[175,8103,1357],{"class":511},[175,8105,539],{"class":406},[175,8107,1688],{"class":414},[175,8109,1691],{"class":406},[175,8111,1694],{"class":679},[175,8113,1697],{"class":402},[175,8115,444],{"class":406},[175,8117,8118,8120,8122],{"class":177,"line":620},[175,8119,1704],{"class":406},[175,8121,1707],{"class":511},[175,8123,1710],{"class":406},[175,8125,8126,8128,8130,8132,8134,8136,8138,8140,8142,8144],{"class":177,"line":633},[175,8127,1704],{"class":406},[175,8129,1357],{"class":511},[175,8131,539],{"class":406},[175,8133,1721],{"class":414},[175,8135,1691],{"class":406},[175,8137,1726],{"class":679},[175,8139,1697],{"class":402},[175,8141,1731],{"class":406},[175,8143,411],{"class":410},[175,8145,1736],{"class":406},[175,8147,8148],{"class":177,"line":646},[175,8149,1492],{"class":406},[175,8151,8152],{"class":177,"line":663},[175,8153,1290],{"class":406},[175,8155,8156,8158,8160],{"class":177,"line":668},[175,8157,1749],{"class":406},[175,8159,1521],{"class":410},[175,8161,1527],{"class":406},[175,8163,8164],{"class":177,"line":686},[175,8165,212],{"emptyLinePlaceholder":211},[175,8167,8168,8170,8172],{"class":177,"line":697},[175,8169,1518],{"class":406},[175,8171,1764],{"class":410},[175,8173,1527],{"class":406},[175,8175,8176,8178,8180,8182,8184,8186,8188,8190,8192],{"class":177,"line":717},[175,8177,1771],{"class":406},[175,8179,1774],{"class":410},[175,8181,1777],{"class":511},[175,8183,411],{"class":406},[175,8185,1782],{"class":414},[175,8187,1785],{"class":511},[175,8189,1788],{"class":511},[175,8191,1792],{"class":1791},[175,8193,1527],{"class":406},[175,8195,8196,8198,8200,8202,8204,8206,8208,8210,8212],{"class":177,"line":734},[175,8197,1771],{"class":406},[175,8199,1774],{"class":410},[175,8201,1777],{"class":511},[175,8203,411],{"class":406},[175,8205,1807],{"class":414},[175,8207,1785],{"class":511},[175,8209,1788],{"class":511},[175,8211,1792],{"class":1791},[175,8213,1527],{"class":406},[175,8215,8216,8218,8220],{"class":177,"line":762},[175,8217,1749],{"class":406},[175,8219,1764],{"class":410},[175,8221,1527],{"class":406},[16,8223,1826],{},[42,8225,1830],{"id":1829},[16,8227,1833],{},[50,8229,8230,8236,8242],{},[53,8231,8232,1843],{},[56,8233,8234,1842],{},[20,8235,31],{"href":30},[53,8237,8238,1850],{},[56,8239,8240,1842],{},[20,8241,36],{"href":35},[53,8243,8244,1856],{},[56,8245,1855],{},[16,8247,1859],{},[16,8249,1862,8250],{},[20,8251,1867],{"href":1865,"rel":8252},[1052],[16,8254,1870],{},[1872,8256,1874],{},{"title":170,"searchDepth":184,"depth":184,"links":8258},[8259,8260,8264,8265,8268],{"id":44,"depth":184,"text":45},{"id":74,"depth":184,"text":75,"children":8261},[8262,8263],{"id":130,"depth":190,"text":131},{"id":360,"depth":190,"text":361},{"id":386,"depth":184,"text":387},{"id":1043,"depth":184,"text":1044,"children":8266},[8267],{"id":1502,"depth":190,"text":1503},{"id":1829,"depth":184,"text":1830},{},{"title":6,"description":1889},{"id":1899,"title":1900,"body":8272,"category":1887,"date":3405,"description":3406,"draft":1890,"extension":1891,"image":3407,"meta":9472,"navigation":211,"path":35,"seo":9473,"stem":3410,"tighten":1890,"__hash__":3411},{"type":8,"value":8273,"toc":9463},[8274,8280,8288,8290,8294,8302,8304,8306,8308,8314,8323,8331,8333,8335,8349,8353,8355,8360,8364,8462,8474,8482,8486,8626,8631,8635,8637,8885,8891,8893,8895,8897,8903,8967,8975,9033,9037,9039,9041,9047,9049,9051,9157,9161,9166,9180,9242,9248,9253,9265,9443,9445,9447,9451,9453,9459,9461],[11,8275,8276],{"color":1905,"icon":14},[16,8277,18,8278,24],{},[20,8279,23],{"href":22},[16,8281,1912,8282,1915,8284,1919,8286,1923],{},[20,8283,31],{"href":30},[56,8285,1918],{},[56,8287,1922],{},[42,8289,1927],{"id":1926},[16,8291,1930,8292,1934],{},[56,8293,1933],{},[50,8295,8296,8298,8300],{},[53,8297,1939],{},[53,8299,1942],{},[53,8301,1945],{},[16,8303,1948],{},[16,8305,1951],{},[42,8307,1955],{"id":1954},[16,8309,1958,8310,1962,8312,1966],{},[172,8311,1961],{},[172,8313,1965],{},[16,8315,1969,8316,1973,8318,1977,8320,126],{},[172,8317,1972],{},[172,8319,1976],{},[20,8321,1982],{"href":1980,"rel":8322},[1052],[16,8324,1985,8325,1989,8327,1993,8329,1997],{},[172,8326,1988],{},[172,8328,1992],{},[172,8330,1996],{},[16,8332,2000],{},[42,8334,2004],{"id":2003},[16,8336,2007,8337,1691,8340,2018,8343,2024,8346,126],{},[20,8338,2012],{"href":2010,"rel":8339},[1052],[20,8341,2017],{"href":2015,"rel":8342},[1052],[20,8344,2023],{"href":2021,"rel":8345},[1052],[20,8347,2029],{"href":2027,"rel":8348},[1052],[16,8350,2032,8351],{},[56,8352,2035],{},[16,8354,2038],{},[2040,8356,8358],{"className":8357,"icon":2044},[2043],[16,8359,2047],{},[16,8361,2050,8362,2054],{},[172,8363,2053],{},[165,8365,8366],{"className":393,"code":2057,"language":395,"meta":170,"style":170},[172,8367,8368,8378,8382,8392,8396,8408,8412,8416,8430,8434,8450,8454,8458],{"__ignoreMap":170},[175,8369,8370,8372,8374,8376],{"class":177,"line":178},[175,8371,2064],{"class":410},[175,8373,2067],{"class":410},[175,8375,2070],{"class":511},[175,8377,978],{"class":406},[175,8379,8380],{"class":177,"line":184},[175,8381,2077],{"class":1028},[175,8383,8384,8386,8388,8390],{"class":177,"line":190},[175,8385,2082],{"class":511},[175,8387,539],{"class":406},[175,8389,2087],{"class":679},[175,8391,683],{"class":406},[175,8393,8394],{"class":177,"line":196},[175,8395,2094],{"class":1028},[175,8397,8398,8400,8402,8404,8406],{"class":177,"line":202},[175,8399,2099],{"class":406},[175,8401,2102],{"class":511},[175,8403,539],{"class":406},[175,8405,2107],{"class":414},[175,8407,731],{"class":406},[175,8409,8410],{"class":177,"line":208},[175,8411,2114],{"class":406},[175,8413,8414],{"class":177,"line":215},[175,8415,2119],{"class":1028},[175,8417,8418,8420,8422,8424,8426,8428],{"class":177,"line":221},[175,8419,2124],{"class":511},[175,8421,539],{"class":406},[175,8423,2087],{"class":679},[175,8425,1691],{"class":406},[175,8427,2133],{"class":679},[175,8429,683],{"class":406},[175,8431,8432],{"class":177,"line":227},[175,8433,2140],{"class":1028},[175,8435,8436,8438,8440,8442,8444,8446,8448],{"class":177,"line":233},[175,8437,2099],{"class":406},[175,8439,2147],{"class":511},[175,8441,539],{"class":406},[175,8443,2107],{"class":414},[175,8445,2154],{"class":406},[175,8447,2157],{"class":511},[175,8449,2160],{"class":406},[175,8451,8452],{"class":177,"line":239},[175,8453,765],{"class":406},[175,8455,8456],{"class":177,"line":245},[175,8457,2169],{"class":1028},[175,8459,8460],{"class":177,"line":250},[175,8461,1290],{"class":406},[50,8463,8464,8470],{},[53,8465,2178,8466,2182,8468,2185],{},[172,8467,2181],{},[172,8469,2107],{},[53,8471,2178,8472,2190],{},[172,8473,2133],{},[16,8475,2193,8476,1691,8478,2200,8480,2203],{},[172,8477,2196],{},[172,8479,2199],{},[172,8481,816],{},[16,8483,2206,8484,2209],{},[172,8485,2087],{},[165,8487,8488],{"className":393,"code":2212,"language":395,"meta":170,"style":170},[172,8489,8490,8494,8506,8510,8514,8530,8550,8554,8558,8562,8574,8590,8594,8598,8610,8614,8618],{"__ignoreMap":170},[175,8491,8492],{"class":177,"line":178},[175,8493,2219],{"class":1028},[175,8495,8496,8498,8500,8502,8504],{"class":177,"line":184},[175,8497,1354],{"class":406},[175,8499,2102],{"class":511},[175,8501,539],{"class":406},[175,8503,2107],{"class":414},[175,8505,731],{"class":406},[175,8507,8508],{"class":177,"line":190},[175,8509,212],{"emptyLinePlaceholder":211},[175,8511,8512],{"class":177,"line":196},[175,8513,2240],{"class":1028},[175,8515,8516,8518,8520,8522,8524,8526,8528],{"class":177,"line":202},[175,8517,1354],{"class":406},[175,8519,2147],{"class":511},[175,8521,539],{"class":406},[175,8523,2107],{"class":414},[175,8525,1691],{"class":406},[175,8527,2255],{"class":414},[175,8529,731],{"class":406},[175,8531,8532,8534,8536,8538,8540,8542,8544,8546,8548],{"class":177,"line":208},[175,8533,1354],{"class":406},[175,8535,2147],{"class":511},[175,8537,539],{"class":406},[175,8539,2107],{"class":414},[175,8541,2270],{"class":406},[175,8543,2273],{"class":414},[175,8545,2276],{"class":406},[175,8547,2279],{"class":414},[175,8549,2282],{"class":406},[175,8551,8552],{"class":177,"line":215},[175,8553,212],{"emptyLinePlaceholder":211},[175,8555,8556],{"class":177,"line":221},[175,8557,2291],{"class":1028},[175,8559,8560],{"class":177,"line":227},[175,8561,2296],{"class":1028},[175,8563,8564,8566,8568,8570,8572],{"class":177,"line":233},[175,8565,1354],{"class":406},[175,8567,740],{"class":511},[175,8569,539],{"class":406},[175,8571,2307],{"class":414},[175,8573,731],{"class":406},[175,8575,8576,8578,8580,8582,8584,8586,8588],{"class":177,"line":239},[175,8577,1354],{"class":406},[175,8579,740],{"class":511},[175,8581,2318],{"class":406},[175,8583,2321],{"class":414},[175,8585,2276],{"class":406},[175,8587,2326],{"class":414},[175,8589,2282],{"class":406},[175,8591,8592],{"class":177,"line":245},[175,8593,212],{"emptyLinePlaceholder":211},[175,8595,8596],{"class":177,"line":250},[175,8597,2337],{"class":1028},[175,8599,8600,8602,8604,8606,8608],{"class":177,"line":256},[175,8601,1354],{"class":406},[175,8603,2344],{"class":511},[175,8605,539],{"class":406},[175,8607,2107],{"class":414},[175,8609,731],{"class":406},[175,8611,8612],{"class":177,"line":588},[175,8613,212],{"emptyLinePlaceholder":211},[175,8615,8616],{"class":177,"line":606},[175,8617,2359],{"class":1028},[175,8619,8620,8622,8624],{"class":177,"line":620},[175,8621,1354],{"class":406},[175,8623,2199],{"class":511},[175,8625,515],{"class":406},[2040,8627,8629],{"className":8628,"icon":2371},[2043],[16,8630,2374],{},[16,8632,2377,8633,126],{},[172,8634,2380],{},[16,8636,2383],{},[165,8638,8639],{"className":1509,"code":2386,"language":1511,"meta":170,"style":170},[172,8640,8641,8651,8661,8665,8677,8693,8715,8719,8723,8739,8747,8753,8773,8781,8785,8793,8797,8805,8843,8877],{"__ignoreMap":170},[175,8642,8643,8645,8647,8649],{"class":177,"line":178},[175,8644,1518],{"class":406},[175,8646,1521],{"class":410},[175,8648,1524],{"class":511},[175,8650,1527],{"class":406},[175,8652,8653,8655,8657,8659],{"class":177,"line":184},[175,8654,1309],{"class":410},[175,8656,2405],{"class":406},[175,8658,1315],{"class":410},[175,8660,2410],{"class":414},[175,8662,8663],{"class":177,"line":190},[175,8664,212],{"emptyLinePlaceholder":211},[175,8666,8667,8669,8671,8673,8675],{"class":177,"line":196},[175,8668,403],{"class":402},[175,8670,2421],{"class":406},[175,8672,411],{"class":410},[175,8674,1777],{"class":511},[175,8676,2428],{"class":406},[175,8678,8679,8681,8683,8685,8687,8689,8691],{"class":177,"line":202},[175,8680,403],{"class":402},[175,8682,2435],{"class":406},[175,8684,411],{"class":410},[175,8686,2440],{"class":511},[175,8688,539],{"class":406},[175,8690,2445],{"class":414},[175,8692,1245],{"class":406},[175,8694,8695,8697,8699,8701,8703,8705,8707,8709,8711,8713],{"class":177,"line":208},[175,8696,2452],{"class":511},[175,8698,2455],{"class":406},[175,8700,2458],{"class":679},[175,8702,1691],{"class":406},[175,8704,807],{"class":679},[175,8706,545],{"class":406},[175,8708,712],{"class":402},[175,8710,2469],{"class":406},[175,8712,2472],{"class":511},[175,8714,2475],{"class":406},[175,8716,8717],{"class":177,"line":215},[175,8718,1290],{"class":406},[175,8720,8721],{"class":177,"line":221},[175,8722,212],{"emptyLinePlaceholder":211},[175,8724,8725,8727,8729,8731,8733,8735,8737],{"class":177,"line":227},[175,8726,403],{"class":402},[175,8728,2490],{"class":406},[175,8730,411],{"class":410},[175,8732,1777],{"class":511},[175,8734,539],{"class":406},[175,8736,2499],{"class":414},[175,8738,731],{"class":406},[175,8740,8741,8743,8745],{"class":177,"line":233},[175,8742,671],{"class":402},[175,8744,2508],{"class":511},[175,8746,2511],{"class":406},[175,8748,8749,8751],{"class":177,"line":239},[175,8750,2516],{"class":511},[175,8752,2519],{"class":406},[175,8754,8755,8757,8759,8761,8763,8765,8767,8769,8771],{"class":177,"line":245},[175,8756,2524],{"class":406},[175,8758,2472],{"class":511},[175,8760,539],{"class":406},[175,8762,2531],{"class":414},[175,8764,748],{"class":410},[175,8766,2536],{"class":406},[175,8768,754],{"class":410},[175,8770,757],{"class":414},[175,8772,731],{"class":406},[175,8774,8775,8777,8779],{"class":177,"line":250},[175,8776,2547],{"class":406},[175,8778,411],{"class":410},[175,8780,2552],{"class":414},[175,8782,8783],{"class":177,"line":256},[175,8784,492],{"class":406},[175,8786,8787,8789,8791],{"class":177,"line":588},[175,8788,1749],{"class":406},[175,8790,1521],{"class":410},[175,8792,1527],{"class":406},[175,8794,8795],{"class":177,"line":606},[175,8796,212],{"emptyLinePlaceholder":211},[175,8798,8799,8801,8803],{"class":177,"line":620},[175,8800,1518],{"class":406},[175,8802,1764],{"class":410},[175,8804,1527],{"class":406},[175,8806,8807,8809,8811,8813,8815,8817,8819,8821,8823,8825,8827,8829,8831,8833,8835,8837,8839,8841],{"class":177,"line":633},[175,8808,1771],{"class":406},[175,8810,2583],{"class":410},[175,8812,2586],{"class":410},[175,8814,411],{"class":406},[175,8816,2591],{"class":406},[175,8818,2594],{"class":406},[175,8820,2597],{"class":410},[175,8822,2600],{"class":406},[175,8824,2591],{"class":406},[175,8826,2605],{"class":406},[175,8828,2608],{"class":511},[175,8830,411],{"class":406},[175,8832,2591],{"class":406},[175,8834,2615],{"class":406},[175,8836,2591],{"class":406},[175,8838,2620],{"class":406},[175,8840,2583],{"class":410},[175,8842,1527],{"class":406},[175,8844,8845,8847,8849,8851,8853,8855,8857,8859,8861,8863,8865,8867,8869,8871,8873,8875],{"class":177,"line":646},[175,8846,1771],{"class":406},[175,8848,2631],{"class":410},[175,8850,2634],{"class":511},[175,8852,411],{"class":406},[175,8854,2591],{"class":406},[175,8856,2631],{"class":406},[175,8858,2591],{"class":406},[175,8860,2645],{"class":406},[175,8862,2648],{"class":511},[175,8864,126],{"class":406},[175,8866,2653],{"class":511},[175,8868,411],{"class":406},[175,8870,2591],{"class":406},[175,8872,2660],{"class":406},[175,8874,2591],{"class":406},[175,8876,2665],{"class":406},[175,8878,8879,8881,8883],{"class":177,"line":663},[175,8880,1749],{"class":406},[175,8882,1764],{"class":410},[175,8884,1527],{"class":406},[16,8886,2676,8887,2680,8889,2683],{},[172,8888,2679],{},[172,8890,740],{},[16,8892,2686],{},[128,8894,2690],{"id":2689},[16,8896,2693],{},[16,8898,8899,2699,8901,2702],{},[56,8900,2698],{},[172,8902,2380],{},[165,8904,8905],{"className":393,"code":2705,"language":395,"meta":170,"style":170},[172,8906,8907,8911,8921,8925,8933,8941,8959,8963],{"__ignoreMap":170},[175,8908,8909],{"class":177,"line":178},[175,8910,2712],{"class":1028},[175,8912,8913,8915,8917,8919],{"class":177,"line":184},[175,8914,2380],{"class":511},[175,8916,539],{"class":406},[175,8918,2445],{"class":414},[175,8920,1245],{"class":406},[175,8922,8923],{"class":177,"line":190},[175,8924,2727],{"class":406},[175,8926,8927,8929,8931],{"class":177,"line":196},[175,8928,2732],{"class":406},[175,8930,2735],{"class":427},[175,8932,990],{"class":406},[175,8934,8935,8937,8939],{"class":177,"line":202},[175,8936,2742],{"class":406},[175,8938,2745],{"class":427},[175,8940,990],{"class":406},[175,8942,8943,8945,8947,8949,8951,8953,8955,8957],{"class":177,"line":208},[175,8944,2752],{"class":511},[175,8946,2755],{"class":406},[175,8948,712],{"class":402},[175,8950,782],{"class":406},[175,8952,723],{"class":511},[175,8954,539],{"class":406},[175,8956,2766],{"class":414},[175,8958,2769],{"class":406},[175,8960,8961],{"class":177,"line":215},[175,8962,765],{"class":406},[175,8964,8965],{"class":177,"line":221},[175,8966,1290],{"class":406},[16,8968,2780,8969,2784,8971,2788,8973,2792],{},[56,8970,2783],{},[1037,8972,2787],{},[1037,8974,2791],{},[165,8976,8977],{"className":393,"code":2795,"language":395,"meta":170,"style":170},[172,8978,8979,8983,8987,8997,9001,9009,9017,9025,9029],{"__ignoreMap":170},[175,8980,8981],{"class":177,"line":178},[175,8982,2802],{"class":1028},[175,8984,8985],{"class":177,"line":184},[175,8986,2807],{"class":1028},[175,8988,8989,8991,8993,8995],{"class":177,"line":190},[175,8990,2380],{"class":511},[175,8992,539],{"class":406},[175,8994,2445],{"class":414},[175,8996,1245],{"class":406},[175,8998,8999],{"class":177,"line":196},[175,9000,2822],{"class":406},[175,9002,9003,9005,9007],{"class":177,"line":202},[175,9004,2827],{"class":406},[175,9006,2830],{"class":414},[175,9008,990],{"class":406},[175,9010,9011,9013,9015],{"class":177,"line":208},[175,9012,2837],{"class":406},[175,9014,2840],{"class":427},[175,9016,990],{"class":406},[175,9018,9019,9021,9023],{"class":177,"line":215},[175,9020,2847],{"class":406},[175,9022,2850],{"class":427},[175,9024,990],{"class":406},[175,9026,9027],{"class":177,"line":221},[175,9028,765],{"class":406},[175,9030,9031],{"class":177,"line":227},[175,9032,1290],{"class":406},[16,9034,2863,9035,2867],{},[172,9036,2866],{},[42,9038,2871],{"id":2870},[16,9040,2874],{},[16,9042,2877,9043,2881,9045,2885],{},[1037,9044,2880],{},[1037,9046,2884],{},[16,9048,2888],{},[16,9050,2891],{},[165,9052,9053],{"className":1509,"code":2894,"language":1511,"meta":170,"style":170},[172,9054,9055,9063,9081,9115,9119,9127,9135,9149],{"__ignoreMap":170},[175,9056,9057,9059,9061],{"class":177,"line":178},[175,9058,1518],{"class":406},[175,9060,1764],{"class":410},[175,9062,1527],{"class":406},[175,9064,9065,9067,9069,9071,9073,9075,9077,9079],{"class":177,"line":184},[175,9066,1771],{"class":406},[175,9068,50],{"class":410},[175,9070,2913],{"class":410},[175,9072,411],{"class":406},[175,9074,2591],{"class":406},[175,9076,2920],{"class":406},[175,9078,2591],{"class":406},[175,9080,1527],{"class":406},[175,9082,9083,9085,9087,9089,9091,9093,9095,9097,9099,9101,9103,9105,9107,9109,9111,9113],{"class":177,"line":190},[175,9084,2929],{"class":406},[175,9086,53],{"class":410},[175,9088,2586],{"class":410},[175,9090,411],{"class":406},[175,9092,2591],{"class":406},[175,9094,2940],{"class":406},[175,9096,2597],{"class":410},[175,9098,2945],{"class":406},[175,9100,2591],{"class":406},[175,9102,2605],{"class":406},[175,9104,2608],{"class":511},[175,9106,411],{"class":406},[175,9108,2591],{"class":406},[175,9110,2958],{"class":406},[175,9112,2591],{"class":406},[175,9114,1527],{"class":406},[175,9116,9117],{"class":177,"line":196},[175,9118,2967],{"class":406},[175,9120,9121,9123,9125],{"class":177,"line":202},[175,9122,2972],{"class":406},[175,9124,53],{"class":410},[175,9126,1527],{"class":406},[175,9128,9129,9131,9133],{"class":177,"line":208},[175,9130,2981],{"class":406},[175,9132,50],{"class":410},[175,9134,1527],{"class":406},[175,9136,9137,9139,9141,9143,9145,9147],{"class":177,"line":215},[175,9138,1771],{"class":406},[175,9140,16],{"class":410},[175,9142,2994],{"class":410},[175,9144,2997],{"class":406},[175,9146,16],{"class":410},[175,9148,1527],{"class":406},[175,9150,9151,9153,9155],{"class":177,"line":221},[175,9152,1749],{"class":406},[175,9154,1764],{"class":410},[175,9156,1527],{"class":406},[16,9158,3012,9159,3015],{},[172,9160,2920],{},[2040,9162,9164],{"className":9163,"icon":3020},[3019],[16,9165,3023],{},[16,9167,9168,3031,9171,3035,9173,3039,9175,3047],{},[20,9169,3030],{"href":3028,"rel":9170},[1052],[56,9172,3034],{},[56,9174,3038],{},[20,9176,9178],{"href":3042,"rel":9177},[1052],[56,9179,3046],{},[165,9181,9182],{"className":393,"code":3050,"language":395,"meta":170,"style":170},[172,9183,9184,9188,9198,9208,9218,9222],{"__ignoreMap":170},[175,9185,9186],{"class":177,"line":178},[175,9187,3057],{"class":1028},[175,9189,9190,9192,9194,9196],{"class":177,"line":184},[175,9191,1309],{"class":410},[175,9193,3064],{"class":406},[175,9195,1315],{"class":410},[175,9197,3069],{"class":414},[175,9199,9200,9202,9204,9206],{"class":177,"line":190},[175,9201,1309],{"class":410},[175,9203,3076],{"class":406},[175,9205,1315],{"class":410},[175,9207,3081],{"class":414},[175,9209,9210,9212,9214,9216],{"class":177,"line":196},[175,9211,1309],{"class":410},[175,9213,3088],{"class":406},[175,9215,1315],{"class":410},[175,9217,3093],{"class":414},[175,9219,9220],{"class":177,"line":202},[175,9221,212],{"emptyLinePlaceholder":211},[175,9223,9224,9226,9228,9230,9232,9234,9236,9238,9240],{"class":177,"line":208},[175,9225,403],{"class":402},[175,9227,3104],{"class":406},[175,9229,411],{"class":410},[175,9231,3109],{"class":511},[175,9233,539],{"class":406},[175,9235,3114],{"class":511},[175,9237,3117],{"class":406},[175,9239,3120],{"class":414},[175,9241,894],{"class":406},[16,9243,3125,9244,3128,9246,3131],{},[172,9245,2920],{},[172,9247,2920],{},[2040,9249,9251],{"className":9250,"icon":3136},[3135],[16,9252,3139],{},[16,9254,3142,9255,3150,9260,126],{},[20,9256,9258],{"href":3145,"rel":9257},[1052],[56,9259,3149],{},[20,9261,9263],{"href":3153,"rel":9262},[1052],[56,9264,3157],{},[165,9266,9267],{"className":393,"code":3160,"language":395,"meta":170,"style":170},[172,9268,9269,9273,9285,9289,9313,9337,9341,9345,9351,9355,9363,9371,9383,9395,9409,9413,9421,9425,9429],{"__ignoreMap":170},[175,9270,9271],{"class":177,"line":178},[175,9272,3167],{"class":1028},[175,9274,9275,9277,9279,9281,9283],{"class":177,"line":184},[175,9276,403],{"class":402},[175,9278,3174],{"class":406},[175,9280,411],{"class":410},[175,9282,3179],{"class":511},[175,9284,515],{"class":406},[175,9286,9287],{"class":177,"line":190},[175,9288,212],{"emptyLinePlaceholder":211},[175,9290,9291,9293,9295,9297,9299,9301,9303,9305,9307,9309,9311],{"class":177,"line":196},[175,9292,403],{"class":402},[175,9294,3192],{"class":406},[175,9296,411],{"class":410},[175,9298,1610],{"class":410},[175,9300,3199],{"class":511},[175,9302,539],{"class":406},[175,9304,3120],{"class":414},[175,9306,1691],{"class":406},[175,9308,1592],{"class":410},[175,9310,709],{"class":406},[175,9312,3212],{"class":402},[175,9314,9315,9317,9319,9321,9323,9325,9327,9329,9331,9333,9335],{"class":177,"line":202},[175,9316,3217],{"class":406},[175,9318,3220],{"class":410},[175,9320,3223],{"class":406},[175,9322,1315],{"class":511},[175,9324,539],{"class":406},[175,9326,3120],{"class":414},[175,9328,1681],{"class":406},[175,9330,3234],{"class":511},[175,9332,539],{"class":406},[175,9334,3239],{"class":414},[175,9336,3242],{"class":406},[175,9338,9339],{"class":177,"line":208},[175,9340,731],{"class":406},[175,9342,9343],{"class":177,"line":215},[175,9344,212],{"emptyLinePlaceholder":211},[175,9346,9347,9349],{"class":177,"line":221},[175,9348,609],{"class":402},[175,9350,3257],{"class":406},[175,9352,9353],{"class":177,"line":227},[175,9354,212],{"emptyLinePlaceholder":211},[175,9356,9357,9359,9361],{"class":177,"line":233},[175,9358,1587],{"class":511},[175,9360,3268],{"class":406},[175,9362,3212],{"class":402},[175,9364,9365,9367,9369],{"class":177,"line":239},[175,9366,689],{"class":406},[175,9368,411],{"class":410},[175,9370,3279],{"class":406},[175,9372,9373,9375,9377,9379,9381],{"class":177,"line":245},[175,9374,3284],{"class":406},[175,9376,3287],{"class":511},[175,9378,539],{"class":406},[175,9380,3292],{"class":414},[175,9382,731],{"class":406},[175,9384,9385,9387,9389,9391,9393],{"class":177,"line":250},[175,9386,3284],{"class":406},[175,9388,1357],{"class":511},[175,9390,539],{"class":406},[175,9392,3305],{"class":414},[175,9394,1245],{"class":406},[175,9396,9397,9399,9401,9403,9405,9407],{"class":177,"line":256},[175,9398,3312],{"class":406},[175,9400,3239],{"class":414},[175,9402,3317],{"class":406},[175,9404,3320],{"class":414},[175,9406,3323],{"class":406},[175,9408,3326],{"class":414},[175,9410,9411],{"class":177,"line":588},[175,9412,3331],{"class":406},[175,9414,9415,9417,9419],{"class":177,"line":606},[175,9416,3284],{"class":406},[175,9418,2102],{"class":511},[175,9420,515],{"class":406},[175,9422,9423],{"class":177,"line":620},[175,9424,731],{"class":406},[175,9426,9427],{"class":177,"line":633},[175,9428,212],{"emptyLinePlaceholder":211},[175,9430,9431,9433,9435,9437,9439,9441],{"class":177,"line":646},[175,9432,3352],{"class":511},[175,9434,3268],{"class":406},[175,9436,712],{"class":402},[175,9438,3223],{"class":406},[175,9440,3361],{"class":511},[175,9442,3364],{"class":406},[16,9444,3367],{},[42,9446,3371],{"id":3370},[16,9448,3374,9449],{},[56,9450,3377],{},[16,9452,3380],{},[16,9454,3383,9455,3386,9457,126],{},[56,9456,75],{},[20,9458,3389],{"href":1894},[16,9460,3392],{},[1872,9462,3395],{},{"title":170,"searchDepth":184,"depth":184,"links":9464},[9465,9466,9467,9470,9471],{"id":1926,"depth":184,"text":1927},{"id":1954,"depth":184,"text":1955},{"id":2003,"depth":184,"text":2004,"children":9468},[9469],{"id":2689,"depth":190,"text":2690},{"id":2870,"depth":184,"text":2871},{"id":3370,"depth":184,"text":3371},{},{"title":1900,"description":3406},{"id":9475,"title":9476,"body":9477,"category":1887,"date":9511,"description":9481,"draft":1890,"extension":1891,"image":9512,"meta":9513,"navigation":211,"path":9514,"seo":9515,"stem":9516,"tighten":211,"__hash__":9517},"blog/blog/state-management-in-vue-3-why-you-should-try-out-pinia.md","State Management In Vue 3: Why You Should Try Out Pinia",{"type":8,"value":9478,"toc":9509},[9479,9482,9489,9492,9500],[16,9480,9481],{},"How do you share state between multiple components in a Vue app? While Vue 3 reactivity is powerful and composables can get you far, your app might benefit from Pinia, the official state management solution for Vue applications.",[16,9483,9484,9485,9488],{},"With Pinia, you can create ",[56,9486,9487],{},"stores",", and any changes to the store data will automatically be reflected in all components that consume it.",[16,9490,9491],{},"Pinia also ensures that stores work in SSR mode without leaking data to other users, and it includes a handy plugin for Vue DevTools. Read more about why using Pinia is a great idea in this article I wrote for Tighten's blog:",[5252,9493,9497],{"image":9494,"link":9495,"title":9496},"https://tighten.com/assets/images/insights/state-management-in-vue-3-why-you-should-try-out-pinia-preview.jpg","https://tighten.com/insights/state-management-in-vue-3-why-you-should-try-out-pinia/","State management in Vue 3: Why you should try out Pinia",[16,9498,9499],{},"They say there are two difficult things in programming: naming things and cache invalidation. I would add one more to the list: state management in modern web applications! Today, we'll delve into state management strategies in Vue and introduce Pinia, the intuitive store.",[16,9501,9502,9503,9508],{},"Thanks to ",[20,9504,9507],{"href":9505,"rel":9506},"https://x.com/posva",[1052],"Eduardo",", the creator of Pinia, for reviewing the text while I was writing it!",{"title":170,"searchDepth":184,"depth":184,"links":9510},[],"2024-02-16","/img/blog/pinia.png",{},"/blog/state-management-in-vue-3-why-you-should-try-out-pinia",{"title":9476,"description":9481},"blog/state-management-in-vue-3-why-you-should-try-out-pinia","HvUJkAwnynO7Ij7j6YUfuhyQrZOAqToJMYOo44q5czw",{"left":9519,"top":9519,"width":9520,"height":9520,"rotate":9519,"vFlip":1890,"hFlip":1890,"body":9521},0,512,"\u003Cpath fill=\"currentColor\" d=\"M64 32C28.7 32 0 60.7 0 96v64c0 35.3 28.7 64 64 64h384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64zm280 72a24 24 0 1 1 0 48a24 24 0 1 1 0-48m48 24a24 24 0 1 1 48 0a24 24 0 1 1-48 0M64 288c-35.3 0-64 28.7-64 64v64c0 35.3 28.7 64 64 64h384c35.3 0 64-28.7 64-64v-64c0-35.3-28.7-64-64-64zm280 72a24 24 0 1 1 0 48a24 24 0 1 1 0-48m56 24a24 24 0 1 1 48 0a24 24 0 1 1-48 0\"/>",{"left":9519,"top":9519,"width":9523,"height":9520,"rotate":9519,"vFlip":1890,"hFlip":1890,"body":9524},640,"\u003Cpath fill=\"currentColor\" d=\"M128 32c-35.3 0-64 28.7-64 64v256h64V96h384v256h64V96c0-35.3-28.7-64-64-64zM19.2 384C8.6 384 0 392.6 0 403.2C0 445.6 34.4 480 76.8 480h486.4c42.4 0 76.8-34.4 76.8-76.8c0-10.6-8.6-19.2-19.2-19.2z\"/>",1779994822427]