@@ -35,7 +35,7 @@ type ReadablePassthrough = {
3535 destroyed : boolean
3636}
3737
38- function createPassthrough ( onCancel ? : ( ) => void ) {
38+ function createPassthrough ( onCancel : ( ) => void ) {
3939 let controller : ReadableStreamDefaultController < any >
4040 const encoder = new TextEncoder ( )
4141 const stream = new ReadableStream ( {
@@ -44,27 +44,33 @@ function createPassthrough(onCancel?: () => void) {
4444 } ,
4545 cancel ( ) {
4646 res . destroyed = true
47- onCancel ?. ( )
47+ onCancel ( )
4848 } ,
4949 } )
5050
5151 const res : ReadablePassthrough = {
5252 stream,
5353 write : ( chunk ) => {
54+ // Don't write to destroyed stream
55+ if ( res . destroyed ) return
5456 if ( typeof chunk === 'string' ) {
5557 controller . enqueue ( encoder . encode ( chunk ) )
5658 } else {
5759 controller . enqueue ( chunk )
5860 }
5961 } ,
6062 end : ( chunk ) => {
63+ // Don't end already destroyed stream
64+ if ( res . destroyed ) return
6165 if ( chunk ) {
6266 res . write ( chunk )
6367 }
64- controller . close ( )
6568 res . destroyed = true
69+ controller . close ( )
6670 } ,
6771 destroy : ( error ) => {
72+ // Don't destroy already destroyed stream
73+ if ( res . destroyed ) return
6874 res . destroyed = true
6975 controller . error ( error )
7076 } ,
@@ -105,14 +111,20 @@ export function transformStreamWithRouter(
105111) {
106112 let stopListeningToInjectedHtml : ( ( ) => void ) | undefined = undefined
107113 let timeoutHandle : NodeJS . Timeout
114+ let cleanedUp = false
108115
109- const finalPassThrough = createPassthrough ( ( ) => {
116+ function cleanup ( ) {
117+ if ( cleanedUp ) return
118+ cleanedUp = true
110119 if ( stopListeningToInjectedHtml ) {
111120 stopListeningToInjectedHtml ( )
112121 stopListeningToInjectedHtml = undefined
113122 }
114123 clearTimeout ( timeoutHandle )
115- } )
124+ router . serverSsr ?. cleanup ( )
125+ }
126+
127+ const finalPassThrough = createPassthrough ( cleanup )
116128 const textDecoder = new TextDecoder ( )
117129
118130 let isAppRendering = true
@@ -149,13 +161,16 @@ export function transformStreamWithRouter(
149161 )
150162
151163 function handleInjectedHtml ( ) {
164+ // Don't process if already cleaned up
165+ if ( cleanedUp ) return
166+
152167 router . serverSsr ! . injectedHtml . forEach ( ( promise ) => {
153168 processingCount ++
154169
155170 promise
156171 . then ( ( html ) => {
157- // Don't write to destroyed stream
158- if ( finalPassThrough . destroyed ) {
172+ // Don't write to destroyed stream or after cleanup
173+ if ( cleanedUp || finalPassThrough . destroyed ) {
159174 return
160175 }
161176 if ( isAppRendering ) {
@@ -165,10 +180,7 @@ export function transformStreamWithRouter(
165180 }
166181 } )
167182 . catch ( ( err ) => {
168- // Only reject if not already settled
169- if ( ! finalPassThrough . destroyed ) {
170- injectedHtmlDonePromise . reject ( err )
171- }
183+ injectedHtmlDonePromise . reject ( err )
172184 } )
173185 . finally ( ( ) => {
174186 processingCount --
@@ -183,6 +195,11 @@ export function transformStreamWithRouter(
183195
184196 injectedHtmlDonePromise
185197 . then ( ( ) => {
198+ // Don't process if already cleaned up or destroyed
199+ if ( cleanedUp || finalPassThrough . destroyed ) {
200+ return
201+ }
202+
186203 clearTimeout ( timeoutHandle )
187204 const finalHtml =
188205 leftover + leftoverHtml + getBufferedRouterStream ( ) + pendingClosingTags
@@ -194,19 +211,24 @@ export function transformStreamWithRouter(
194211 finalPassThrough . end ( finalHtml )
195212 } )
196213 . catch ( ( err ) => {
214+ // Don't process if already cleaned up
215+ if ( cleanedUp || finalPassThrough . destroyed ) {
216+ return
217+ }
218+
197219 console . error ( 'Error reading routerStream:' , err )
198220 finalPassThrough . destroy ( err )
199221 } )
200- . finally ( ( ) => {
201- if ( stopListeningToInjectedHtml ) {
202- stopListeningToInjectedHtml ( )
203- stopListeningToInjectedHtml = undefined
204- }
205- } )
222+ . finally ( cleanup )
206223
207224 // Transform the appStream
208225 readStream ( appStream , {
209226 onData : ( chunk ) => {
227+ // Don't process if already cleaned up
228+ if ( cleanedUp || finalPassThrough . destroyed ) {
229+ return
230+ }
231+
210232 const text = decodeChunk ( chunk . value )
211233 const chunkString = leftover + text
212234 const bodyEndMatch = chunkString . match ( patternBodyEnd )
@@ -266,8 +288,8 @@ export function transformStreamWithRouter(
266288 }
267289 } ,
268290 onEnd : ( ) => {
269- // Don't process if stream was already destroyed/cancelled
270- if ( finalPassThrough . destroyed ) {
291+ // Don't process if stream was already destroyed/cancelled or cleaned up
292+ if ( cleanedUp || finalPassThrough . destroyed ) {
271293 return
272294 }
273295
@@ -288,6 +310,11 @@ export function transformStreamWithRouter(
288310 }
289311 } ,
290312 onError : ( error ) => {
313+ // Don't process if already cleaned up
314+ if ( cleanedUp ) {
315+ return
316+ }
317+
291318 console . error ( 'Error reading appStream:' , error )
292319 isAppRendering = false
293320 router . serverSsr ! . setRenderFinished ( )
0 commit comments