Some notes on React Server Components (specifically the streaming wire format, aka Flight), Next.js v13+ (specifically __next_f), and Webpack.
- Figure a better way of extracting the main script URLs from the
index.html/etc:-
const scriptTags = document.querySelectorAll('html script[src*="_next"]'); const scriptUrls = Array.from(scriptTags).map(tag => tag.src).sort() // console.log(scriptUrls); const joinedUrls = urls.join('\n') console.log(joinedUrls); copy(joinedUrls);
-
const scriptTags = document.querySelectorAll('html script[src*="_next"]'); const scriptUrls = Array.from(scriptTags).map(tag => tag.src).sort() const joinedUrls = urls.join('\n') // const missingIds = window.webpackChunk_N_E.map(([[id]]) => id).filter(id => !joinedUrls.includes(id)).sort() // Note: See below code for how to calculate chunkMappings const missingIds = window.webpackChunk_N_E .map(([[id]]) => id) .filter(id => !Object.hasOwn(chunkMappings, id)) .filter(id => !scriptUrls.some(url => url.includes(`chunks/${id}-`))) .sort() const missingIdModules = window.webpackChunk_N_E.filter(([[id]]) => missingIds.includes(id)) console.log({ missingIds, chunkMappings, scriptUrls }) // If there are only 2 missingIds left, then one of them is probably yhe 'main-app-' chunk, and one is probably a chunk with a hashed name, which might be react or similar.
- This seems to give us ~13 webpack script files at the moment; but
window.webpackChunk_N_E.map(([[id]])=> id).sort()is giving us ~44 entries.. so need to figure out how to find and load the others statically..
-
- Figure out how Udio handles the files, as it doesn't seem to use a buildManifest, instead seeming to use
self.__next_fin the main index page- vercel/next.js#42170
- vercel/next.js#42170 (comment)
-
Here's how it works in the App Router. First, the stream returns critical metadata for the page (e.g. generateMetadata). Then, parts of your UI can be incrementally streamed in, helping achieve that fast initial paint. This UI streamed can be progressively enhanced, so if you have forms that call server actions, those will still work even though hydration hasn't completed. At the same time, the JS is streaming in to hydrate the page, as well as React Server Components payload that’s being added to the end of the stream. That's
self.__next_f. -
self.__next_fcontains the RSC payload, which you can see when you're viewing source.
-
- vercel/next.js#42170 (comment)
- https://www.reddit.com/r/nextjs/comments/173nx13/what_are_all_the_self_next_fpush_function_calls/
- vercel/next.js#14779
- vercel/next.js#56180 (comment)
-
those scripts are the initialization of the router state on the client. When next navigates from one page to another it does a client side transition (with a fetch to the server to get whatever new markup is required). Unlike in pages router App Router has shared layouts and the client is aware of these layouts and only fetches the part of the page that is not shared. The scripts you see here are the initialization of the client router state so that it can do these navigations. effectively. This includes preserving scroll position when hitting the back button and other aspects of the Router behavior.
-
- There seems to be some code related to
__next_fhere:- https://github.com/vercel/next.js/blob/50f3823d7eec2a9c6b652d5272d824d441c8cf69/packages/next/src/server/send-payload.ts#L81-L114
- This just seems to be sorting the script tags to ensure e-tag generation is deterministic
- https://github.com/vercel/next.js/blob/50f3823d7eec2a9c6b652d5272d824d441c8cf69/packages/next/src/server/app-render/use-flight-response.tsx#L138-L166
- This seems to be the main part of the code that generates/inserts the script tags (including defining what the 0, 1, 2 means)
- https://github.com/vercel/next.js/blob/50f3823d7eec2a9c6b652d5272d824d441c8cf69/packages/next/src/client/app-index.tsx#L115-L118
- This seems to be related to parsing the
__next_fchunks..
- This seems to be related to parsing the
- https://github.com/vercel/next.js/blob/50f3823d7eec2a9c6b652d5272d824d441c8cf69/packages/next/src/client/app-index.tsx#L54-L74
- This is the callback from the above, that handles the different
__next_fscript chunks
- This is the callback from the above, that handles the different
- https://github.com/vercel/next.js/blob/50f3823d7eec2a9c6b652d5272d824d441c8cf69/packages/next/src/server/send-payload.ts#L81-L114
- The webpack file seems to just refer to a single
.jsfile like this (and i'm not even sure if that gets used...):-
(d.u = function (e) { return "static/chunks/" + e + ".302361b2546aebf4.js"; }),
-
-
self.__next_f.map(f => f?.[1]).filter(f => f?.includes('static/'))
-
// const parseJSONFromEntry = entry => { // const jsonPart = entry.substring(entry.indexOf('[') + 1, entry.lastIndexOf(']')); // try { // return JSON.parse(`[${jsonPart}]`); // } catch (e) { // console.error("Failed to parse JSON for entry: ", entry); // return []; // Return an empty array or null as per error handling choice // } // }; // // Get build ID/etc // const buildId = self.__next_f // .map(f => f?.[1]) // .filter(f => f?.includes('buildId')) // .flatMap(f => f.trim().split('\n')) // .flatMap(parseJSONFromEntry) // .map(f => Array.isArray(f) ? f.flat() : f) // .map(f => f?.[3]?.buildId) // .filter(Boolean)?.[0]
-
// Source: https://gist.github.com/0xdevalias/ac465fb2f7e6fded183c2a4273d21e61#react-server-components-nextjs-v13-and-webpack-notes-on-streaming-wire-format-__next_f-etc const parseJSONFromEntry = entry => { const jsonPart = entry.substring(entry.indexOf('[') + 1, entry.lastIndexOf(']')); try { return JSON.parse(`[${jsonPart}]`); } catch (e) { console.error("Failed to parse JSON for entry: ", entry); return []; // Return an empty array or null as per error handling choice } }; // Function to transform dependencies into a simpler, directly accessible format function transformDependencies(dependencies) { return Object.values(dependencies).reduce((acc, currentDeps) => { Object.entries(currentDeps).forEach(([moduleId, path]) => { // If the paths match, skip to the next entry if (acc?.[moduleId] === path) return if (!acc[moduleId]) { // If this module ID has not been encountered yet, initialize it with the current path acc[moduleId] = path; } else if (typeof acc[moduleId] === 'string' && acc[moduleId] !== path) { // If the current path for this module ID is different from the existing one, // and the existing one is a string, transform it into an array containing both paths. const oldPath = acc[moduleId]; acc[moduleId] = [oldPath, path]; } else if (Array.isArray(acc[moduleId]) && !acc[moduleId].includes(path)) { // If the existing entry for this module ID is an array and does not already include the current path, // add the current path to the array. acc[moduleId].push(path); } else { // Log any unhandled cases for further investigation. This could be used to catch any unexpected data structures or duplicates. console.error('Unhandled case', { acc, currentDeps, moduleId, path }); } }); return acc; }, {}); } // Get _next script urls const scriptTags = document.querySelectorAll('html script[src*="_next"]'); const scriptUrls = Array.from(scriptTags).map(tag => tag.src).sort() // console.log(scriptUrls); // Get imports/etc (v3) const moduleDependencies = self.__next_f .map(f => f?.[1]) .filter(f => f?.includes('static/')) .flatMap(f => f.split('\n')) .map(parseJSONFromEntry) .filter(f => Array.isArray(f) ? f.length > 0 : !!f) .map(f => { if (!Array.isArray(f?.[1])) { return f } else { // Convert alternating key/value array to an object const keyValueArray = f[1]; const keyValuePairs = []; for (let i = 0; i < keyValueArray.length; i += 2) { keyValuePairs.push([keyValueArray[i], keyValueArray[i + 1]]); } f[1] = Object.fromEntries(keyValuePairs); return f; } }) .filter(f => Array.isArray(f) && f.length === 3 && typeof f?.[1] === 'object') .reduce((acc, [moduleId, dependencies, _]) => { acc[moduleId] = dependencies; return acc; }, {}); const chunkMappings = transformDependencies(moduleDependencies) const uniqueChunkPaths = Array.from(new Set(Object.values(chunkMappings))).sort() const dynamicChunkUrls = uniqueChunkPaths .map(path => `https://www.udio.com/_next/${path}`) .sort() const chunkUrls = Array.from(new Set([...scriptUrls, ...dynamicChunkUrls])).sort() const chunkUrlsJoined = chunkUrls.join('\n') const buildId = self.__next_f .map(f => f?.[1]) .filter(f => f?.includes('buildId')) .flatMap(f => f.trim().split('\n')) .flatMap(parseJSONFromEntry) .map(f => Array.isArray(f) ? f.flat() : f) .map(f => f?.[3]?.buildId) .filter(Boolean)?.[0] console.log({ scriptUrls, moduleDependencies, chunkMappings, uniqueChunkPaths, dynamicChunkUrls, chunkUrls, buildId, }) console.log(chunkUrlsJoined) copy(chunkUrlsJoined) console.log('Chunk URLs (joined) copied to clipboard')
-
// Get imports/etc (v2) // const parseJSONFromEntry = entry => { // const jsonPart = entry.substring(entry.indexOf('[') + 1, entry.lastIndexOf(']')); // try { // return JSON.parse(`[${jsonPart}]`); // } catch (e) { // console.error("Failed to parse JSON for entry: ", entry); // return []; // Return an empty array or null as per error handling choice // } // }; // // Get imports/etc (v2) // self.__next_f // .map(f => f?.[1]) // .filter(f => f?.includes('static/')) // .flatMap(f => f.split('\n')) // .map(parseJSONFromEntry) // .filter(f => Array.isArray(f) ? f.length > 0 : !!f) // .map(f => { // if (!Array.isArray(f?.[1])) { return f } else { // // Convert alternating key/value array to an object // const keyValueArray = f[1]; // const keyValuePairs = []; // for (let i = 0; i < keyValueArray.length; i += 2) { // keyValuePairs.push([keyValueArray[i], keyValueArray[i + 1]]); // } // f[1] = Object.fromEntries(keyValuePairs); // return f; // } // })
-
// Get imports/etc (v1) // const parseJSONFromEntry = entry => { // const jsonPart = entry.substring(entry.indexOf('[') + 1, entry.lastIndexOf(']')); // try { // return JSON.parse(`[${jsonPart}]`); // } catch (e) { // console.error("Failed to parse JSON for entry: ", entry); // return []; // Return an empty array or null as per error handling choice // } // }; // // Get imports/etc (v1) // self.__next_f // .map(f => f?.[1]) // .filter(f => f?.includes('static/')) // .flatMap(f => f.split('\n')) // .map(parseJSONFromEntry) // .filter(f => Array.isArray(f) ? f.length > 0 : !!f)
- vercel/next.js#42170
- pionxzh/wakaru#190
-
wakaru Issue #190: Support decoding React Server Components (RSC) Flight payloads, with Next.js
__next_fextraction as an initial source
-
- https://github.com/alvarlagerlof/rsc-parser
-
RSC Parser
-
This is a parser for React Server Components (RSC) when sent over the network. React uses a format to represent a tree of components/html or metadata such as requiered imports, suspense boundaries, and css/fonts that needs to be loaded.
I made this tool to more easily let you understand the data and explore it visually.
- https://github.com/alvarlagerlof/rsc-parser#extension
-
There is a Chrome Extension than you can add
- https://chromewebstore.google.com/detail/rsc-devtools/jcejahepddjnppkhomnidalpnnnemomn
-
RSC Devtools
-
React Server Components network visualizer
This is a tool that lets you record streaming data from React Server Components to then visualize and explore it. You can see how your components and data are loading and in what order and it lets you travel back in time by dragging the timeline slider.
-
- https://chromewebstore.google.com/detail/rsc-devtools/jcejahepddjnppkhomnidalpnnnemomn
-
- alvarlagerlof/rsc-parser#229
-
Explore turning on recording mode as soon as dev tools panel is shown
-
- alvarlagerlof/rsc-parser#923
-
Improve error message in web version
-
- alvarlagerlof/rsc-parser#924
-
Publish 'raw' package for use in other apps + CLI
- alvarlagerlof/rsc-parser#924 (comment)
-
I think I could expose
createFlightResponsefrom@rsc-parser/core. I'll make a PR.- alvarlagerlof/rsc-parser#946
-
Export
unstable_createFlightResponsefrom@rsc-parser/core
-
- alvarlagerlof/rsc-parser#946
-
- alvarlagerlof/rsc-parser#924 (comment)
-
I think that this is the best reference for how to use it:
- Format for the
messagesto pass tocreateFlightResponse: https://github.com/alvarlagerlof/rsc-parser/blob/0ceab13e635f741fd8765a31ccef26ef2d6c6f43/packages/core/src/components/ViewerPayload.tsx#L51-L66createFlightResponse: https://github.com/alvarlagerlof/rsc-parser/blob/main/packages/core/src/createFlightResponse.ts#L9processBinaryChunk: https://github.com/alvarlagerlof/rsc-parser/blob/main/packages/core/src/react/ReactFlightClient.ts#L882parseModelString: https://github.com/alvarlagerlof/rsc-parser/blob/main/packages/core/src/react/ReactFlightClient.ts#L177
- Format for the
-
-
- https://rsc-parser.vercel.app/
- https://rsc-parser-storybook.vercel.app/
- https://www.alvar.dev/blog/creating-devtools-for-react-server-components
-
- https://github.com/horita-yuya/rscq
-
CLI parser for React Server Component
- https://hrtyy.dev/web/rsc_parser/
-
- https://github.com/littledivy/rsc-parser
-
rsc-parser
-
Rust crate for parsing RSC (React server components) payloads.
-
- https://github.com/ronaldeddings/rsc-to-json
-
RSC to JSON
-
A TypeScript library for parsing React Server Components (RSC) network payloads into JSON.
-
- 🚀 Full RSC Support: Parse all RSC chunk types (imports, models, errors, hints)
- 🔗 Reference Resolution: Automatically resolve
$Lreferences into nested JSON - 📦 Dual Module Support: Works with both CommonJS and ES Modules
- 🎯 Type Safe: Written in TypeScript with full type definitions
- 🧪 Well Tested: Comprehensive test suite with real-world examples
-
- https://github.com/balyakin/flight-devtools
-
Flight Security DevTools
-
Security boundary DevTools for React Flight and RSC payloads. Parse streams, inspect server/client exposure, detect secrets, PII, Server Actions, and protocol anomalies in a Chrome DevTools panel.
-
The local-first security boundary inspector for React Server Components and Flight payloads.
-
Flight Security DevTools turns unreadable React Flight streams into an explainable map of what crossed the server/client boundary: data, module references, Server Actions, pending refs, parser anomalies, secrets, PII, and RCE-class patterns.
-
Highlights
- Chrome DevTools panel: opens as a dedicated
Flighttab next to Elements, Console, and Network. - Zero proxy MVP capture: uses
chrome.devtools.networkto detect completed RSC responses withoutdebuggerpermission. - Parser Worker: parses Flight payloads off the UI thread.
- Boundary Risk Summary: deterministic 0-100 risk score with Low, Medium, High, and Critical levels.
- Security rules: detects prototype traversal, suspicious thenables, constructor access, dangerous modules, secrets, PII, source/stack exposure, and oversized chunks.
- Tree and Raw evidence: jump from a finding to the parsed chunk or raw Flight row.
- Boundary Map: separates data, callable, module, and control-flow exposure.
- Streaming-first parser API: supports complete responses and incremental stream parsing.
- Local-first privacy: analysis runs locally; long-term storage/export primitives are designed for sanitized traces.
- Chrome DevTools panel: opens as a dedicated
-
- https://github.com/Adversis/nextjs-rsc-parser
-
RSC Parser for Burp Suite
-
Burpsuite plugin that parses React Server Component responses to extract interesting data
-
Parses React Server Component (RSC) responses to extract security-relevant data.
RSC is a React feature using the "Flight" wire protocol. Mostly seen in Next.js, but also used by Waku, Shopify Hydrogen, and RedwoodJS.
-
RSC responses (
text/x-component) contain serialized React trees with embedded data. This extension parses them and surfaces:- Route parameters (IDOR targets)
- Sensitive values (JWTs, API keys, tokens - detected by pattern, not keyword)
- Entity arrays (database records)
- Text content (rendered strings)
- Server actions (RPC endpoints)
-
- https://github.com/matt-kruse/demystifying-rsc
- https://demystifying-rsc.vercel.app/
-
Demystifying React Server Components with NextJS 13 App Router
-
This purpose of this application is to demonstrate the concepts and code of React Server Components in NextJS13 in a way that exposes what is really happening.
-
- https://demystifying-rsc.vercel.app/
- https://github.com/react/react/tree/main/packages
- https://github.com/react/react/tree/main/packages/react-client
-
react-client
This is an experimental package for consuming custom React streaming models.
-
- https://github.com/react/react/tree/main/packages/react-server
-
react-server
This is an experimental package for creating custom React streaming server renderers.
-
react-serveris a package implementing various Server Rendering capabilities. The two implementation are codenamedFizzandFlight.Fizzis a renderer for Server Side Rendering React. The same code that runs in the client (browser or native) is run on the server to produce an initial view to send to the client before it has to download and run React and all the user code to produce that view on the client.Flightis a renderer for React Server Components. These are components that never run on a client. The output of a React Server Component render can be a React tree that can run on the client or be SSR'd usingFizz. - https://github.com/react/react/tree/main/packages/react-server#flight-rendering
-
FlightRenderingreact-serverimplements the React Server Components rendering implementation. React Server Components is in essence a general purpose serialization and deserialization capability with support for some built-in React primitives such as Suspense and Lazy.
-
-
- https://github.com/react/react/tree/main/packages/react-server-dom-esm
-
react-server-dom-esm
Experimental React Flight bindings for DOM using ESM.
-
- https://github.com/react/react/tree/main/packages/react-server-dom-fb
- https://github.com/react/react/tree/main/packages/react-server-dom-parcel
-
react-server-dom-parcel
Experimental React Flight bindings for DOM using Parcel.
-
- https://github.com/react/react/tree/main/packages/react-server-dom-turbopack
-
react-server-dom-turbopack
Experimental React Flight bindings for DOM using Turbopack.
-
- https://github.com/react/react/tree/main/packages/react-server-dom-unbundled
-
react-server-dom-unbundled
Test-only React Flight bindings for DOM using Node.js.
This only exists for internal testing.
-
- https://github.com/react/react/tree/main/packages/react-server-dom-webpack
-
react-server-dom-webpack
Experimental React Flight bindings for DOM using Webpack.
- https://github.com/react/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightWebpackPlugin.js
- https://github.com/react/react/blob/main/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js
- https://github.com/react/react/blob/ce2bc58a9f6f3b0bfc8c738a0d8e2a5f3a332ff5/packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js#L75
renderToPipeableStream
- https://github.com/react/react/blob/ce2bc58a9f6f3b0bfc8c738a0d8e2a5f3a332ff5/packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js#L71
createFromReadableStream
-
- https://github.com/react/react/tree/main/packages/react-flight-server-fb
-
react-flight-server-fb
React Flight bindings for DOM using Meta's internal bundler.
-
- https://github.com/react/react/tree/main/packages/react-client
- https://timtech.blog/posts/react-server-components-rsc-no-framework/
-
React Server Components, without a framework? (07 Nov 2023)
-
- https://tonyalicea.dev/blog/understanding-react-server-components/
-
Understanding React Server Components (January 14, 2025)
-
- https://blog.nidhin.dev/decoding-the-flight-payload-in-react-server-components
-
Decoding the Flight Payload in React Server Components (December 21, 2025)
-
If you’ve worked with React Server Components (RSC), you know the server streams a special payload to the client using the React Flight protocol. At first glance, it looks like harmless serialized data — just chunks that eventually turn into UI.
But what if an attacker could inject their own chunks into that stream?
- https://tonyalicea.dev/blog/understanding-react-server-components/#:~:text=on%20the%20server?-,Flight,-In%20order%20to
-
Flight
-
In order to support executing function components on the server, and then building the Virtual DOM from their results on the client, React added the ability to serialize the React Element tree returned from server-executed functions.
-
In this case, the results of our component functions need to be serialized and sent to the client.
-
To prevent having to send this function to the client for execution, its results need to be serialized. Inside React's codebase this serialization format is called "flight" and the sum of data sent is called the "RSC Payload".
-
While React is providing the serialization format, the meta-framework (in this case Next.js) must do the work of ensuring the payload is created and sent to the client.
Next.js, for example, has a function in its codebase called
generateDynamicRSCPayload.The meta-framework is ensuring that the payload is generated and sent to the client. Thanks to the payload, on the client React can build an accurate Virtual DOM and do its normal reconciliation work.
- https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx#L594-L610
-
Next.js: generateDynamicRSCPayload
-
This is used by server actions & client-side navigations to generate RSC data from a client-side request.
-
- https://github.com/vercel/next.js/blob/canary/packages/next/src/server/app-render/app-render.tsx#L594-L610
-
-
the Flight format itself includes markers for things that haven't completed yet. Like Promises and lazy loading.
- https://tonyalicea.dev/blog/understanding-react-server-components/#:~:text=Giving%20React%20the%20Payload
-
Giving React the Payload
-
To support RSCs, React added to its codebase the ability to accept the Flight format (a string) and convert it to React Elements in functions like
parseModelString.It's up to the RSC-supporting meta-framework to execute those React APIs, sending the appropriate data.
- https://github.com/react/react/blob/main/packages/react-client/src/ReactFlightClient.js#L2386-L2391
react-client:parseModelString
- https://github.com/react/react/blob/main/packages/react-server/src/ReactFlightReplyServer.js#L1583-L1590
react-server:parseModelString
- https://github.com/react/react/blob/main/packages/react-client/src/ReactFlightClient.js#L2386-L2391
-
-
- https://www.debugbear.com/blog/react-server-components
-
An Introduction to React Server Components (March 11, 2026)
- https://www.debugbear.com/blog/react-server-components#how-the-rsc-payload-and-flight-protocol-work
-
How the RSC Payload and Flight Protocol Work
React serializes Server Components into an intermediate, streamable format called the Flight protocol. Rather than HTML, this payload is a compact representation of the Server Component tree that describes what to render without shipping the Server Component code itself. It includes:
- the rendered output of Server Components
- references to the required Client Component bundles
- props passed from Server Components to Client Components
The browser uses this payload to reconstruct and render the component tree for the initial load. Subsequent navigations may also request a new server-generated RSC payload if the route or its data can't be served from cache.
-
-
- https://gist.github.com/0xdevalias
- https://github.com/0xdevalias/chatgpt-source-watch : Analyzing the evolution of ChatGPT's codebase through time with curated archives and scripts.
- Deobfuscating / Unminifying Obfuscated Web App Code (0xdevalias gist)
- Fingerprinting Minified JavaScript Libraries / AST Fingerprinting / Source Code Similarity / Etc (0xdevalias gist)
- Reverse Engineering Webpack Apps (0xdevalias gist)
- Bypassing Cloudflare, Akamai, etc (0xdevalias gist)
- Debugging Electron Apps (and related memory issues) (0xdevalias gist)
- devalias' Beeper CSS Hacks (0xdevalias gist)
- Reverse Engineering Golang (0xdevalias' gist)
- Reverse Engineering on macOS (0xdevalias' gist)