[{"data":1,"prerenderedAt":2190},["ShallowReactive",2],{"all-writing":3},[4,980],{"id":5,"title":6,"author":7,"body":8,"date":965,"description":966,"extension":967,"meta":968,"navigation":126,"path":970,"seo":971,"stem":972,"tags":973,"__hash__":979},"writing\u002Fwriting\u002Freactflow-workflow-visualization.md","ReactFlow: Building Intuitive Workflow Visualizations for Non-Technical Users",null,{"type":9,"value":10,"toc":952},"minimark",[11,15,20,24,32,36,39,50,54,59,62,347,350,363,367,370,593,596,600,603,613,660,668,781,789,853,857,860,880,884,887,901,905,938,942,945,948],[12,13,6],"h1",{"id":14},"reactflow-building-intuitive-workflow-visualizations-for-non-technical-users",[16,17,19],"h2",{"id":18},"why-im-writing-this","Why I'm Writing This",[21,22,23],"p",{},"Recently, I faced an interesting challenge while building our marketing website at Capptions. We needed to showcase our Workflow Builder functionality to QHSE (Quality, Health, Safety, and Environment) managers - a user group that spans the spectrum from tech-savvy to non-technical backgrounds. The goal was to make complex workflow concepts accessible and visually appealing - so users understand prior having to sign up.",[21,25,26,27,31],{},"After exploring other options ",[28,29,30],"em",{},"(plain Tailwind, motion.dev)",", I landed on ReactFlow, and the journey taught me valuable lessons about technical documentation, user experience, and the art of simplifying complex concepts.",[16,33,35],{"id":34},"key-insight-visualization-explanation","Key Insight: Visualization > Explanation",[21,37,38],{},"The main \"aha\" moment came when I realized that trying to explain workflow builders through text and static images wasn't cutting it. Our users needed something interactive and familiar - something that reflected the actual tool they'd be using.",[21,40,41],{},[42,43],"img",{"src":44,"alt":45,"className":46},"\u002Fimages\u002Fcontent\u002Freact-flow-workflow-visualisation.webp","ReactFlow Workflow Visualization",[47,48,49],"w-3\u002F4","mx-auto","rounded-lg",[16,51,53],{"id":52},"deep-dive","Deep Dive",[55,56,58],"h3",{"id":57},"setting-up-custom-nodes","Setting Up Custom Nodes",[21,60,61],{},"The first challenge was creating custom nodes that felt native to our application. Here's how I approached it:",[63,64,69],"pre",{"className":65,"code":66,"language":67,"meta":68,"style":68},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark","type CustomNodeData = {\n  label: React.ReactNode;\n};\n\nconst CustomNode = ({ data }: { data: CustomNodeData }) => (\n  \u003Cdiv className=\"relative p-2 bg-white rounded-md border-2 border-gray-300\">\n    \u003CHandle\n      type=\"target\"\n      position={Position.Top}\n      className=\"!w-3 !h-3 !bg-primary !border-1 !border-white ring-1 ring-offset-2 ring-gray-300\"\n    \u002F>\n    {data.label}\n    \u003CHandle\n      type=\"source\"\n      position={Position.Bottom}\n      className=\"!w-3 !h-3 !bg-primary !border-1 !border-white ring-1 ring-offset-2 ring-gray-300\"\n    \u002F>\n  \u003C\u002Fdiv>\n);\n","typescript","",[70,71,72,93,115,121,128,171,196,205,221,238,253,259,275,282,296,310,323,328,339],"code",{"__ignoreMap":68},[73,74,77,81,85,89],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"sbsja","type",[73,82,84],{"class":83},"sbgvK"," CustomNodeData",[73,86,88],{"class":87},"smGrS"," =",[73,90,92],{"class":91},"sP7_E"," {\n",[73,94,96,100,103,106,109,112],{"class":75,"line":95},2,[73,97,99],{"class":98},"sucvu","  label",[73,101,102],{"class":87},":",[73,104,105],{"class":83}," React",[73,107,108],{"class":91},".",[73,110,111],{"class":83},"ReactNode",[73,113,114],{"class":91},";\n",[73,116,118],{"class":75,"line":117},3,[73,119,120],{"class":91},"};\n",[73,122,124],{"class":75,"line":123},4,[73,125,127],{"emptyLinePlaceholder":126},true,"\n",[73,129,131,134,138,140,143,147,150,152,155,157,159,161,164,167],{"class":75,"line":130},5,[73,132,133],{"class":79},"const",[73,135,137],{"class":136},"sfCm-"," CustomNode",[73,139,88],{"class":87},[73,141,142],{"class":91}," ({",[73,144,146],{"class":145},"s99_P"," data",[73,148,149],{"class":91}," }",[73,151,102],{"class":87},[73,153,154],{"class":91}," {",[73,156,146],{"class":98},[73,158,102],{"class":87},[73,160,84],{"class":83},[73,162,163],{"class":91}," })",[73,165,166],{"class":79}," =>",[73,168,170],{"class":169},"su5hD"," (\n",[73,172,174,177,180,183,187,191,193],{"class":75,"line":173},6,[73,175,176],{"class":87},"  \u003C",[73,178,179],{"class":169},"div className",[73,181,182],{"class":87},"=",[73,184,186],{"class":185},"sjJ54","\"",[73,188,190],{"class":189},"s_sjI","relative p-2 bg-white rounded-md border-2 border-gray-300",[73,192,186],{"class":185},[73,194,195],{"class":87},">\n",[73,197,199,202],{"class":75,"line":198},7,[73,200,201],{"class":87},"    \u003C",[73,203,204],{"class":145},"Handle\n",[73,206,208,211,213,215,218],{"class":75,"line":207},8,[73,209,210],{"class":169},"      type",[73,212,182],{"class":87},[73,214,186],{"class":185},[73,216,217],{"class":189},"target",[73,219,220],{"class":185},"\"\n",[73,222,224,227,229,232,235],{"class":75,"line":223},9,[73,225,226],{"class":169},"      position",[73,228,182],{"class":87},[73,230,231],{"class":91},"{",[73,233,234],{"class":169},"Position.Top",[73,236,237],{"class":91},"}\n",[73,239,241,244,246,248,251],{"class":75,"line":240},10,[73,242,243],{"class":169},"      className",[73,245,182],{"class":87},[73,247,186],{"class":185},[73,249,250],{"class":189},"!w-3 !h-3 !bg-primary !border-1 !border-white ring-1 ring-offset-2 ring-gray-300",[73,252,220],{"class":185},[73,254,256],{"class":75,"line":255},11,[73,257,258],{"class":87},"    \u002F>\n",[73,260,262,265,268,270,273],{"class":75,"line":261},12,[73,263,264],{"class":91},"    {",[73,266,267],{"class":145},"data",[73,269,108],{"class":169},[73,271,272],{"class":145},"label",[73,274,237],{"class":91},[73,276,278,280],{"class":75,"line":277},13,[73,279,201],{"class":87},[73,281,204],{"class":145},[73,283,285,287,289,291,294],{"class":75,"line":284},14,[73,286,210],{"class":169},[73,288,182],{"class":87},[73,290,186],{"class":185},[73,292,293],{"class":189},"source",[73,295,220],{"class":185},[73,297,299,301,303,305,308],{"class":75,"line":298},15,[73,300,226],{"class":169},[73,302,182],{"class":87},[73,304,231],{"class":91},[73,306,307],{"class":169},"Position.Bottom",[73,309,237],{"class":91},[73,311,313,315,317,319,321],{"class":75,"line":312},16,[73,314,243],{"class":169},[73,316,182],{"class":87},[73,318,186],{"class":185},[73,320,250],{"class":189},[73,322,220],{"class":185},[73,324,326],{"class":75,"line":325},17,[73,327,258],{"class":87},[73,329,331,334,337],{"class":75,"line":330},18,[73,332,333],{"class":87},"  \u003C\u002F",[73,335,336],{"class":169},"div",[73,338,195],{"class":87},[73,340,342,345],{"class":75,"line":341},19,[73,343,344],{"class":169},")",[73,346,114],{"class":91},[21,348,349],{},"I designed the nodes with a few key principles in mind:",[351,352,353,357,360],"ul",{},[354,355,356],"li",{},"Clean, minimal styling that matches our UI",[354,358,359],{},"Clear connection points (handles) for visual flow",[354,361,362],{},"Flexible content rendering through React nodes",[55,364,366],{"id":365},"state-management-and-flow-control","State Management and Flow Control",[21,368,369],{},"The core flow management is surprisingly straightforward with ReactFlow's hooks:",[63,371,373],{"className":65,"code":372,"language":67,"meta":68,"style":68},"const [nodes, setNodes] = useState\u003CNode[]>(initialNodes);\nconst [edges, setEdges] = useState(initialEdges);\n\nconst onNodesChange = useCallback(\n  (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)),\n  [],\n);\n\nconst onEdgesChange = useCallback(\n  (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)),\n  [],\n);\n",[70,374,375,418,443,447,462,509,516,522,526,539,581,587],{"__ignoreMap":68},[73,376,377,379,382,386,389,392,395,397,401,404,407,410,413,416],{"class":75,"line":76},[73,378,133],{"class":79},[73,380,381],{"class":91}," [",[73,383,385],{"class":384},"s_hVV","nodes",[73,387,388],{"class":91},",",[73,390,391],{"class":384}," setNodes",[73,393,394],{"class":91},"]",[73,396,88],{"class":87},[73,398,400],{"class":399},"sGLFI"," useState",[73,402,403],{"class":91},"\u003C",[73,405,406],{"class":83},"Node",[73,408,409],{"class":169},"[]",[73,411,412],{"class":91},">",[73,414,415],{"class":169},"(initialNodes)",[73,417,114],{"class":91},[73,419,420,422,424,427,429,432,434,436,438,441],{"class":75,"line":95},[73,421,133],{"class":79},[73,423,381],{"class":91},[73,425,426],{"class":384},"edges",[73,428,388],{"class":91},[73,430,431],{"class":384}," setEdges",[73,433,394],{"class":91},[73,435,88],{"class":87},[73,437,400],{"class":399},[73,439,440],{"class":169},"(initialEdges)",[73,442,114],{"class":91},[73,444,445],{"class":75,"line":117},[73,446,127],{"emptyLinePlaceholder":126},[73,448,449,451,454,456,459],{"class":75,"line":123},[73,450,133],{"class":79},[73,452,453],{"class":384}," onNodesChange",[73,455,88],{"class":87},[73,457,458],{"class":399}," useCallback",[73,460,461],{"class":169},"(\n",[73,463,464,467,470,472,475,477,479,481,483,486,488,491,493,495,498,501,503,506],{"class":75,"line":130},[73,465,466],{"class":91},"  (",[73,468,469],{"class":145},"changes",[73,471,102],{"class":87},[73,473,474],{"class":83}," NodeChange",[73,476,409],{"class":169},[73,478,344],{"class":91},[73,480,166],{"class":79},[73,482,391],{"class":399},[73,484,485],{"class":169},"(",[73,487,485],{"class":91},[73,489,490],{"class":145},"nds",[73,492,344],{"class":91},[73,494,166],{"class":79},[73,496,497],{"class":399}," applyNodeChanges",[73,499,500],{"class":169},"(changes",[73,502,388],{"class":91},[73,504,505],{"class":169}," nds))",[73,507,508],{"class":91},",\n",[73,510,511,514],{"class":75,"line":173},[73,512,513],{"class":169},"  []",[73,515,508],{"class":91},[73,517,518,520],{"class":75,"line":198},[73,519,344],{"class":169},[73,521,114],{"class":91},[73,523,524],{"class":75,"line":207},[73,525,127],{"emptyLinePlaceholder":126},[73,527,528,530,533,535,537],{"class":75,"line":223},[73,529,133],{"class":79},[73,531,532],{"class":384}," onEdgesChange",[73,534,88],{"class":87},[73,536,458],{"class":399},[73,538,461],{"class":169},[73,540,541,543,545,547,550,552,554,556,558,560,562,565,567,569,572,574,576,579],{"class":75,"line":240},[73,542,466],{"class":91},[73,544,469],{"class":145},[73,546,102],{"class":87},[73,548,549],{"class":83}," EdgeChange",[73,551,409],{"class":169},[73,553,344],{"class":91},[73,555,166],{"class":79},[73,557,431],{"class":399},[73,559,485],{"class":169},[73,561,485],{"class":91},[73,563,564],{"class":145},"eds",[73,566,344],{"class":91},[73,568,166],{"class":79},[73,570,571],{"class":399}," applyEdgeChanges",[73,573,500],{"class":169},[73,575,388],{"class":91},[73,577,578],{"class":169}," eds))",[73,580,508],{"class":91},[73,582,583,585],{"class":75,"line":255},[73,584,513],{"class":169},[73,586,508],{"class":91},[73,588,589,591],{"class":75,"line":261},[73,590,344],{"class":169},[73,592,114],{"class":91},[21,594,595],{},"What I love about this approach is how ReactFlow handles the complex state management internally while exposing a clean API for customization.",[55,597,599],{"id":598},"making-it-non-technical-user-friendly","Making It Non-Technical User Friendly",[21,601,602],{},"Here's where things got interesting. For our QHSC managers, I implemented several UX decisions:",[604,605,606],"ol",{},[354,607,608,612],{},[609,610,611],"strong",{},"Locked Navigation",": Prevented accidental canvas dragging",[63,614,616],{"className":65,"code":615,"language":67,"meta":68,"style":68},"\u003CReactFlow panOnDrag={false} draggable={false} preventScrolling={false} \u002F>\n",[70,617,618],{"__ignoreMap":68},[73,619,620,622,625,627,629,632,635,638,640,642,644,646,649,651,653,655,657],{"class":75,"line":76},[73,621,403],{"class":87},[73,623,624],{"class":169},"ReactFlow panOnDrag",[73,626,182],{"class":87},[73,628,231],{"class":91},[73,630,631],{"class":169},"false",[73,633,634],{"class":91},"}",[73,636,637],{"class":169}," draggable",[73,639,182],{"class":87},[73,641,231],{"class":91},[73,643,631],{"class":169},[73,645,634],{"class":91},[73,647,648],{"class":169}," preventScrolling",[73,650,182],{"class":87},[73,652,231],{"class":91},[73,654,631],{"class":169},[73,656,634],{"class":91},[73,658,659],{"class":87}," \u002F>\n",[604,661,662],{"start":95},[354,663,664,667],{},[609,665,666],{},"Visual Feedback",": Added animated edges to show flow direction",[63,669,671],{"className":65,"code":670,"language":67,"meta":68,"style":68},"const initialEdges: Edge[] = [\n  {\n    id: \"start-to-inspection\",\n    source: \"start\",\n    target: \"inspection\",\n    animated: true, \u002F\u002F This small detail makes a big difference\n  },\n  \u002F\u002F ...\n];\n",[70,672,673,693,698,716,732,748,765,770,775],{"__ignoreMap":68},[73,674,675,677,680,682,685,688,690],{"class":75,"line":76},[73,676,133],{"class":79},[73,678,679],{"class":384}," initialEdges",[73,681,102],{"class":87},[73,683,684],{"class":83}," Edge",[73,686,687],{"class":169},"[] ",[73,689,182],{"class":87},[73,691,692],{"class":169}," [\n",[73,694,695],{"class":75,"line":95},[73,696,697],{"class":91},"  {\n",[73,699,700,704,706,709,712,714],{"class":75,"line":117},[73,701,703],{"class":702},"skxfh","    id",[73,705,102],{"class":91},[73,707,708],{"class":185}," \"",[73,710,711],{"class":189},"start-to-inspection",[73,713,186],{"class":185},[73,715,508],{"class":91},[73,717,718,721,723,725,728,730],{"class":75,"line":123},[73,719,720],{"class":702},"    source",[73,722,102],{"class":91},[73,724,708],{"class":185},[73,726,727],{"class":189},"start",[73,729,186],{"class":185},[73,731,508],{"class":91},[73,733,734,737,739,741,744,746],{"class":75,"line":130},[73,735,736],{"class":702},"    target",[73,738,102],{"class":91},[73,740,708],{"class":185},[73,742,743],{"class":189},"inspection",[73,745,186],{"class":185},[73,747,508],{"class":91},[73,749,750,753,755,759,761],{"class":75,"line":173},[73,751,752],{"class":702},"    animated",[73,754,102],{"class":91},[73,756,758],{"class":757},"syTEX"," true",[73,760,388],{"class":91},[73,762,764],{"class":763},"sutJx"," \u002F\u002F This small detail makes a big difference\n",[73,766,767],{"class":75,"line":198},[73,768,769],{"class":91},"  },\n",[73,771,772],{"class":75,"line":207},[73,773,774],{"class":763},"  \u002F\u002F ...\n",[73,776,777,779],{"class":75,"line":223},[73,778,394],{"class":169},[73,780,114],{"class":91},[604,782,783],{"start":117},[354,784,785,788],{},[609,786,787],{},"Intuitive Icons",": Used familiar icons for different node types",[63,790,792],{"className":65,"code":791,"language":67,"meta":68,"style":68},"\u003Cdiv className=\"flex gap-2 items-center font-medium text-indigo-900\">\n  \u003CClipboardCheck className=\"w-4 h-4\" \u002F>\n  \u003Cspan>Inspection\u003C\u002Fspan>\n\u003C\u002Fdiv>\n",[70,793,794,811,829,845],{"__ignoreMap":68},[73,795,796,798,800,802,804,807,809],{"class":75,"line":76},[73,797,403],{"class":87},[73,799,179],{"class":169},[73,801,182],{"class":87},[73,803,186],{"class":185},[73,805,806],{"class":189},"flex gap-2 items-center font-medium text-indigo-900",[73,808,186],{"class":185},[73,810,195],{"class":87},[73,812,813,815,818,820,822,825,827],{"class":75,"line":95},[73,814,176],{"class":87},[73,816,817],{"class":169},"ClipboardCheck className",[73,819,182],{"class":87},[73,821,186],{"class":185},[73,823,824],{"class":189},"w-4 h-4",[73,826,186],{"class":185},[73,828,659],{"class":87},[73,830,831,833,835,838,841,843],{"class":75,"line":117},[73,832,176],{"class":169},[73,834,73],{"class":83},[73,836,837],{"class":169},">Inspection",[73,839,840],{"class":87},"\u003C\u002F",[73,842,73],{"class":169},[73,844,195],{"class":87},[73,846,847,849,851],{"class":75,"line":123},[73,848,840],{"class":87},[73,850,336],{"class":169},[73,852,195],{"class":87},[16,854,856],{"id":855},"what-id-do-differently","What I'd Do Differently",[21,858,859],{},"Looking back, there are a few things I'd approach differently:",[604,861,862,868,874],{},[354,863,864,867],{},[609,865,866],{},"Start with Mobile-First",": While ReactFlow works great on desktop, I should have considered mobile interactions earlier in the development process.",[354,869,870,873],{},[609,871,872],{},"More Interactive Elements",": I could have added more interactive elements to demonstrate the actual workflow building process, not just the final result.",[354,875,876,879],{},[609,877,878],{},"Performance Optimization",": For larger workflows, I'd implement virtualization earlier in the development cycle.",[16,881,883],{"id":882},"the-documentation-inspiration","The Documentation Inspiration",[21,885,886],{},"One unexpected outcome was how much I learned from ReactFlow's documentation. It's a masterclass in technical writing - clear, well-structured, and practical. It inspired me to improve our own documentation with:",[351,888,889,892,895,898],{},[354,890,891],{},"Interactive examples",[354,893,894],{},"Clear, concise code snippets",[354,896,897],{},"Progressive disclosure of complexity",[354,899,900],{},"Practical use cases",[16,902,904],{"id":903},"resources","Resources",[351,906,907,916,923,931],{},[354,908,909],{},[910,911,915],"a",{"href":912,"rel":913},"https:\u002F\u002Freactflow.dev\u002Fdocs\u002Fintroduction\u002F",[914],"nofollow","ReactFlow Documentation",[354,917,918],{},[910,919,922],{"href":920,"rel":921},"https:\u002F\u002Freactflow.dev\u002Fdocs\u002Fexamples\u002Foverview\u002F",[914],"React Flow Examples",[354,924,925,930],{},[910,926,929],{"href":927,"rel":928},"https:\u002F\u002Flucide.dev\u002F",[914],"Lucide Icons"," - For the workflow icons",[354,932,933],{},[910,934,937],{"href":935,"rel":936},"https:\u002F\u002Fwww.patterns.dev\u002Freact",[914],"TypeScript React Patterns",[16,939,941],{"id":940},"final-thoughts","Final Thoughts",[21,943,944],{},"Building this workflow visualization reminded me that sometimes the best technical solution isn't about showing off complex features - it's about making complex things feel simple. RF definitely helped me bridge the gap between technical capability and user understanding, which is exactly what we needed for our marketing site.",[21,946,947],{},"The next time you're faced with explaining complex technical concepts to non-technical users, consider whether a visual, interactive approach might be more effective than traditional documentation or static diagrams.",[949,950,951],"style",{},"html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sfCm-, html code.shiki .sfCm-{--shiki-light:#90A4AE;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}",{"title":68,"searchDepth":95,"depth":95,"links":953},[954,955,956,961,962,963,964],{"id":18,"depth":95,"text":19},{"id":34,"depth":95,"text":35},{"id":52,"depth":95,"text":53,"children":957},[958,959,960],{"id":57,"depth":117,"text":58},{"id":365,"depth":117,"text":366},{"id":598,"depth":117,"text":599},{"id":855,"depth":95,"text":856},{"id":882,"depth":95,"text":883},{"id":903,"depth":95,"text":904},{"id":940,"depth":95,"text":941},"2025-06-23","Using ReactFlow to build intuitive workflow visualizations for non-technical users, with insights on creating custom nodes and handling complex state management.","md",{"slug":969},"reactflow-workflow-visualization","\u002Fwriting\u002Freactflow-workflow-visualization",{"title":6,"description":966},"writing\u002Freactflow-workflow-visualization",[974,975,976,977,978],"React","ReactFlow","TypeScript","UI\u002FUX","Workflow Visualization","lVPty6QJp8WNFtvnnl40UxeZKhVzJHO7UAHJd0wV-sU",{"id":981,"title":982,"author":7,"body":983,"date":2177,"description":2178,"extension":967,"meta":2179,"navigation":126,"path":2181,"seo":2182,"stem":2183,"tags":2184,"__hash__":2189},"writing\u002Fwriting\u002Fbrowser-extension-messaging.md","Understanding Browser Extension Messaging",{"type":9,"value":984,"toc":2167},[985,988,990,993,996,1000,1007,1013,1017,1021,1024,1118,1123,1137,1389,1393,1404,1565,1569,1580,1708,1712,1723,1727,1734,1853,1860,1995,2002,2113,2115,2135,2138,2140,2164],[12,986,982],{"id":987},"understanding-browser-extension-messaging",[16,989,19],{"id":18},[21,991,992],{},"While building a Chrome extension for automated documentation generation (a story for another day), I found myself drowning in a sea of browser extension messaging concepts. Background scripts, content scripts, popup windows, offscreen documents – each piece seemed simple in isolation, but orchestrating communication between them felt like conducting an orchestra where every musician was in a different room.",[21,994,995],{},"After many late nights of debugging and several \"aha\" moments, I've developed a mental model that I wish I had when starting.",[16,997,999],{"id":998},"the-key-mental-model","The Key Mental Model",[21,1001,1002,1003,1006],{},"Think of a browser extension as a distributed system running in a single browser. Each component (background worker, popup, content script) is like a microservice with its own lifecycle and constraints. The key to mastery? Understanding not just how they communicate, but ",[28,1004,1005],{},"why"," they're separated in the first place.",[21,1008,1009],{},[42,1010],{"alt":1011,"src":1012},"Extension Components Diagram","\u002Fimages\u002Fcontent\u002Fbrowser-architecture.webp",[16,1014,1016],{"id":1015},"deep-dive-into-extension-messaging","Deep Dive into Extension Messaging",[55,1018,1020],{"id":1019},"_1-the-players-in-our-distributed-system","1. The Players in Our Distributed System",[21,1022,1023],{},"Let's break down each component and its role:",[63,1025,1027],{"className":65,"code":1026,"language":67,"meta":68,"style":68},"\u002F\u002F Example message type definition\ninterface Message {\n  target: \"background\" | \"content-script\" | \"popup\" | \"offscreen\";\n  action: string;\n  data?: unknown;\n}\n",[70,1028,1029,1034,1044,1088,1101,1114],{"__ignoreMap":68},[73,1030,1031],{"class":75,"line":76},[73,1032,1033],{"class":763},"\u002F\u002F Example message type definition\n",[73,1035,1036,1039,1042],{"class":75,"line":95},[73,1037,1038],{"class":79},"interface",[73,1040,1041],{"class":83}," Message",[73,1043,92],{"class":91},[73,1045,1046,1049,1051,1053,1056,1058,1061,1063,1066,1068,1070,1072,1075,1077,1079,1081,1084,1086],{"class":75,"line":117},[73,1047,1048],{"class":98},"  target",[73,1050,102],{"class":87},[73,1052,708],{"class":185},[73,1054,1055],{"class":189},"background",[73,1057,186],{"class":185},[73,1059,1060],{"class":87}," |",[73,1062,708],{"class":185},[73,1064,1065],{"class":189},"content-script",[73,1067,186],{"class":185},[73,1069,1060],{"class":87},[73,1071,708],{"class":185},[73,1073,1074],{"class":189},"popup",[73,1076,186],{"class":185},[73,1078,1060],{"class":87},[73,1080,708],{"class":185},[73,1082,1083],{"class":189},"offscreen",[73,1085,186],{"class":185},[73,1087,114],{"class":91},[73,1089,1090,1093,1095,1099],{"class":75,"line":123},[73,1091,1092],{"class":98},"  action",[73,1094,102],{"class":87},[73,1096,1098],{"class":1097},"sZMiF"," string",[73,1100,114],{"class":91},[73,1102,1103,1106,1109,1112],{"class":75,"line":130},[73,1104,1105],{"class":98},"  data",[73,1107,1108],{"class":87},"?:",[73,1110,1111],{"class":1097}," unknown",[73,1113,114],{"class":91},[73,1115,1116],{"class":75,"line":173},[73,1117,237],{"class":91},[1119,1120,1122],"h4",{"id":1121},"background-service-worker","Background Service Worker",[351,1124,1125,1128,1131,1134],{},[354,1126,1127],{},"The orchestrator",[354,1129,1130],{},"Always running (but can be inactive)",[354,1132,1133],{},"Can't access DOM",[354,1135,1136],{},"Handles long-running tasks",[63,1138,1140],{"className":65,"code":1139,"language":67,"meta":68,"style":68},"\u002F\u002F From my actual implementation\nexport default defineBackground(() => {\n  browser.runtime.onMessage.addListener((message, sender, sendResponse) => {\n    if (message.target !== \"background\") return;\n\n    const handleMessage = async () => {\n      switch (message.action) {\n        case \"start\":\n          \u002F\u002F Handle start action\n          break;\n        case \"stop\":\n          \u002F\u002F Handle stop action\n          break;\n      }\n    };\n\n    handleMessage();\n    return true; \u002F\u002F Important for async responses!\n  });\n});\n",[70,1141,1142,1147,1168,1211,1242,1246,1266,1285,1299,1304,1311,1324,1329,1335,1340,1345,1349,1358,1371,1380],{"__ignoreMap":68},[73,1143,1144],{"class":75,"line":76},[73,1145,1146],{"class":763},"\u002F\u002F From my actual implementation\n",[73,1148,1149,1153,1156,1159,1161,1164,1166],{"class":75,"line":95},[73,1150,1152],{"class":1151},"sVHd0","export",[73,1154,1155],{"class":1151}," default",[73,1157,1158],{"class":399}," defineBackground",[73,1160,485],{"class":169},[73,1162,1163],{"class":91},"()",[73,1165,166],{"class":79},[73,1167,92],{"class":91},[73,1169,1170,1173,1175,1178,1180,1183,1185,1188,1190,1192,1195,1197,1200,1202,1205,1207,1209],{"class":75,"line":117},[73,1171,1172],{"class":169},"  browser",[73,1174,108],{"class":91},[73,1176,1177],{"class":169},"runtime",[73,1179,108],{"class":91},[73,1181,1182],{"class":169},"onMessage",[73,1184,108],{"class":91},[73,1186,1187],{"class":399},"addListener",[73,1189,485],{"class":702},[73,1191,485],{"class":91},[73,1193,1194],{"class":145},"message",[73,1196,388],{"class":91},[73,1198,1199],{"class":145}," sender",[73,1201,388],{"class":91},[73,1203,1204],{"class":145}," sendResponse",[73,1206,344],{"class":91},[73,1208,166],{"class":79},[73,1210,92],{"class":91},[73,1212,1213,1216,1219,1221,1223,1225,1228,1230,1232,1234,1237,1240],{"class":75,"line":123},[73,1214,1215],{"class":1151},"    if",[73,1217,1218],{"class":702}," (",[73,1220,1194],{"class":169},[73,1222,108],{"class":91},[73,1224,217],{"class":169},[73,1226,1227],{"class":87}," !==",[73,1229,708],{"class":185},[73,1231,1055],{"class":189},[73,1233,186],{"class":185},[73,1235,1236],{"class":702},") ",[73,1238,1239],{"class":1151},"return",[73,1241,114],{"class":91},[73,1243,1244],{"class":75,"line":130},[73,1245,127],{"emptyLinePlaceholder":126},[73,1247,1248,1251,1254,1256,1259,1262,1264],{"class":75,"line":173},[73,1249,1250],{"class":79},"    const",[73,1252,1253],{"class":136}," handleMessage",[73,1255,88],{"class":87},[73,1257,1258],{"class":79}," async",[73,1260,1261],{"class":91}," ()",[73,1263,166],{"class":79},[73,1265,92],{"class":91},[73,1267,1268,1271,1273,1275,1277,1280,1282],{"class":75,"line":198},[73,1269,1270],{"class":1151},"      switch",[73,1272,1218],{"class":702},[73,1274,1194],{"class":169},[73,1276,108],{"class":91},[73,1278,1279],{"class":169},"action",[73,1281,1236],{"class":702},[73,1283,1284],{"class":91},"{\n",[73,1286,1287,1290,1292,1294,1296],{"class":75,"line":207},[73,1288,1289],{"class":1151},"        case",[73,1291,708],{"class":185},[73,1293,727],{"class":189},[73,1295,186],{"class":185},[73,1297,1298],{"class":91},":\n",[73,1300,1301],{"class":75,"line":223},[73,1302,1303],{"class":763},"          \u002F\u002F Handle start action\n",[73,1305,1306,1309],{"class":75,"line":240},[73,1307,1308],{"class":1151},"          break",[73,1310,114],{"class":91},[73,1312,1313,1315,1317,1320,1322],{"class":75,"line":255},[73,1314,1289],{"class":1151},[73,1316,708],{"class":185},[73,1318,1319],{"class":189},"stop",[73,1321,186],{"class":185},[73,1323,1298],{"class":91},[73,1325,1326],{"class":75,"line":261},[73,1327,1328],{"class":763},"          \u002F\u002F Handle stop action\n",[73,1330,1331,1333],{"class":75,"line":277},[73,1332,1308],{"class":1151},[73,1334,114],{"class":91},[73,1336,1337],{"class":75,"line":284},[73,1338,1339],{"class":91},"      }\n",[73,1341,1342],{"class":75,"line":298},[73,1343,1344],{"class":91},"    };\n",[73,1346,1347],{"class":75,"line":312},[73,1348,127],{"emptyLinePlaceholder":126},[73,1350,1351,1354,1356],{"class":75,"line":325},[73,1352,1353],{"class":399},"    handleMessage",[73,1355,1163],{"class":702},[73,1357,114],{"class":91},[73,1359,1360,1363,1365,1368],{"class":75,"line":330},[73,1361,1362],{"class":1151},"    return",[73,1364,758],{"class":757},[73,1366,1367],{"class":91},";",[73,1369,1370],{"class":763}," \u002F\u002F Important for async responses!\n",[73,1372,1373,1376,1378],{"class":75,"line":341},[73,1374,1375],{"class":91},"  }",[73,1377,344],{"class":702},[73,1379,114],{"class":91},[73,1381,1383,1385,1387],{"class":75,"line":1382},20,[73,1384,634],{"class":91},[73,1386,344],{"class":169},[73,1388,114],{"class":91},[1119,1390,1392],{"id":1391},"content-scripts","Content Scripts",[351,1394,1395,1398,1401],{},[354,1396,1397],{},"Your \"eyes and ears\" in the webpage",[354,1399,1400],{},"Can access DOM",[354,1402,1403],{},"Limited access to extension APIs",[63,1405,1407],{"className":65,"code":1406,"language":67,"meta":68,"style":68},"\u002F\u002F Content script message handling\nbrowser.runtime.onMessage.addListener(async (message) => {\n  if (message.target !== \"content-script\") return;\n\n  switch (message.action) {\n    case \"track-dom\":\n      startDomTracking();\n      break;\n    case \"stop-tracking\":\n      stopDomTracking();\n      break;\n  }\n});\n",[70,1408,1409,1414,1446,1473,1477,1494,1508,1517,1524,1537,1546,1552,1557],{"__ignoreMap":68},[73,1410,1411],{"class":75,"line":76},[73,1412,1413],{"class":763},"\u002F\u002F Content script message handling\n",[73,1415,1416,1419,1421,1423,1425,1427,1429,1431,1433,1436,1438,1440,1442,1444],{"class":75,"line":95},[73,1417,1418],{"class":169},"browser",[73,1420,108],{"class":91},[73,1422,1177],{"class":169},[73,1424,108],{"class":91},[73,1426,1182],{"class":169},[73,1428,108],{"class":91},[73,1430,1187],{"class":399},[73,1432,485],{"class":169},[73,1434,1435],{"class":79},"async",[73,1437,1218],{"class":91},[73,1439,1194],{"class":145},[73,1441,344],{"class":91},[73,1443,166],{"class":79},[73,1445,92],{"class":91},[73,1447,1448,1451,1453,1455,1457,1459,1461,1463,1465,1467,1469,1471],{"class":75,"line":117},[73,1449,1450],{"class":1151},"  if",[73,1452,1218],{"class":702},[73,1454,1194],{"class":169},[73,1456,108],{"class":91},[73,1458,217],{"class":169},[73,1460,1227],{"class":87},[73,1462,708],{"class":185},[73,1464,1065],{"class":189},[73,1466,186],{"class":185},[73,1468,1236],{"class":702},[73,1470,1239],{"class":1151},[73,1472,114],{"class":91},[73,1474,1475],{"class":75,"line":123},[73,1476,127],{"emptyLinePlaceholder":126},[73,1478,1479,1482,1484,1486,1488,1490,1492],{"class":75,"line":130},[73,1480,1481],{"class":1151},"  switch",[73,1483,1218],{"class":702},[73,1485,1194],{"class":169},[73,1487,108],{"class":91},[73,1489,1279],{"class":169},[73,1491,1236],{"class":702},[73,1493,1284],{"class":91},[73,1495,1496,1499,1501,1504,1506],{"class":75,"line":173},[73,1497,1498],{"class":1151},"    case",[73,1500,708],{"class":185},[73,1502,1503],{"class":189},"track-dom",[73,1505,186],{"class":185},[73,1507,1298],{"class":91},[73,1509,1510,1513,1515],{"class":75,"line":198},[73,1511,1512],{"class":399},"      startDomTracking",[73,1514,1163],{"class":702},[73,1516,114],{"class":91},[73,1518,1519,1522],{"class":75,"line":207},[73,1520,1521],{"class":1151},"      break",[73,1523,114],{"class":91},[73,1525,1526,1528,1530,1533,1535],{"class":75,"line":223},[73,1527,1498],{"class":1151},[73,1529,708],{"class":185},[73,1531,1532],{"class":189},"stop-tracking",[73,1534,186],{"class":185},[73,1536,1298],{"class":91},[73,1538,1539,1542,1544],{"class":75,"line":240},[73,1540,1541],{"class":399},"      stopDomTracking",[73,1543,1163],{"class":702},[73,1545,114],{"class":91},[73,1547,1548,1550],{"class":75,"line":255},[73,1549,1521],{"class":1151},[73,1551,114],{"class":91},[73,1553,1554],{"class":75,"line":261},[73,1555,1556],{"class":91},"  }\n",[73,1558,1559,1561,1563],{"class":75,"line":277},[73,1560,634],{"class":91},[73,1562,344],{"class":169},[73,1564,114],{"class":91},[1119,1566,1568],{"id":1567},"popup-ui","Popup UI",[351,1570,1571,1574,1577],{},[354,1572,1573],{},"Temporary lifecycle",[354,1575,1576],{},"Rich UI capabilities",[354,1578,1579],{},"Dies when closed",[63,1581,1583],{"className":65,"code":1582,"language":67,"meta":68,"style":68},"\u002F\u002F Popup component\nfunction PopupApp() {\n  const sendMessage = async () => {\n    await browser.runtime.sendMessage({\n      target: \"background\",\n      action: \"start\",\n      data: {\n        \u002F* configuration *\u002F\n      },\n    });\n  };\n}\n",[70,1584,1585,1590,1602,1620,1641,1656,1671,1680,1685,1690,1699,1704],{"__ignoreMap":68},[73,1586,1587],{"class":75,"line":76},[73,1588,1589],{"class":763},"\u002F\u002F Popup component\n",[73,1591,1592,1595,1598,1600],{"class":75,"line":95},[73,1593,1594],{"class":79},"function",[73,1596,1597],{"class":399}," PopupApp",[73,1599,1163],{"class":91},[73,1601,92],{"class":91},[73,1603,1604,1607,1610,1612,1614,1616,1618],{"class":75,"line":117},[73,1605,1606],{"class":79},"  const",[73,1608,1609],{"class":136}," sendMessage",[73,1611,88],{"class":87},[73,1613,1258],{"class":79},[73,1615,1261],{"class":91},[73,1617,166],{"class":79},[73,1619,92],{"class":91},[73,1621,1622,1625,1628,1630,1632,1634,1637,1639],{"class":75,"line":123},[73,1623,1624],{"class":1151},"    await",[73,1626,1627],{"class":169}," browser",[73,1629,108],{"class":91},[73,1631,1177],{"class":169},[73,1633,108],{"class":91},[73,1635,1636],{"class":399},"sendMessage",[73,1638,485],{"class":702},[73,1640,1284],{"class":91},[73,1642,1643,1646,1648,1650,1652,1654],{"class":75,"line":130},[73,1644,1645],{"class":702},"      target",[73,1647,102],{"class":91},[73,1649,708],{"class":185},[73,1651,1055],{"class":189},[73,1653,186],{"class":185},[73,1655,508],{"class":91},[73,1657,1658,1661,1663,1665,1667,1669],{"class":75,"line":173},[73,1659,1660],{"class":702},"      action",[73,1662,102],{"class":91},[73,1664,708],{"class":185},[73,1666,727],{"class":189},[73,1668,186],{"class":185},[73,1670,508],{"class":91},[73,1672,1673,1676,1678],{"class":75,"line":198},[73,1674,1675],{"class":702},"      data",[73,1677,102],{"class":91},[73,1679,92],{"class":91},[73,1681,1682],{"class":75,"line":207},[73,1683,1684],{"class":763},"        \u002F* configuration *\u002F\n",[73,1686,1687],{"class":75,"line":223},[73,1688,1689],{"class":91},"      },\n",[73,1691,1692,1695,1697],{"class":75,"line":240},[73,1693,1694],{"class":91},"    }",[73,1696,344],{"class":702},[73,1698,114],{"class":91},[73,1700,1701],{"class":75,"line":255},[73,1702,1703],{"class":91},"  };\n",[73,1705,1706],{"class":75,"line":261},[73,1707,237],{"class":91},[1119,1709,1711],{"id":1710},"offscreen-documents","Offscreen Documents",[351,1713,1714,1717,1720],{},[354,1715,1716],{},"Modern replacement for background pages",[354,1718,1719],{},"Handles tasks requiring DOM but no UI",[354,1721,1722],{},"Perfect for audio processing, canvas operations",[55,1724,1726],{"id":1725},"_2-common-pitfalls-and-solutions","2. Common Pitfalls and Solutions",[604,1728,1729],{},[354,1730,1731],{},[609,1732,1733],{},"Race Conditions",[63,1735,1737],{"className":65,"code":1736,"language":67,"meta":68,"style":68},"\u002F\u002F BAD: Fire and forget\nbrowser.runtime.sendMessage({ action: \"do_something\" });\n\n\u002F\u002F GOOD: Wait for response\nconst response = await browser.runtime.sendMessage({ action: \"do_something\" });\nif (response.success) {\n  \u002F\u002F Continue\n}\n",[70,1738,1739,1744,1778,1782,1787,1829,1844,1849],{"__ignoreMap":68},[73,1740,1741],{"class":75,"line":76},[73,1742,1743],{"class":763},"\u002F\u002F BAD: Fire and forget\n",[73,1745,1746,1748,1750,1752,1754,1756,1758,1760,1763,1765,1767,1770,1772,1774,1776],{"class":75,"line":95},[73,1747,1418],{"class":169},[73,1749,108],{"class":91},[73,1751,1177],{"class":169},[73,1753,108],{"class":91},[73,1755,1636],{"class":399},[73,1757,485],{"class":169},[73,1759,231],{"class":91},[73,1761,1762],{"class":702}," action",[73,1764,102],{"class":91},[73,1766,708],{"class":185},[73,1768,1769],{"class":189},"do_something",[73,1771,186],{"class":185},[73,1773,149],{"class":91},[73,1775,344],{"class":169},[73,1777,114],{"class":91},[73,1779,1780],{"class":75,"line":117},[73,1781,127],{"emptyLinePlaceholder":126},[73,1783,1784],{"class":75,"line":123},[73,1785,1786],{"class":763},"\u002F\u002F GOOD: Wait for response\n",[73,1788,1789,1791,1794,1796,1799,1801,1803,1805,1807,1809,1811,1813,1815,1817,1819,1821,1823,1825,1827],{"class":75,"line":130},[73,1790,133],{"class":79},[73,1792,1793],{"class":384}," response",[73,1795,88],{"class":87},[73,1797,1798],{"class":1151}," await",[73,1800,1627],{"class":169},[73,1802,108],{"class":91},[73,1804,1177],{"class":169},[73,1806,108],{"class":91},[73,1808,1636],{"class":399},[73,1810,485],{"class":169},[73,1812,231],{"class":91},[73,1814,1762],{"class":702},[73,1816,102],{"class":91},[73,1818,708],{"class":185},[73,1820,1769],{"class":189},[73,1822,186],{"class":185},[73,1824,149],{"class":91},[73,1826,344],{"class":169},[73,1828,114],{"class":91},[73,1830,1831,1834,1837,1839,1842],{"class":75,"line":173},[73,1832,1833],{"class":1151},"if",[73,1835,1836],{"class":169}," (response",[73,1838,108],{"class":91},[73,1840,1841],{"class":169},"success) ",[73,1843,1284],{"class":91},[73,1845,1846],{"class":75,"line":198},[73,1847,1848],{"class":763},"  \u002F\u002F Continue\n",[73,1850,1851],{"class":75,"line":207},[73,1852,237],{"class":91},[604,1854,1855],{"start":95},[354,1856,1857],{},[609,1858,1859],{},"Message Handler Memory Leaks",[63,1861,1863],{"className":65,"code":1862,"language":67,"meta":68,"style":68},"\u002F\u002F BAD: Listeners pile up\nfunction addListener() {\n  browser.runtime.onMessage.addListener(handler);\n}\n\n\u002F\u002F GOOD: Clean up listeners\nconst handler = (message) => {\n  \u002F* ... *\u002F\n};\nbrowser.runtime.onMessage.addListener(handler);\nreturn () => browser.runtime.onMessage.removeListener(handler);\n",[70,1864,1865,1870,1881,1906,1910,1914,1919,1938,1943,1947,1968],{"__ignoreMap":68},[73,1866,1867],{"class":75,"line":76},[73,1868,1869],{"class":763},"\u002F\u002F BAD: Listeners pile up\n",[73,1871,1872,1874,1877,1879],{"class":75,"line":95},[73,1873,1594],{"class":79},[73,1875,1876],{"class":399}," addListener",[73,1878,1163],{"class":91},[73,1880,92],{"class":91},[73,1882,1883,1885,1887,1889,1891,1893,1895,1897,1899,1902,1904],{"class":75,"line":117},[73,1884,1172],{"class":169},[73,1886,108],{"class":91},[73,1888,1177],{"class":169},[73,1890,108],{"class":91},[73,1892,1182],{"class":169},[73,1894,108],{"class":91},[73,1896,1187],{"class":399},[73,1898,485],{"class":702},[73,1900,1901],{"class":169},"handler",[73,1903,344],{"class":702},[73,1905,114],{"class":91},[73,1907,1908],{"class":75,"line":123},[73,1909,237],{"class":91},[73,1911,1912],{"class":75,"line":130},[73,1913,127],{"emptyLinePlaceholder":126},[73,1915,1916],{"class":75,"line":173},[73,1917,1918],{"class":763},"\u002F\u002F GOOD: Clean up listeners\n",[73,1920,1921,1923,1926,1928,1930,1932,1934,1936],{"class":75,"line":198},[73,1922,133],{"class":79},[73,1924,1925],{"class":136}," handler",[73,1927,88],{"class":87},[73,1929,1218],{"class":91},[73,1931,1194],{"class":145},[73,1933,344],{"class":91},[73,1935,166],{"class":79},[73,1937,92],{"class":91},[73,1939,1940],{"class":75,"line":207},[73,1941,1942],{"class":763},"  \u002F* ... *\u002F\n",[73,1944,1945],{"class":75,"line":223},[73,1946,120],{"class":91},[73,1948,1949,1951,1953,1955,1957,1959,1961,1963,1966],{"class":75,"line":240},[73,1950,1418],{"class":169},[73,1952,108],{"class":91},[73,1954,1177],{"class":169},[73,1956,108],{"class":91},[73,1958,1182],{"class":169},[73,1960,108],{"class":91},[73,1962,1187],{"class":399},[73,1964,1965],{"class":169},"(handler)",[73,1967,114],{"class":91},[73,1969,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1991,1993],{"class":75,"line":255},[73,1971,1239],{"class":1151},[73,1973,1261],{"class":91},[73,1975,166],{"class":79},[73,1977,1627],{"class":169},[73,1979,108],{"class":91},[73,1981,1177],{"class":169},[73,1983,108],{"class":91},[73,1985,1182],{"class":169},[73,1987,108],{"class":91},[73,1989,1990],{"class":399},"removeListener",[73,1992,1965],{"class":169},[73,1994,114],{"class":91},[604,1996,1997],{"start":117},[354,1998,1999],{},[609,2000,2001],{},"Context Death",[63,2003,2005],{"className":65,"code":2004,"language":67,"meta":68,"style":68},"\u002F\u002F BAD: Assuming context is always alive\n\u002F\u002F GOOD: Handle disconnects gracefully\ntry {\n  await browser.runtime.sendMessage({\n    \u002F* ... *\u002F\n  });\n} catch (error) {\n  if (error.message.includes(\"receiving end does not exist\")) {\n    \u002F\u002F Handle disconnected context\n  }\n}\n",[70,2006,2007,2012,2017,2024,2043,2048,2056,2068,2100,2105,2109],{"__ignoreMap":68},[73,2008,2009],{"class":75,"line":76},[73,2010,2011],{"class":763},"\u002F\u002F BAD: Assuming context is always alive\n",[73,2013,2014],{"class":75,"line":95},[73,2015,2016],{"class":763},"\u002F\u002F GOOD: Handle disconnects gracefully\n",[73,2018,2019,2022],{"class":75,"line":117},[73,2020,2021],{"class":1151},"try",[73,2023,92],{"class":91},[73,2025,2026,2029,2031,2033,2035,2037,2039,2041],{"class":75,"line":123},[73,2027,2028],{"class":1151},"  await",[73,2030,1627],{"class":169},[73,2032,108],{"class":91},[73,2034,1177],{"class":169},[73,2036,108],{"class":91},[73,2038,1636],{"class":399},[73,2040,485],{"class":702},[73,2042,1284],{"class":91},[73,2044,2045],{"class":75,"line":130},[73,2046,2047],{"class":763},"    \u002F* ... *\u002F\n",[73,2049,2050,2052,2054],{"class":75,"line":173},[73,2051,1375],{"class":91},[73,2053,344],{"class":702},[73,2055,114],{"class":91},[73,2057,2058,2060,2063,2066],{"class":75,"line":198},[73,2059,634],{"class":91},[73,2061,2062],{"class":1151}," catch",[73,2064,2065],{"class":169}," (error) ",[73,2067,1284],{"class":91},[73,2069,2070,2072,2074,2077,2079,2081,2083,2086,2088,2090,2093,2095,2098],{"class":75,"line":207},[73,2071,1450],{"class":1151},[73,2073,1218],{"class":702},[73,2075,2076],{"class":169},"error",[73,2078,108],{"class":91},[73,2080,1194],{"class":169},[73,2082,108],{"class":91},[73,2084,2085],{"class":399},"includes",[73,2087,485],{"class":702},[73,2089,186],{"class":185},[73,2091,2092],{"class":189},"receiving end does not exist",[73,2094,186],{"class":185},[73,2096,2097],{"class":702},")) ",[73,2099,1284],{"class":91},[73,2101,2102],{"class":75,"line":223},[73,2103,2104],{"class":763},"    \u002F\u002F Handle disconnected context\n",[73,2106,2107],{"class":75,"line":240},[73,2108,1556],{"class":91},[73,2110,2111],{"class":75,"line":255},[73,2112,237],{"class":91},[16,2114,856],{"id":855},[604,2116,2117,2123,2129],{},[354,2118,2119,2122],{},[609,2120,2121],{},"Start with TypeScript"," - Define your message types early. I started without it and regretted it.",[354,2124,2125,2128],{},[609,2126,2127],{},"Use a Message Bus Pattern"," - Centralize message handling logic instead of spreading it across files.",[354,2130,2131,2134],{},[609,2132,2133],{},"Build with Testing in Mind"," - Mock the messaging system for easier testing.",[21,2136,2137],{},"While I used WXT (a fantastic framework) for my extension, these principles apply to any browser extension. The framework handles the boilerplate, but understanding the underlying architecture is crucial.",[16,2139,904],{"id":903},[351,2141,2142,2149,2156],{},[354,2143,2144],{},[910,2145,2148],{"href":2146,"rel":2147},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fextensions\u002Fmv3\u002Farchitecture-overview\u002F",[914],"Chrome Extension Architecture Overview",[354,2150,2151],{},[910,2152,2155],{"href":2153,"rel":2154},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FMozilla\u002FAdd-ons\u002FWebExtensions\u002FContent_scripts#communicating_with_background_scripts",[914],"Browser Extension Messaging Guide",[354,2157,2158,2163],{},[910,2159,2162],{"href":2160,"rel":2161},"https:\u002F\u002Fwxt.dev\u002F",[914],"WXT Framework"," - If you want a modern development experience",[949,2165,2166],{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sfCm-, html code.shiki .sfCm-{--shiki-light:#90A4AE;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":68,"searchDepth":95,"depth":95,"links":2168},[2169,2170,2171,2175,2176],{"id":18,"depth":95,"text":19},{"id":998,"depth":95,"text":999},{"id":1015,"depth":95,"text":1016,"children":2172},[2173,2174],{"id":1019,"depth":117,"text":1020},{"id":1725,"depth":117,"text":1726},{"id":855,"depth":95,"text":856},{"id":903,"depth":95,"text":904},"2024-03-21","A little dive into browser extension messaging architecture, born from building a real SaaS product. Learn how different extension contexts communicate and how to architect your messaging system properly.",{"slug":2180},"browser-extension-messaging","\u002Fwriting\u002Fbrowser-extension-messaging",{"title":982,"description":2178},"writing\u002Fbrowser-extension-messaging",[2185,2186,2187,976,2188],"Browser Extensions","Architecture","Chrome Extensions","WXT","X43s65cDHVO8rtdFvQTLHLdevTtARnL1vIEOYKPpPu8",1780955296971]