Serializable State
- Serialize plain data only
- Supported: primitives, arrays, plain objects, Dates, Map/Set, TypedArrays, URL/SearchParams, BigInt
- References and cycles are preserved
- Avoid: user-defined functions, non-built-in class instances, DOM, closures
State passed from server to client must be serialized into HTML. Keeping state plain and deterministic ensures reliable hydration and enables compiler optimizations.
Serializable Data
State is embedded into HTML during server rendering. The serializer supports the following plain data types:
- Primitives:
null
,boolean
,number
,string
,bigint
- Arrays and plain objects with serializable values
- Dates
- Map, Set
- Typed arrays and ArrayBuffer/DataView
- URL and URLSearchParams
- Additional built-in JS and Browser objects
- For a complete list, see the serializer file from source
Nested values must also be serializable.
<let/user={
id: 12,
name: "Marko",
tags: ["admin", "editor"],
created: "2024-04-15",
}>
<const/created=new Date(user.created)>
<let/ids=new Set([user.id])>
let/user={
id: 12,
name: "Marko",
tags: ["admin", "editor"],
created: "2024-04-15",
}
const/created=new Date(user.created)
let/ids=new Set([user.id])
<let/user={
id: 12,
name: "Marko",
tags: ["admin", "editor"],
created: "2024-04-15",
}>
<const/created=new Date(user.created)>
<let/ids=new Set([user.id])>
let/user={
id: 12,
name: "Marko",
tags: ["admin", "editor"],
created: "2024-04-15",
}
const/created=new Date(user.created)
let/ids=new Set([user.id])
Unserializable Data
Some values cannot be embedded into HTML in a stable, deterministic way. These should not generally be stored in state:
- Closures over JS instance variables
- Class instances (except built-ins explicitly supported by the runtime)
- DOM nodes and elements
<Most
functions
and
closures
in
Marko
_are_
serializable.
Static
variables
and
Marko
state
can
be
used
safely./>
<let/handler=null>
<const/onSecondClick() {
// serializable!
}>
<button onClick() {
handler?.();
handler = onSecondClick;
}/>
Most
,functions
,and
,closures
,in
,Marko
,_are_
,serializable.
,Static
,variables
,and
,Marko
,state
,can
,be
,used
,safely.
let/handler=null
const/onSecondClick() {
// serializable!
}
button onClick() {
handler?.();
handler = onSecondClick;
}
<Most
functions
and
closures
in
Marko
_are_
serializable.
Static
variables
and
Marko
state
can
be
used
safely./>
<let/handler=null>
<const/onSecondClick() {
// serializable!
}>
<button onClick() {
handler?.();
handler = onSecondClick;
}/>
Most
,functions
,and
,closures
,in
,Marko
,_are_
,serializable.
,Static
,variables
,and
,Marko
,state
,can
,be
,used
,safely.
let/handler=null
const/onSecondClick() {
// serializable!
}
button onClick() {
handler?.();
handler = onSecondClick;
}
Instead, keep plain data in state and construct functions, class instances, or DOM references at usage sites (for example, in event handlers or server actions).
// ❌ BAD: function in state
<let/state={ save: () => doThing() }>
// ❌ BAD: custom class instance in state
<let/state=new Cart()>
// ❌ BAD: DOM nodes in state
<let/state={ el: document.body }>
// ❌ BAD: function in state
let/state={ save: () => doThing() }
// ❌ BAD: custom class instance in state
let/state=new Cart()
// ❌ BAD: DOM nodes in state
let/state={ el: document.body }
// ❌ BAD: function in state
<let/state={ save: () => doThing() }>
// ❌ BAD: custom class instance in state
<let/state=new Cart()>
// ❌ BAD: DOM nodes in state
<let/state={ el: document.body }>
// ❌ BAD: function in state
let/state={ save: () => doThing() }
// ❌ BAD: custom class instance in state
let/state=new Cart()
// ❌ BAD: DOM nodes in state
let/state={ el: document.body }
Further Reading
Contributors
Helpful? You can thank these awesome people! You can also edit this doc if you see any issues or want to improve it.