Skip to content

Commit e2c76a5

Browse files
ihabadhamclaude
andcommitted
Wrap RSCRoute in local Suspense to prevent whole-page collapse
Without a local Suspense boundary, RSCRoute's in-flight fetch suspension bubbles up to whatever outer Suspense the Pro stream_react_component infrastructure provides. That outer fallback wipes the entire rendered tree, so during a Refresh or Simulate Error click, the whole page collapsed to viewport height for ~500ms and the browser snapped scrollY to 0 (since the page wasn't tall enough to preserve the prior scroll position). Verified empirically via window.scrollY + document.body.scrollHeight sampling on the deployed review-app: pre-fix, pageHeight went from 2193px to 764px between +50ms and +500ms after click. Local Suspense boundary contains the suspension to the LiveActivity section; same- shape skeleton fallback keeps the section's height stable so the surrounding layout doesn't shift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f23beba commit e2c76a5

1 file changed

Lines changed: 22 additions & 2 deletions

File tree

client/app/bundles/server-components/components/LiveActivityRefresher.jsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
'use client';
22

3-
import React, { useState } from 'react';
3+
import React, { useState, Suspense } from 'react';
44
import { ErrorBoundary } from 'react-error-boundary';
55
import RSCRoute from 'react-on-rails-pro/RSCRoute';
66
import { useRSC } from 'react-on-rails-pro/RSCProvider';
77

8+
// Same shape and dimensions as the rendered LiveActivity card. Local Suspense
9+
// fallback prevents the RSCRoute suspension from bubbling to an outer
10+
// boundary, which would collapse the whole page during in-flight fetches.
11+
const ActivityCardSkeleton = () => (
12+
<div className="bg-gradient-to-br from-indigo-50 to-purple-50 border border-indigo-200 rounded-xl p-5">
13+
<div className="grid grid-cols-3 gap-4 text-sm">
14+
{['Server Time', 'Free RAM', 'Uptime (hrs)'].map((label) => (
15+
<div key={label}>
16+
<div className="text-xs text-indigo-600 font-medium uppercase tracking-wide mb-1">
17+
{label}
18+
</div>
19+
<div className="font-mono text-indigo-300 animate-pulse"></div>
20+
</div>
21+
))}
22+
</div>
23+
</div>
24+
);
25+
826
const LiveActivityRefresher = () => {
927
const [refreshKey, setRefreshKey] = useState(0);
1028
const [simulateError, setSimulateError] = useState(false);
@@ -67,7 +85,9 @@ const LiveActivityRefresher = () => {
6785
)}
6886
resetKeys={[refreshKey]}
6987
>
70-
<RSCRoute componentName="LiveActivity" componentProps={{ simulateError, refreshKey }} />
88+
<Suspense fallback={<ActivityCardSkeleton />}>
89+
<RSCRoute componentName="LiveActivity" componentProps={{ simulateError, refreshKey }} />
90+
</Suspense>
7191
</ErrorBoundary>
7292
</div>
7393
);

0 commit comments

Comments
 (0)