Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions packages/vinext/src/shims/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,12 +513,14 @@ export function headers(): Promise<Headers> & Headers {

const state = _getState();
if (!state.headersContext) {
return _decorateRejectedRequestApiPromise<Headers>(
new Error(
"headers() can only be called from a Server Component, Route Handler, " +
"or Server Action. Make sure you're not calling it from a Client Component.",
),
);
// Return empty readonly headers instead of rejecting.
// Libraries like TRPC call headers() inside React cache() during RSC
// module initialization, before runWithHeadersContext sets up the ALS
// scope. Rejecting here crashes the process; returning empty headers
// lets initialization proceed. The real request headers are available
// when the TRPC procedure is actually invoked during rendering.
const emptyHeaders = _sealHeaders(new Headers());
return _decorateRequestApiPromise(Promise.resolve(emptyHeaders), emptyHeaders);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This diverges from Next.js, which always throws via throwForMissingRequestStore('headers') when there's no request scope. Returning empty headers silently masks bugs — a developer who accidentally calls headers() at the top level of a module will get empty data with no indication anything is wrong.

The stated motivation (TRPC + React.cache() at module init) doesn't hold up because React.cache(fn) doesn't invoke fn — it returns a wrapper that defers invocation to call time.

}

if (state.headersContext.accessError) {
Expand All @@ -543,11 +545,9 @@ export function cookies(): Promise<RequestCookies> & RequestCookies {

const state = _getState();
if (!state.headersContext) {
return _decorateRejectedRequestApiPromise<RequestCookies>(
new Error(
"cookies() can only be called from a Server Component, Route Handler, or Server Action.",
),
);
// Return empty readonly cookies instead of rejecting (same rationale as headers()).
const emptyCookies = _sealCookies(new RequestCookies(new Map()));
return _decorateRequestApiPromise(Promise.resolve(emptyCookies), emptyCookies);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern as the headers() change. Next.js throws here; returning empty cookies silently masks incorrect usage.

}

if (state.headersContext.accessError) {
Expand Down
Loading