@tanstack/router-core 1.168.9 → 1.168.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/cjs/hash-scroll.cjs +1 -1
  2. package/dist/cjs/hash-scroll.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +1 -1
  4. package/dist/cjs/load-matches.cjs +6 -6
  5. package/dist/cjs/load-matches.cjs.map +1 -1
  6. package/dist/cjs/router.cjs +57 -58
  7. package/dist/cjs/router.cjs.map +1 -1
  8. package/dist/cjs/router.d.cts +3 -1
  9. package/dist/cjs/scroll-restoration.cjs +1 -1
  10. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  11. package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
  12. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  13. package/dist/cjs/ssr/serializer/RawStream.cjs +41 -32
  14. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
  15. package/dist/cjs/ssr/serializer/RawStream.d.cts +12 -4
  16. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
  17. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.d.cts +2 -2
  18. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
  19. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -1
  20. package/dist/cjs/ssr/serializer/transformer.cjs +16 -14
  21. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  22. package/dist/cjs/ssr/serializer/transformer.d.cts +24 -23
  23. package/dist/cjs/ssr/ssr-client.cjs +9 -9
  24. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  25. package/dist/cjs/ssr/ssr-server.cjs +31 -9
  26. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  27. package/dist/cjs/ssr/ssr-server.d.cts +3 -2
  28. package/dist/cjs/ssr/transformStreamWithRouter.cjs +4 -1
  29. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  30. package/dist/cjs/stores.cjs +57 -57
  31. package/dist/cjs/stores.cjs.map +1 -1
  32. package/dist/cjs/stores.d.cts +16 -16
  33. package/dist/esm/hash-scroll.js +1 -1
  34. package/dist/esm/hash-scroll.js.map +1 -1
  35. package/dist/esm/index.d.ts +1 -1
  36. package/dist/esm/load-matches.js +6 -6
  37. package/dist/esm/load-matches.js.map +1 -1
  38. package/dist/esm/router.d.ts +3 -1
  39. package/dist/esm/router.js +57 -58
  40. package/dist/esm/router.js.map +1 -1
  41. package/dist/esm/scroll-restoration.js +1 -1
  42. package/dist/esm/scroll-restoration.js.map +1 -1
  43. package/dist/esm/ssr/createRequestHandler.js +2 -2
  44. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  45. package/dist/esm/ssr/serializer/RawStream.d.ts +12 -4
  46. package/dist/esm/ssr/serializer/RawStream.js +41 -32
  47. package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
  48. package/dist/esm/ssr/serializer/ShallowErrorPlugin.d.ts +2 -2
  49. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
  50. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -1
  51. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
  52. package/dist/esm/ssr/serializer/transformer.d.ts +24 -23
  53. package/dist/esm/ssr/serializer/transformer.js +16 -14
  54. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  55. package/dist/esm/ssr/ssr-client.js +9 -9
  56. package/dist/esm/ssr/ssr-client.js.map +1 -1
  57. package/dist/esm/ssr/ssr-server.d.ts +3 -2
  58. package/dist/esm/ssr/ssr-server.js +31 -9
  59. package/dist/esm/ssr/ssr-server.js.map +1 -1
  60. package/dist/esm/ssr/transformStreamWithRouter.js +4 -1
  61. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  62. package/dist/esm/stores.d.ts +16 -16
  63. package/dist/esm/stores.js +58 -58
  64. package/dist/esm/stores.js.map +1 -1
  65. package/package.json +3 -3
  66. package/src/hash-scroll.ts +1 -1
  67. package/src/index.ts +1 -1
  68. package/src/load-matches.ts +8 -11
  69. package/src/router.ts +74 -85
  70. package/src/scroll-restoration.ts +1 -1
  71. package/src/ssr/createRequestHandler.ts +4 -5
  72. package/src/ssr/serializer/RawStream.ts +65 -56
  73. package/src/ssr/serializer/ShallowErrorPlugin.ts +2 -2
  74. package/src/ssr/serializer/seroval-plugins.ts +2 -1
  75. package/src/ssr/serializer/transformer.ts +71 -76
  76. package/src/ssr/ssr-client.ts +8 -12
  77. package/src/ssr/ssr-server.ts +39 -7
  78. package/src/ssr/transformStreamWithRouter.ts +3 -0
  79. package/src/stores.ts +86 -86
