Skip to content

Conversation

@azami
Copy link

@azami azami commented Jun 30, 2025

Summary

  • Fixed Suspense fallback components not displaying when using React Router v7's Suspense/Await pattern
  • Added proper streaming headers to enable React Router v7's Suspense streaming functionality

Problem

In React Router v7, when using the Suspense/Await pattern for deferred data loading, the Suspense fallback component was not being displayed. Instead, the page would appear to hang without showing any loading state.

Example code that wasn't working properly:

// Loader returns promise directly (v7 pattern, no defer() needed)
export const loader = ({ params }) => {
  const userPromise = getUserById(params.id); // Returns Promise
  return { user: userPromise }; // Return promise directly
};

export default function UserDetailPage() {
  const { user } = useLoaderData();
  return (
    <Suspense fallback={<Loading />}>
      <Await resolve={user}>
        {(resolved) => <UserDetail user={resolved} />}
      </Await>
    </Suspense>
  );
}

Root Cause

React Router v7 requires proper HTTP streaming headers to enable Suspense streaming functionality. The development handler was not setting the necessary transfer-encoding: chunked header for HTML responses, preventing the streaming behavior that allows Suspense fallbacks to render while awaiting deferred promises.

Solution

Modified src/dev.ts to:

  1. Await the response from React Router's createRequestHandler
  2. Set transfer-encoding: chunked header for HTML responses to enable streaming
  3. Return the properly configured response
const response = await handler(c.req.raw, resolvedContext)

if (response.headers.get('content-type')?.includes('text/html')) {
  response.headers.set('transfer-encoding', 'chunked')
}

return response

Why This Fix Works

  • HTTP Streaming: The transfer-encoding: chunked header tells the browser that the response will be sent in chunks, enabling the streaming behavior
  • Suspense Boundaries: React Router v7's Suspense implementation relies on HTTP streaming to send the initial HTML with fallback content, then stream updates when promises resolve
  • Development Mode: This ensures the development server properly supports the same streaming behavior as production deployments

🤖 Generated with Claude Code

Add transfer-encoding chunked header for HTML responses to properly support
Suspense streaming in React Router v7 development mode.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
const response = await handler(c.req.raw, resolvedContext)

if (response.headers.get('content-type')?.includes('text/html')) {
response.headers.set('transfer-encoding', 'chunked')
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure it's a good design if we set transfering-encoding: chunked for all HTML responses.

Copy link
Author

Choose a reason for hiding this comment

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

I agree, this fix was quite hasty. Since it doesn't seem to be a straightforward fix, I will log an issue for it at this time.

@azami
Copy link
Author

azami commented Jun 30, 2025

This should be handled in a separate issue. I'll create one.

@azami azami closed this Jun 30, 2025
@azami azami deleted the fix/react-router-v7-suspense-streaming branch June 30, 2025 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants