@@ -25,6 +25,7 @@ import {
25
25
type CachedFetchData ,
26
26
} from '../response-cache'
27
27
import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler'
28
+ import { cloneResponse } from './clone-response'
28
29
29
30
const isEdgeRuntime = process . env . NEXT_RUNTIME === 'edge'
30
31
@@ -676,20 +677,25 @@ export function createPatchedFetcher(
676
677
statusText : res . statusText ,
677
678
} )
678
679
} else {
680
+ // We're cloning the response using this utility because there
681
+ // exists a bug in the undici library around response cloning.
682
+ // See the following pull request for more details:
683
+ // https://github.com/vercel/next.js/pull/73274
684
+ const [ cloned1 , cloned2 ] = cloneResponse ( res )
685
+
679
686
// We are dynamically rendering including dev mode. We want to return
680
687
// the response to the caller as soon as possible because it might stream
681
688
// over a very long time.
682
- res
683
- . clone ( )
689
+ cloned1
684
690
. arrayBuffer ( )
685
691
. then ( async ( arrayBuffer ) => {
686
692
const bodyBuffer = Buffer . from ( arrayBuffer )
687
693
688
694
const fetchedData = {
689
- headers : Object . fromEntries ( res . headers . entries ( ) ) ,
695
+ headers : Object . fromEntries ( cloned1 . headers . entries ( ) ) ,
690
696
body : bodyBuffer . toString ( 'base64' ) ,
691
- status : res . status ,
692
- url : res . url ,
697
+ status : cloned1 . status ,
698
+ url : cloned1 . url ,
693
699
}
694
700
695
701
requestStore ?. serverComponentsHmrCache ?. set (
@@ -720,7 +726,7 @@ export function createPatchedFetcher(
720
726
)
721
727
. finally ( handleUnlock )
722
728
723
- return res
729
+ return cloned2
724
730
}
725
731
}
726
732
@@ -788,14 +794,23 @@ export function createPatchedFetcher(
788
794
if ( entry . isStale ) {
789
795
workStore . pendingRevalidates ??= { }
790
796
if ( ! workStore . pendingRevalidates [ cacheKey ] ) {
791
- workStore . pendingRevalidates [ cacheKey ] = doOriginalFetch (
792
- true
793
- )
794
- . catch ( console . error )
797
+ const pendingRevalidate = doOriginalFetch ( true )
798
+ . then ( async ( response ) => ( {
799
+ body : await response . arrayBuffer ( ) ,
800
+ headers : response . headers ,
801
+ status : response . status ,
802
+ statusText : response . statusText ,
803
+ } ) )
795
804
. finally ( ( ) => {
796
805
workStore . pendingRevalidates ??= { }
797
806
delete workStore . pendingRevalidates [ cacheKey || '' ]
798
807
} )
808
+
809
+ // Attach the empty catch here so we don't get a "unhandled
810
+ // promise rejection" warning.
811
+ pendingRevalidate . catch ( console . error )
812
+
813
+ workStore . pendingRevalidates [ cacheKey ] = pendingRevalidate
799
814
}
800
815
}
801
816
@@ -895,7 +910,7 @@ export function createPatchedFetcher(
895
910
if ( cacheKey && isForegroundRevalidate ) {
896
911
const pendingRevalidateKey = cacheKey
897
912
workStore . pendingRevalidates ??= { }
898
- const pendingRevalidate =
913
+ let pendingRevalidate =
899
914
workStore . pendingRevalidates [ pendingRevalidateKey ]
900
915
901
916
if ( pendingRevalidate ) {
@@ -914,27 +929,27 @@ export function createPatchedFetcher(
914
929
915
930
// We used to just resolve the Response and clone it however for
916
931
// static generation with dynamicIO we need the response to be able to
917
- // be resolved in a microtask and Response#clone() will never have a
918
- // body that can resolve in a microtask in node (as observed through
932
+ // be resolved in a microtask and cloning the response will never have
933
+ // a body that can resolve in a microtask in node (as observed through
919
934
// experimentation) So instead we await the body and then when it is
920
935
// available we construct manually cloned Response objects with the
921
936
// body as an ArrayBuffer. This will be resolvable in a microtask
922
937
// making it compatible with dynamicIO.
923
938
const pendingResponse = doOriginalFetch ( true , cacheReasonOverride )
924
-
925
- const nextRevalidate = pendingResponse
926
- . then ( async ( response ) => {
927
- // Clone the response here. It'll run first because we attached
928
- // the resolve before we returned below. We have to clone it
929
- // because the original response is going to be consumed by
930
- // at a later point in time.
931
- const clonedResponse = response . clone ( )
932
-
939
+ // We're cloning the response using this utility because there
940
+ // exists a bug in the undici library around response cloning.
941
+ // See the following pull request for more details:
942
+ // https://github.com/vercel/next.js/pull/73274
943
+ . then ( cloneResponse )
944
+
945
+ pendingRevalidate = pendingResponse
946
+ . then ( async ( responses ) => {
947
+ const response = responses [ 0 ]
933
948
return {
934
- body : await clonedResponse . arrayBuffer ( ) ,
935
- headers : clonedResponse . headers ,
936
- status : clonedResponse . status ,
937
- statusText : clonedResponse . statusText ,
949
+ body : await response . arrayBuffer ( ) ,
950
+ headers : response . headers ,
951
+ status : response . status ,
952
+ statusText : response . statusText ,
938
953
}
939
954
} )
940
955
. finally ( ( ) => {
@@ -949,11 +964,11 @@ export function createPatchedFetcher(
949
964
950
965
// Attach the empty catch here so we don't get a "unhandled promise
951
966
// rejection" warning
952
- nextRevalidate . catch ( ( ) => { } )
967
+ pendingRevalidate . catch ( ( ) => { } )
953
968
954
- workStore . pendingRevalidates [ pendingRevalidateKey ] = nextRevalidate
969
+ workStore . pendingRevalidates [ pendingRevalidateKey ] = pendingRevalidate
955
970
956
- return pendingResponse
971
+ return pendingResponse . then ( ( responses ) => responses [ 1 ] )
957
972
} else {
958
973
return doOriginalFetch ( false , cacheReasonOverride )
959
974
}
0 commit comments