@@ -1,7 +1,7 @@
1
1
  import { createPlugin } from 'seroval'
2
- import type { SerovalNode } from 'seroval'
2
+ import type { PluginInfo, SerovalNode } from 'seroval'
3
3
 
4
- export interface ErrorNode {
4
+ export interface ErrorNode extends PluginInfo {
5
5
  message: SerovalNode
6
6
  }
7
7
 
@@ -1,12 +1,13 @@
1
1
  import { ReadableStreamPlugin } from 'seroval-plugins/web'
2
2
  import { ShallowErrorPlugin } from './ShallowErrorPlugin'
3
3
  import { RawStreamSSRPlugin } from './RawStream'
4
+ import type { RawStream } from './RawStream'
4
5
  import type { Plugin } from 'seroval'
5
6
 
6
7
  export const defaultSerovalPlugins = [
7
8
  ShallowErrorPlugin as Plugin<Error, any>,
8
9
  // RawStreamSSRPlugin must come before ReadableStreamPlugin to match first
9
- RawStreamSSRPlugin,
10
+ RawStreamSSRPlugin as Plugin<RawStream, any>,
10
11
  // ReadableStreamNode is not exported by seroval
11
12
  ReadableStreamPlugin as Plugin<ReadableStream, any>,
12
13
  ]
@@ -1,6 +1,6 @@
1
1
  import { createPlugin } from 'seroval'
2
2
  import { GLOBAL_TSR } from '../constants'
3
- import type { Plugin, SerovalNode } from 'seroval'
3
+ import type { Plugin, PluginInfo, SerovalNode } from 'seroval'
4
4
  import type {
5
5
  RegisteredConfigType,
6
6
  RegisteredSsr,
@@ -14,6 +14,7 @@ declare const TSR_SERIALIZABLE: unique symbol
14
14
  export type TSR_SERIALIZABLE = typeof TSR_SERIALIZABLE
15
15
 
16
16
  export type TsrSerializable = { [TSR_SERIALIZABLE]: true }
17
+
17
18
  export interface DefaultSerializable {
18
19
  number: number
19
20
  string: string
@@ -25,6 +26,7 @@ export interface DefaultSerializable {
25
26
  Uint8Array: Uint8Array
26
27
  RawStream: RawStream
27
28
  TsrSerializable: TsrSerializable
29
+ void: void
28
30
  }
29
31
 
30
32
  export interface SerializableExtensions extends DefaultSerializable {}
@@ -72,13 +74,14 @@ export interface CreateSerializationAdapterOptions<
72
74
  fromSerializable: (value: TOutput) => TInput
73
75
  }
74
76
 
75
- export type ValidateSerializable<T, TSerializable> =
76
- T extends ReadonlyArray<unknown>
77
- ? ResolveArrayShape<T, TSerializable, 'input'>
78
- : T extends TSerializable
79
- ? T
80
- : T extends (...args: Array<any>) => any
81
- ? 'Function is not serializable'
77
+ export type ValidateSerializable<T, TSerializable> = T extends TSerializable
78
+ ? T
79
+ : T extends (...args: Array<any>) => any
80
+ ? SerializationError<'Function may not be serializable'>
81
+ : T extends RegisteredReadableStream
82
+ ? SerializationError<'JSX is not be serializable'>
83
+ : T extends ReadonlyArray<any>
84
+ ? ValidateSerializableArray<T, TSerializable>
82
85
  : T extends Promise<any>
83
86
  ? ValidateSerializablePromise<T, TSerializable>
84
87
  : T extends ReadableStream<any>
@@ -89,9 +92,9 @@ export type ValidateSerializable<T, TSerializable> =
89
92
  ? ValidateSerializableMap<T, TSerializable>
90
93
  : T extends AsyncGenerator<any, any>
91
94
  ? ValidateSerializableAsyncGenerator<T, TSerializable>
92
- : {
93
- [K in keyof T]: ValidateSerializable<T[K], TSerializable>
94
- }
95
+ : T extends object
96
+ ? ValidateSerializableMapped<T, TSerializable>
97
+ : SerializationError<'Type may not be serializable'>
95
98
 
96
99
  export type ValidateSerializableAsyncGenerator<T, TSerializable> =
97
100
  T extends AsyncGenerator<infer T, infer TReturn, infer TNext>
@@ -125,16 +128,26 @@ export type ValidateSerializableMap<T, TSerializable> =
125
128
  >
126
129
  : never
127
130
 
128
- export type RegisteredReadableStream =
129
- unknown extends SerializerExtensions['ReadableStream']
130
- ? never
131
- : SerializerExtensions['ReadableStream']
131
+ export type ValidateSerializableArray<T, TSerializable> = T extends readonly [
132
+ any,
133
+ ...Array<any>,
134
+ ]
135
+ ? ValidateSerializableMapped<T, TSerializable>
136
+ : T extends Array<infer U>
137
+ ? Array<ValidateSerializable<U, TSerializable>>
138
+ : T extends ReadonlyArray<infer U>
139
+ ? ReadonlyArray<ValidateSerializable<U, TSerializable>>
140
+ : never
132
141
 
133
- export interface DefaultSerializerExtensions {
134
- ReadableStream: unknown
142
+ export type ValidateSerializableMapped<T, TSerializable> = {
143
+ [K in keyof T]: ValidateSerializable<T[K], TSerializable>
135
144
  }
136
145
 
137
- export interface SerializerExtensions extends DefaultSerializerExtensions {}
146
+ const SERIALIZATION_ERROR = Symbol.for('TSR_SERIALIZATION_ERROR')
147
+
148
+ export interface SerializationError<in out TMessage extends string> {
149
+ [SERIALIZATION_ERROR]: TMessage
150
+ }
138
151
 
139
152
  export interface SerializationAdapter<
140
153
  TInput,
@@ -161,27 +174,34 @@ export interface SerializationAdapterTypes<
161
174
 
162
175
  export type AnySerializationAdapter = SerializationAdapter<any, any, any>
163
176
 
177
+ export interface AdapterNode extends PluginInfo {
178
+ v: SerovalNode
179
+ }
180
+
164
181
  /** Create a Seroval plugin for server-side serialization only. */
182
+ /* @__NO_SIDE_EFFECTS__ */
165
183
  export function makeSsrSerovalPlugin(
166
184
  serializationAdapter: AnySerializationAdapter,
167
185
  options: { didRun: boolean },
168
- ): Plugin<any, SerovalNode> {
169
- return createPlugin<any, SerovalNode>({
186
+ ): Plugin<any, AdapterNode> {
187
+ return /* @__PURE__ */ createPlugin<any, AdapterNode>({
170
188
  tag: '$TSR/t/' + serializationAdapter.key,
171
189
  test: serializationAdapter.test,
172
190
  parse: {
173
- stream(value, ctx) {
174
- return ctx.parse(serializationAdapter.toSerializable(value))
191
+ stream(value, ctx, _data) {
192
+ return {
193
+ v: ctx.parse(serializationAdapter.toSerializable(value)),
194
+ }
175
195
  },
176
196
  },
177
- serialize(node, ctx) {
197
+ serialize(node, ctx, _data) {
178
198
  options.didRun = true
179
199
  return (
180
200
  GLOBAL_TSR +
181
201
  '.t.get("' +
182
202
  serializationAdapter.key +
183
203
  '")(' +
184
- ctx.serialize(node) +
204
+ ctx.serialize(node.v) +
185
205
  ')'
186
206
  )
187
207
  },
@@ -191,27 +211,34 @@ export function makeSsrSerovalPlugin(
191
211
  }
192
212
 
193
213
  /** Create a Seroval plugin for client/server symmetric (de)serialization. */
214
+ /* @__NO_SIDE_EFFECTS__ */
194
215
  export function makeSerovalPlugin(
195
216
  serializationAdapter: AnySerializationAdapter,
196
- ): Plugin<any, SerovalNode> {
197
- return createPlugin<any, SerovalNode>({
217
+ ): Plugin<any, AdapterNode> {
218
+ return /* @__PURE__ */ createPlugin<any, AdapterNode>({
198
219
  tag: '$TSR/t/' + serializationAdapter.key,
199
220
  test: serializationAdapter.test,
200
221
  parse: {
201
- sync(value, ctx) {
202
- return ctx.parse(serializationAdapter.toSerializable(value))
222
+ sync(value, ctx, _data) {
223
+ return {
224
+ v: ctx.parse(serializationAdapter.toSerializable(value)),
225
+ }
203
226
  },
204
- async async(value, ctx) {
205
- return await ctx.parse(serializationAdapter.toSerializable(value))
227
+ async async(value, ctx, _data) {
228
+ return {
229
+ v: await ctx.parse(serializationAdapter.toSerializable(value)),
230
+ }
206
231
  },
207
- stream(value, ctx) {
208
- return ctx.parse(serializationAdapter.toSerializable(value))
232
+ stream(value, ctx, _data) {
233
+ return {
234
+ v: ctx.parse(serializationAdapter.toSerializable(value)),
235
+ }
209
236
  },
210
237
  },
211
238
  // we don't generate JS code outside of SSR (for now)
212
239
  serialize: undefined as never,
213
- deserialize(node, ctx) {
214
- return serializationAdapter.fromSerializable(ctx.deserialize(node))
240
+ deserialize(node, ctx, _data) {
241
+ return serializationAdapter.fromSerializable(ctx.deserialize(node.v))
215
242
  },
216
243
  })
217
244
  }
@@ -234,20 +261,6 @@ export type RegisteredSerializationAdapters<TRegister> = RegisteredConfigType<
234
261
  'serializationAdapters'
235
262
  >
236
263
 
237
- export type ValidateSerializableInputResult<TRegister, T> =
238
- ValidateSerializableResult<T, RegisteredSerializableInput<TRegister>>
239
-
240
- export type ValidateSerializableResult<T, TSerializable> =
241
- T extends ReadonlyArray<unknown>
242
- ? ResolveArrayShape<T, TSerializable, 'result'>
243
- : T extends TSerializable
244
- ? T
245
- : unknown extends SerializerExtensions['ReadableStream']
246
- ? { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
247
- : T extends SerializerExtensions['ReadableStream']
248
- ? ReadableStream
249
- : { [K in keyof T]: ValidateSerializableResult<T[K], TSerializable> }
250
-
251
264
  export type RegisteredSSROption<TRegister> =
252
265
  unknown extends RegisteredConfigType<TRegister, 'defaultSsr'>
253
266
  ? SSROption
@@ -282,31 +295,13 @@ export type ValidateSerializableLifecycleResultSSR<
282
295
  ? any
283
296
  : ValidateSerializableInput<TRegister, LooseReturnType<TFn>>
284
297
 
285
- type ResolveArrayShape<
286
- T extends ReadonlyArray<unknown>,
287
- TSerializable,
288
- TMode extends 'input' | 'result',
289
- > = number extends T['length']
290
- ? T extends Array<infer U>
291
- ? Array<ArrayModeResult<TMode, U, TSerializable>>
292
- : ReadonlyArray<ArrayModeResult<TMode, T[number], TSerializable>>
293
- : ResolveTupleShape<T, TSerializable, TMode>
294
-
295
- type ResolveTupleShape<
296
- T extends ReadonlyArray<unknown>,
297
- TSerializable,
298
- TMode extends 'input' | 'result',
299
- > = T extends readonly [infer THead, ...infer TTail]
300
- ? readonly [
301
- ArrayModeResult<TMode, THead, TSerializable>,
302
- ...ResolveTupleShape<Readonly<TTail>, TSerializable, TMode>,
303
- ]
304
- : T
305
-
306
- type ArrayModeResult<
307
- TMode extends 'input' | 'result',
308
- TValue,
309
- TSerializable,
310
- > = TMode extends 'input'
311
- ? ValidateSerializable<TValue, TSerializable>
312
- : ValidateSerializableResult<TValue, TSerializable>
298
+ export type RegisteredReadableStream =
299
+ unknown extends SerializerExtensions['ReadableStream']
300
+ ? never
301
+ : SerializerExtensions['ReadableStream']
302
+
303
+ export interface DefaultSerializerExtensions {
304
+ ReadableStream: unknown
305
+ }
306
+
307
+ export interface SerializerExtensions extends DefaultSerializerExtensions {}
@@ -95,7 +95,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
95
95
  }
96
96
 
97
97
  // Hydrate the router state
98
- const matches = router.matchRoutes(router.stores.location.state)
98
+ const matches = router.matchRoutes(router.stores.location.get())
99
99
 
100
100
  // kick off loading the route chunks
101
101
  const routeChunkPromise = Promise.all(
@@ -162,7 +162,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
162
162
  }
163
163
  })
164
164
 
165
- router.stores.setActiveMatches(matches)
165
+ router.stores.setMatches(matches)
166
166
 
167
167
  // Allow the user to handle custom hydration data
168
168
  await router.options.hydrate?.(dehydratedData)
@@ -170,8 +170,8 @@ export async function hydrate(router: AnyRouter): Promise<any> {
170
170
  // now that all necessary data is hydrated:
171
171
  // 1) fully reconstruct the route context
172
172
  // 2) execute `head()` and `scripts()` for each match
173
- const activeMatches = router.stores.activeMatchesSnapshot.state
174
- const location = router.stores.location.state
173
+ const activeMatches = router.stores.matches.get()
174
+ const location = router.stores.location.get()
175
175
  await Promise.all(
176
176
  activeMatches.map(async (match) => {
177
177
  try {
@@ -258,7 +258,7 @@ export async function hydrate(router: AnyRouter): Promise<any> {
258
258
  // (e.g. preloads, invalidations) don't mistakenly detect a href change
259
259
  // (resolvedLocation defaults to undefined and router.load() is skipped
260
260
  // in the normal SSR hydration path).
261
- router.stores.resolvedLocation.setState(() => router.stores.location.state)
261
+ router.stores.resolvedLocation.set(router.stores.location.get())
262
262
  return routeChunkPromise
263
263
  }
264
264
 
@@ -292,13 +292,9 @@ export async function hydrate(router: AnyRouter): Promise<any> {
292
292
  // ensure router is not in status 'pending' anymore
293
293
  // this usually happens in Transitioner but if loading synchronously resolves,
294
294
  // Transitioner won't be rendered while loading so it cannot track the change from loading:true to loading:false
295
- if (router.stores.status.state === 'pending') {
296
- router.batch(() => {
297
- router.stores.status.setState(() => 'idle')
298
- router.stores.resolvedLocation.setState(
299
- () => router.stores.location.state,
300
- )
301
- })
295
+ if (router.stores.status.get() === 'pending') {
296
+ router.stores.status.set('idle')
297
+ router.stores.resolvedLocation.set(router.stores.location.get())
302
298
  }
303
299
  // hide the pending component once the load is finished
304
300
  router.updateMatch(match.id, (prev) => ({
@@ -2,6 +2,7 @@ import { crossSerializeStream, getCrossReferenceHeader } from 'seroval'
2
2
  import { invariant } from '../invariant'
3
3
  import { decodePath } from '../utils'
4
4
  import { createLRUCache } from '../lru-cache'
5
+ import { rootRouteId } from '../root'
5
6
  import minifiedTsrBootStrapScript from './tsrScript?script-string'
6
7
  import { GLOBAL_TSR, TSR_SCRIPT_BARRIER_ID } from './constants'
7
8
  import { dehydrateSsrMatchId } from './ssr-match-id'
@@ -171,12 +172,31 @@ function getManifestCache(manifest: Manifest): ManifestLRU {
171
172
  export function attachRouterServerSsrUtils({
172
173
  router,
173
174
  manifest,
175
+ getRequestAssets,
174
176
  }: {
175
177
  router: AnyRouter
176
178
  manifest: Manifest | undefined
179
+ getRequestAssets?: () => Array<RouterManagedTag> | undefined
177
180
  }) {
178
181
  router.ssr = {
179
- manifest,
182
+ get manifest() {
183
+ const requestAssets = getRequestAssets?.()
184
+ if (!requestAssets?.length) return manifest
185
+ // Merge request-scoped assets into root route without mutating cached manifest
186
+ return {
187
+ ...manifest,
188
+ routes: {
189
+ ...manifest?.routes,
190
+ [rootRouteId]: {
191
+ ...manifest?.routes?.[rootRouteId],
192
+ assets: [
193
+ ...requestAssets,
194
+ ...(manifest?.routes?.[rootRouteId]?.assets ?? []),
195
+ ],
196
+ },
197
+ },
198
+ }
199
+ },
180
200
  }
181
201
  let _dehydrated = false
182
202
  let _serializationFinished = false
@@ -200,7 +220,7 @@ export function attachRouterServerSsrUtils({
200
220
  const html = `<script${router.options.ssr?.nonce ? ` nonce='${router.options.ssr.nonce}'` : ''}>${script}</script>`
201
221
  router.serverSsr!.injectHtml(html)
202
222
  },
203
- dehydrate: async () => {
223
+ dehydrate: async (opts?: { requestAssets?: Array<RouterManagedTag> }) => {
204
224
  if (_dehydrated) {
205
225
  if (process.env.NODE_ENV !== 'production') {
206
226
  throw new Error('Invariant failed: router is already dehydrated!')
@@ -208,7 +228,7 @@ export function attachRouterServerSsrUtils({
208
228
 
209
229
  invariant()
210
230
  }
211
- let matchesToDehydrate = router.stores.activeMatchesSnapshot.state
231
+ let matchesToDehydrate = router.stores.matches.get()
212
232
  if (router.isShell()) {
213
233
  // In SPA mode we only want to dehydrate the root match
214
234
  matchesToDehydrate = matchesToDehydrate.slice(0, 1)
@@ -257,6 +277,15 @@ export function attachRouterServerSsrUtils({
257
277
  manifestToDehydrate = {
258
278
  routes: filteredRoutes,
259
279
  }
280
+
281
+ // Merge request-scoped assets into root route (without mutating cached manifest)
282
+ if (opts?.requestAssets?.length) {
283
+ const existingRoot = manifestToDehydrate.routes[rootRouteId]
284
+ manifestToDehydrate.routes[rootRouteId] = {
285
+ ...existingRoot,
286
+ assets: [...opts.requestAssets, ...(existingRoot?.assets ?? [])],
287
+ }
288
+ }
260
289
  }
261
290
  const dehydratedRouter: DehydratedRouter = {
262
291
  manifest: manifestToDehydrate,
@@ -305,6 +334,13 @@ export function attachRouterServerSsrUtils({
305
334
  }
306
335
  scriptBuffer.enqueue(serialized)
307
336
  },
337
+ onError: (err: unknown) => {
338
+ console.error('Serialization error:', err)
339
+ if (err && (err as any).stack) {
340
+ console.error((err as any).stack)
341
+ }
342
+ signalSerializationComplete()
343
+ },
308
344
  scopeId: SCOPE_ID,
309
345
  onDone: () => {
310
346
  scriptBuffer.enqueue(GLOBAL_TSR + '.e()')
@@ -313,10 +349,6 @@ export function attachRouterServerSsrUtils({
313
349
  scriptBuffer.flush()
314
350
  signalSerializationComplete()
315
351
  },
316
- onError: (err) => {
317
- console.error('Serialization error:', err)
318
- signalSerializationComplete()
319
- },
320
352
  })
321
353
  },
322
354
  isDehydrated() {
@@ -366,6 +366,9 @@ export function transformStreamWithRouter(
366
366
  if (isAppRendering || leftover || pendingClosingTags) {
367
367
  appendRouterHtml(html)
368
368
  } else {
369
+ // App is done rendering - flush any pending buffer first to maintain order,
370
+ // then write the new HTML directly
371
+ flushPendingRouterHtml()
369
372
  safeEnqueue(html)
370
373
  }
371
374
  })