Parse and traverse __NUXT_DATA__ — the SSR payload that Nuxt 3 embeds in every page but never really documents.
When Nuxt 3 renders a page server-side, it serializes the entire app state into a <script id="__NUXT_DATA__"> tag. The format is not a plain JSON object — it's a flat array where values reference each other by index.
[
"Alice",
42,
{ "name": 0, "age": 1 },
["Ref", 1],
["Profile", 2]
]Index 2 is an object, but its values (0, 1) are pointers into the array, not actual values. Index 3 is a Vue Ref wrapping index 1. Index 4 is an app-defined type (Profile) pointing to index 2.
Resolving this correctly means handling circular references, Vue reactivity wrappers, custom app types, and more. None of which is documented anywhere.
This library does that.
npm install nuxt-data-parser
# or
bun add nuxt-data-parserimport { extractFromHtml, extractFromUrl } from 'nuxt-data-parser'
// From an HTML string
const extractor = extractFromHtml(html)
// Or fetch directly (Node/Bun only)
const extractor = await extractFromUrl('https://example.com/page')
// Resolve any index in the raw array
extractor.resolve(0)
// Find the first value tagged with a type name — typed if you pass an interface
extractor.findByType<MyProfile>('Profile')
// Find all values tagged with a type name
extractor.findAllByType<Experience>('Experience')
// Inspect what's inside — useful when exploring an unknown site
const { tags, stores } = extractor.inspect()
// tags → ['Date', 'Experience', 'Profile', 'Ref', ...]
// stores → resolved plain objects with more than 8 keys (likely Pinia stores)
// Find a specific Pinia store by duck-typing its keys
const store = extractor.getPiniaStore(['skill-set', 'profile'])Every entry in the array is one of three things:
- A primitive (
string,number,boolean,null) — stored as-is - An object or array — whose values are indices into the same array, not actual values
- A tagged array —
["TypeName", ref, ...args]whereTypeNamesignals how to interpret the rest
Nuxt defines a handful of built-in tags (Ref, ShallowReactive, Reactive, Set, Map, Date, EmptyRef, payload-urls). Everything else is app-defined — your framework, your Pinia stores, your composables.
The tricky part is circular references. An object can eventually point back to itself. This library resolves them without looping by using a cache with a null sentinel before resolution starts.
Negative indices are special values from devalue: -1 → undefined, -2 → empty slot (hole in a sparse array), -3 → NaN, -4 → Infinity, -5 → -Infinity, -6 → -0. -7 as the first element of an array marks it as a sparse array encoded as [-7, length, idx, ref, ...].
Ps: Do bear in mind to pay close attention to payload-urls; from what I’ve seen, these are often —if not always— API endpoints, which aren’t always used by the platforms after the page has loaded, but are there because of NuxtLinks or the potential hydration of components.
Low-level. Takes the parsed JSON array directly.
Finds the __NUXT_DATA__ script tag in an HTML string and creates an extractor. Works in browser and Node.
Fetches a URL and creates an extractor. Node/Bun only.
| Method | Description |
|---|---|
resolve(idx) |
Resolve a single index |
resolveAll() |
Resolve the entire array — useful for debugging |
findByType<T>(name) |
First value tagged with name, typed |
findAllByType<T>(name) |
All values tagged with name, typed |
inspect() |
Returns all tag names and candidate Pinia stores |
getPiniaStore(keys?) |
Find a store by duck-typing on key presence |
Full coverage of the devalue format plus Nuxt-specific types.
| Tag | Resolves to |
|---|---|
Ref, Reactive, ShallowReactive |
Unwrapped value |
EmptyRef |
null |
Date |
Date object |
Set |
Set |
Map |
Map |
RegExp |
RegExp |
BigInt |
BigInt |
URL |
URL object |
URLSearchParams |
URLSearchParams object |
ArrayBuffer |
ArrayBuffer |
Int8Array … BigUint64Array |
Typed array |
payload-urls |
Base64-decoded URL string |
NuxtError, AsyncData, NavigationFailure |
Unwrapped value |
| anything else | First argument resolved — covers all app-defined types |
MIT