-
Notifications
You must be signed in to change notification settings - Fork 373
Expand file tree
/
Copy pathServerComponentsPage.jsx
More file actions
129 lines (122 loc) · 5.47 KB
/
ServerComponentsPage.jsx
File metadata and controls
129 lines (122 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Server Component - this entire component runs on the server.
// It can use Node.js APIs and server-only dependencies directly.
// None of these imports are shipped to the client bundle.
import React, { Suspense } from 'react';
import ServerInfo from './components/ServerInfo';
import CommentsFeed from './components/CommentsFeed';
import TogglePanel from './components/TogglePanel';
const ServerComponentsPage = () => {
return (
<div className="max-w-4xl mx-auto py-8 px-4">
<header className="mb-10">
<h1 className="text-3xl font-bold text-slate-800 mb-2">
React Server Components Demo
</h1>
<p className="text-slate-600 text-lg">
This page is rendered using <strong>React Server Components</strong> with React on Rails Pro.
Server components run on the server and stream their output to the client, keeping
heavy dependencies out of the browser bundle entirely.
</p>
</header>
<div className="space-y-8">
{/* Server Info - uses Node.js os module (impossible on client) */}
<section>
<h2 className="text-xl font-semibold text-slate-700 mb-4 flex items-center gap-2">
Server Environment
<span className="text-xs font-normal bg-emerald-100 text-emerald-700 px-2 py-0.5 rounded-full">
Server Only
</span>
</h2>
<ServerInfo />
</section>
{/* Interactive toggle - demonstrates mixing server + client components */}
<section>
<h2 className="text-xl font-semibold text-slate-700 mb-4 flex items-center gap-2">
Interactive Client Component
<span className="text-xs font-normal bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full">
Client Hydrated
</span>
</h2>
<TogglePanel title="How does this work?">
<div className="prose prose-slate max-w-none text-sm">
<p>
This toggle is a <code>'use client'</code> component, meaning it ships JavaScript
to the browser for interactivity. But the content inside is rendered on the server
and passed as children — a key RSC pattern called the <strong>donut pattern</strong>.
</p>
<ul>
<li>The <code>TogglePanel</code> wrapper runs on the client (handles click events)</li>
<li>The children content is rendered on the server (no JS cost)</li>
<li>Heavy libraries used by server components never reach the browser</li>
</ul>
</div>
</TogglePanel>
</section>
{/* Async data fetching with Suspense streaming */}
<section>
<h2 className="text-xl font-semibold text-slate-700 mb-4 flex items-center gap-2">
Streamed Comments
<span className="text-xs font-normal bg-amber-100 text-amber-700 px-2 py-0.5 rounded-full">
Async + Suspense
</span>
</h2>
<p className="text-slate-500 text-sm mb-4">
Comments are fetched directly on the server using the Rails API.
The page shell renders immediately while this section streams in progressively.
</p>
<Suspense
fallback={
<div className="animate-pulse space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="bg-slate-100 rounded-lg p-4">
<div className="h-4 bg-slate-200 rounded w-1/4 mb-2" />
<div className="h-3 bg-slate-200 rounded w-3/4" />
</div>
))}
</div>
}
>
<CommentsFeed />
</Suspense>
</section>
{/* Architecture explanation */}
<section className="bg-slate-50 border border-slate-200 rounded-xl p-6">
<h2 className="text-lg font-semibold text-slate-700 mb-3">
What makes this different?
</h2>
<div className="grid md:grid-cols-2 gap-4 text-sm text-slate-600">
<div>
<h3 className="font-medium text-slate-800 mb-1">Smaller Client Bundle</h3>
<p>
Libraries like <code>lodash</code>, <code>marked</code>, and Node.js <code>os</code> module
are used on this page but never downloaded by the browser.
</p>
</div>
<div>
<h3 className="font-medium text-slate-800 mb-1">Direct Data Access</h3>
<p>
Server components fetch data by calling your Rails API internally — no
client-side fetch waterfalls or loading spinners for initial data.
</p>
</div>
<div>
<h3 className="font-medium text-slate-800 mb-1">Progressive Streaming</h3>
<p>
The page shell renders instantly. Async components (like the comments feed)
stream in as their data resolves, with Suspense boundaries showing fallbacks.
</p>
</div>
<div>
<h3 className="font-medium text-slate-800 mb-1">Selective Hydration</h3>
<p>
Only client components (like the toggle above) receive JavaScript.
Everything else is pure HTML — zero hydration cost.
</p>
</div>
</div>
</section>
</div>
</div>
);
};
export default ServerComponentsPage;