@lazarv
react-server
ã©ãã§ãReactãå®è¡
ãµãŒããŒãã¯ã©ã€ã¢ã³ããã¯ãŒã«ãŒããšããžç°å¢ã§Reactã³ã³ããŒãã³ããå®è¡ããåäžã©ã³ã¿ã€ã â React Server Componentsãã¹ããªãŒãã³ã°ããããã€ãæšæºæèŒã
ã³ã³ã»ãã
ã©ã³ã¿ã€ã ãšããŠã®React
Reactã¯ãã©ãŠã¶ã®å€ã§åäœããŸããã³ã³ããŒãã³ãã¯å®è¡å¯èœãªãŠãããã§ããããµãŒããŒãã¯ãŒã«ãŒããšããžã§å®è¡ãããŸããã©ã³ã¿ã€ã ãåã³ã³ããŒãã³ãã®å®è¡å Žæãšæ¹æ³ã決å®ããŸãã
ã©ã³ã¿ã€ã ã®å¢çã¯ãããŸãããReactãæžãã ãã§ãåãã¢ãã«ãã©ãã§ãæ©èœããŸããã³ã³ããŒãã³ãã¯ãµãŒããŒã§ããŒã¿ãååŸããã¯ã©ã€ã¢ã³ãã«HTMLãã¹ããªãŒãã³ã°ããã¯ãŒã«ãŒã¹ã¬ããã§ç»åãåŠçã§ããŸã â ãã¹ãŠåãæ§æã§ã
"use client"ã"use worker"ã"use live" â ãã£ãäžã€ã®ãã£ã¬ã¯ãã£ãã§ã³ã³ããŒãã³ãã®å®è¡å Žæã倿Žã§ããŸãããã«ãã¹ãããã®éæ³ããå¥ã®APIãèŠããå¿
èŠããããŸãããã©ã³ã¿ã€ã ãã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ãã¹ããªãŒãã³ã°ããã€ãã¬ãŒã·ã§ã³ãèªåçã«åŠçããŸãã
ã€ãŸããã¢ããªã±ãŒã·ã§ã³ã¯ãµãŒããŒã³ãŒããã¯ã©ã€ã¢ã³ãã³ãŒãããããŠãã®éã®ã°ã«ãŒã³ãŒãã®ãããã¯ãŒã¯ã§ã¯ãªããäžã€ã®çµ±äžãããReactããªãŒã§ãããã©ãã€ã ãåãæ¿ããããšãªããåäžãã¡ã€ã«ãã忣ã·ã¹ãã ãŸã§ã¹ã±ãŒã«ã§ããŸãã
Reactã³ã³ããŒãã³ãã¯ãã¯ãåãªãUIã§ã¯ãªããã¢ããªã±ãŒã·ã§ã³ã©ã³ã¿ã€ã ã®å®è¡å¯èœãªãŠãããã§ãã
ãµãŒããŒã³ã³ããŒãã³ã â ãµãŒããŒäžã§å®è¡ãããŒã¿ããŒã¹ãAPIã«çŽæ¥ã¢ã¯ã»ã¹ããã¯ã©ã€ã¢ã³ããã³ãã«ã®ã³ã¹ãã¯ãŒãã
export default async function Page() {
const data = await db.query("SELECT ...");
return <Feed items={data} />;
}
ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ã â ãã©ãŠã¶ã§ãã€ãã¬ãŒããããªãã¿ã®Reactãã®ãã®ã
"use client";
export default function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}
ã¯ãŒã«ãŒ â ããã¯ã°ã©ãŠã³ãã¹ã¬ããã§å®è¡ããµãŒããŒãUIããããã¯ããã«éãåŠçããªãããŒãã
"use worker";
export async function resize(buf) {
return sharp(buf).resize(800).toBuffer();
}
ã©ã€ãã³ã³ããŒãã³ã â WebSocketçµç±ã§æŽæ°ãã¹ããªãŒãã³ã°ãåyieldãæ°ããReactããªãŒãã¯ã©ã€ã¢ã³ãã«éä¿¡ã
"use live";
export default async function* Price({ symbol }) {
while (true) {
yield <span>{await getPrice(symbol)}</span>;
await new Promise((r) => setTimeout(r, 1000));
}
}
@lazarv/react-server ã¯ããµãŒããŒãã¯ã©ã€ã¢ã³ããã¯ãŒã«ãŒããšããžç°å¢ã§Reactã³ã³ããŒãã³ããå®è¡ããããã®ã©ã³ã¿ã€ã ã§ãã
ãã¡ã€ã«ã1ã€äœæ â ãããã¢ããªå
šäœã§ããèšå®ããã€ã©ãŒãã¬ãŒããäžèŠãnpx @lazarv/react-server ./App.jsx ã§èµ·åã
export default function App() {
return <h1>Hello World!</h1>;
}
ã¹ã±ãŒã«ããæºåãã§ããããapp/page.jsx ã«ããŒãžã远å ããŠãšã³ããªãŒãã€ã³ããçç¥ããã¡ã€ã«ã·ã¹ãã ã«ãŒã¿ãŒãèªåçã«åŒãç¶ããŸãã
export default async function Home() {
const posts = await db.posts.findMany();
return (
<main>
<h1>Blog</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
ç念
ãªã @lazarv/react-server ãªã®ãïŒ
ã»ãšãã©ã®Reactãã¬ãŒã ã¯ãŒã¯ãã¡ã¿ãã¬ãŒã ã¯ãŒã¯ã¯ãæåã®ã³ã³ããŒãã³ããæžãåã«å€§éã®ã»ããã¢ãããå¿
èŠã§ãã@lazarv/react-server ã¯ç°ãªãã¢ãããŒããåããŸã â node ã§ã¹ã¯ãªãããå®è¡ããããã«ç°¡åã§ãããReactåãã§ãã
åäžãã¡ã€ã«ããå§ããŠãããŒãºã®æé·ã«åãããŠãã¡ã€ã«ã·ã¹ãã ã«ãŒãã£ã³ã°ããã£ãã·ã³ã°ãéççæããã«ãã©ã³ã¿ã€ã ãããã€ãåãããã«æ©èœã¢ããªã±ãŒã·ã§ã³ã«ã¹ã±ãŒã«ã§ããŸãã
å³åº§ã®HMRãå®çŸããViteæèŒãReactã忢±ããŠããã®ã§ãããžã§ã¯ãã¯è»œéãNode.jsãBunãDenoããµããŒãããã«ãã€ã³ã¢ããã¿ãŒã§VercelãNetlifyãCloudflareãªã©ã«ãããã€å¯èœã
æ°ç§ã§éå§
äŸåé¢ä¿1ã€ããã¡ã€ã«1ã€ãã³ãã³ã1ã€ã
ã¯ã€ãã¯ã¹ã¿ãŒã
ã€ã³ã¹ããŒã«ïŒå®è¡
äŸåé¢ä¿ã1ã€è¿œå ããReactã³ã³ããŒãã³ãããšã¯ã¹ããŒããã .jsx ãã¡ã€ã«ãäœæããéçºãµãŒããŒãèµ·åããŸããããã ãã§å®å
šãªSSRãšãããã¢ãžã¥ãŒã«ãªãã¬ãŒã¹ã¡ã³ããåäœããŸã â ãã€ã©ãŒãã¬ãŒãã¯äžèŠã§ãã
Reactãã€ã³ã¹ããŒã«ããå¿ èŠãããããŸãããã©ã³ã¿ã€ã ã«ãã³ãã«ãããŠãããåžžã«ææ°ã®ç¶æ ã«ä¿ãããŠããŸãã
åäžã®äŸåé¢ä¿ã§ã©ã³ã¿ã€ã ããããžã§ã¯ãã«è¿œå â Reactã¯å«ãŸããŠãããå¥éã€ã³ã¹ããŒã«äžèŠã
pnpm add @lazarv/react-server
ããã©ã«ããšã¯ã¹ããŒããããReactã³ã³ããŒãã³ããèšè¿°ããã®ãã¡ã€ã«ãã¢ããªã±ãŒã·ã§ã³å šäœã®ãšã³ããªãŒãã€ã³ãã
export default function App() {
return <h1>Hello World!</h1>;
}
Viteã«ããå³åº§ã®ãããã¢ãžã¥ãŒã«ãªãã¬ãŒã¹ã¡ã³ãã§éçºãµãŒããŒãèµ·åã
pnpm react-server ./App.jsx
ã€ã³ã¹ããŒã«ãå®å šã«ã¹ããã â npxã§ä»»æã®Reactãã¡ã€ã«ãçŽæ¥å®è¡ãå®éšããããã¿ã€ãã³ã°ã«æé©ã
npx @lazarv/react-server ./App.jsx
ãã¡ã€ã«ã·ã¹ãã ã«ãŒãã£ã³ã°ãã¬ã€ã¢ãŠããæ¬çªçšèšå®ãåããã¢ããªã±ãŒã·ã§ã³ãcreateããã±ãŒãžã§æ§ç¯ã
npx @lazarv/create-react-server
Viteã«ããå³åº§ã®HMRã§éçºãµãŒããŒãèµ·åã
react-server
æé©åãããæ¬çªãã«ããäœæãããã«ãã€ã³ã®å§çž®ãšã¯ã©ã¹ã¿ãŒã¢ãŒãã§é ä¿¡ã
react-server build react-server start
ãã®ä»ã®æ¹æ³
ããŸããŸãªéå§æ¹æ³
npx ã§ã€ã³ã¹ããŒã«ãå®å
šã«ã¹ãããããŠä»»æã®Reactãã¡ã€ã«ããã®å Žã§å®è¡ã§ããŸã â ãããã¿ã€ãã³ã°ãã¯ã€ãã¯ãã¢ã«æé©ã§ãããŸãã¯ãcreateããã±ãŒãžã䜿çšããŠããã¡ã€ã«ã·ã¹ãã ã«ãŒãã£ã³ã°ãã¬ã€ã¢ãŠããæ¬çªèšå®ãäºåã«çµã¿èŸŒãŸãããã«ãããžã§ã¯ããã¹ãã£ãã©ãŒã«ãã§ããŸãã
ã¹ãã£ãã©ãŒã«ãããããããžã§ã¯ãã«ã¯å®å
šãªéçºã¯ãŒã¯ãããŒãå«ãŸããŸãïŒå³åº§ã®HMRãåããéçºãµãŒããŒãæé©åãããæ¬çªãã«ãã¹ããããå§çž®ã»ãã£ãã·ã³ã°ã»ã¯ã©ã¹ã¿ãŒã¢ãŒããåããæ¬çªãµãŒã㌠â ãã¹ãŠ react-server CLIã§è¿œå èšå®ãªãã«å©çšå¯èœã§ãã
ã³ã¢å®è¡ã¢ãã«
ãµãŒããŒã³ã³ããŒãã³ããã¯ã©ã€ã¢ã³ãã€ã³ã¿ã©ã¯ãã£ããã£ããµãŒããŒé¢æ°ãã¹ããªãŒãã³ã° â ã¬ã³ããªã³ã°ã®åºç€ã
ããã©ã«ãã¬ã³ããªã³ã°
React Server Components
ãµãŒããŒã³ã³ããŒãã³ãã¯ãµãŒããŒäžã§å®è¡ãããasync/await ããµããŒãããã³ãŒãã¯ã¯ã©ã€ã¢ã³ãã«éãããŸãããããŒã¿ããŒã¹ããã¡ã€ã«ã·ã¹ãã ãå
éšAPIã«çŽæ¥ã¢ã¯ã»ã¹ã§ããŸã â å¥ã®ããã¯ãšã³ãã¯äžèŠã§ãã
ãµãŒããŒå°çšã³ã³ããŒãã³ãã«ã¯ãã€ãã¬ãŒã·ã§ã³ã³ã¹ãããããŸãããããé«éãªããŒãžèªã¿èŸŒã¿ãããå°ããªãã³ãã«ãããã·ã³ãã«ãªã¢ãŒããã¯ãã£ã
export default async function Dashboard() {
const stats = await fetch(
"https://api.example.com/stats",
{ headers: { Authorization: `Bearer ${API_KEY}` } }
).then((r) => r.json());
return (
<section>
<h1>Dashboard</h1>
<p>Total users: {stats.users}</p>
<p>Revenue: ${stats.revenue}</p>
</section>
);
}
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Current count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
ã€ã³ã¿ã©ã¯ãã£ããã£
ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ã
"use client" ã远å ããŠã€ã³ã¿ã©ã¯ãã£ããã£ãæå¹ã«ããŸããã³ã³ããŒãã³ãã¯ãŸããµãŒããŒã§ã¬ã³ããªã³ã°ããé«éãªåæè¡šç€ºãå®çŸãããã®åŸã¯ã©ã€ã¢ã³ãã§å®å
šãªããã¯ãšã€ãã³ããã³ãã©ã®ãµããŒãä»ãã§ãã€ãã¬ãŒããããŸãã
ãã€ãã¬ãŒã·ã§ã³ã³ã¹ãã¯ã€ã³ã¿ã©ã¯ãã£ããã£ãå®éã«å¿ èŠãªå Žæã§ã®ã¿çºçããŸãããã以å€ã¯ãã¹ãŠãµãŒããŒå°çšã®ãŸãŸã§ãã
ããŒã¿ãã¥ãŒããŒã·ã§ã³
ãµãŒããŒé¢æ°
"use server" ã§ä»»æã®async颿°ããµãŒããŒãµã€ãRPCãšã³ããã€ã³ãã«å€æããŸããããã°ã¬ãã·ããšã³ãã³ã¹ã¡ã³ãä»ãã®ãã©ãŒã ã¢ã¯ã·ã§ã³ãšããŠäœ¿çšå¯èœ â JavaScriptãªãã§ããã©ãŒã ãæ©èœããŸãã
APIã«ãŒãã®å®çŸ©ãfetchã©ãããŒãäžèŠã§ããã©ã³ã¿ã€ã ãã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ããšã©ãŒããŠã³ããªãåæ€èšŒãåŠçããŸãã
async function createPost(formData) {
"use server";
const title = formData.get("title");
const body = formData.get("body");
await db.posts.create({ title, body });
revalidate("/posts");
}
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" />
<textarea name="body" placeholder="Content" />
<button type="submit">Publish</button>
</form>
);
}
import { Suspense } from "react";
async function SlowData() {
const data = await fetchFromSlowAPI();
return <div>{data.result}</div>;
}
export default function Page() {
return (
<main>
<h1>Instant header</h1>
<p>This content appears immediately.</p>
<Suspense fallback={<p>Loading data...</p>}>
<SlowData />
</Suspense>
</main>
);
}
ããã©ãŒãã³ã¹
ã¹ããªãŒãã³ã°SSR
HTMLã¯å©çšå¯èœã«ãªããšããã«ã¯ã©ã€ã¢ã³ãã«ã¹ããªãŒãã³ã°ãããŸãã<Suspense> ãšçµã¿åãããããšã§ãããŒãžã®äžéšãããã°ã¬ãã·ãã«ã¬ã³ããªã³ã°ãã â ãŠãŒã¶ãŒã¯é
ãã»ã¯ã·ã§ã³ãããã¯ã°ã©ãŠã³ãã§èªã¿èŸŒãŸããéãããã«ã³ã³ãã³ãã確èªã§ããŸãã
ã¹ããªãŒãã³ã°ã«ããé«éãªTime-to-First-Byteããã€ãã¬ãŒã·ã§ã³å®äºåŸã«å®å šãªã€ã³ã¿ã©ã¯ãã£ãäœéšãèšå®ã¯äžèŠã§ãã
忣React
åäžããã»ã¹ãè¶ ããã³ã³ããŒãã³ã â ã©ã€ãã¹ããªãŒãã³ã°ãã¯ãŒã«ãŒã¹ã¬ããããã€ã¯ãããã³ããšã³ãã
ãªã¢ã«ã¿ã€ã
ã©ã€ãã³ã³ããŒãã³ã
WebSocketçµç±ã®asyncãžã§ãã¬ãŒã¿ã«ãããµãŒããŒããã¯ã©ã€ã¢ã³ããžã®ã¹ããªãŒãã³ã°ãåã³ã³ããŒãã³ãã€ã³ã¹ã¿ã³ã¹ãç¬èªã®æ°žç¶çãªãµãŒããŒãµã€ãå®è¡ã³ã³ããã¹ããæã¡ãŸãã
"use live" ã远å ããŠasyncãžã§ãã¬ãŒã¿ããšã¯ã¹ããŒãããã ããå yield ãæ°ããReactããªãŒãã¯ã©ã€ã¢ã³ãã«éä¿¡ããŸããããã·ã¥ããŒãããã£ãŒããã¢ãã¿ãªã³ã°ã«æé©ã§ãã
"use live";
export default async function* StockPrice({ symbol }) {
while (true) {
const price = await getPrice(symbol);
const trend = await getTrend(symbol);
yield (
<div>
<span className="text-2xl">${price}</span>
<span className={trend > 0 ? "green" : "red"}>
{trend > 0 ? "â²" : "âŒ"} {Math.abs(trend)}%
</span>
</div>
);
await new Promise((r) => setTimeout(r, 1000));
}
}
"use worker";
export async function processImage(buffer, options) {
// Runs in a Worker Thread â won't block
// the main server or the UI thread
const result = await sharp(buffer)
.resize(options.width, options.height)
.webp({ quality: options.quality })
.toBuffer();
return result;
}
䞊è¡åŠç
ã¯ãŒã«ãŒ
"use worker" ã§ããã¯ã°ã©ãŠã³ãã¹ã¬ããã«éãèšç®ããªãããŒãããµãŒããŒã§ã¯Worker Threadsããã©ãŠã¶ã§ã¯Web WorkersãšããŠåäœããŸãã
åŒæ°ãšæ»ãå€ã¯RSC Flightãããã³ã«ã§ã·ãªã¢ã©ã€ãºãããReactèŠçŽ ãçŽæ¥è¿ãããšãã§ããŸããå®å šãªHMRãµããŒãä»ãã
ã³ã³ããžã·ã§ã³
ãã€ã¯ãããã³ããšã³ã
ç¬ç«ããŠãããã€ãããRSCã¢ããªã±ãŒã·ã§ã³ãåæããŸããåãªã¢ãŒãã¢ããªã¯ãã¹ãããŒãžã«ã¹ããªãŒãã³ã°ãããŸããisolate ã§Shadow DOMã«ãã»ã«åãæå¹ã«ããã¹ã¿ã€ã«ã®ãªãŒã¯ãé²ããŸãã
ã€ã³ããŒããããã§äŸåé¢ä¿ãå
±æãdefer ã§ãã³ããããã³ã°ããŒãã£ã³ã°ãå®çŸããšããžé
ä¿¡çšã«éçãã«ããšããŠãšã¯ã¹ããŒãå¯èœã
import { RemoteComponent }
from "@lazarv/react-server/remote";
export default function App() {
return (
<main>
<h1>Host Application</h1>
<RemoteComponent
src="https://team-a.example.com/header"
/>
<RemoteComponent
src="https://team-b.example.com/widget"
isolate
defer
/>
</main>
);
}
ãã©ãããã©ãŒã æ©èœ
ã©ã³ã¿ã€ã äžã«æ§ç¯ãããã¢ããªã±ãŒã·ã§ã³æ©èœ â ã«ãŒãã£ã³ã°ãããã²ãŒã·ã§ã³ããã£ãã·ã³ã°ãã³ã³ãã³ãåæã
ã«ãŒãã£ã³ã°
å®å šåä»ããã¡ã€ã«ã·ã¹ãã ã«ãŒã¿ãŒ
ãšã³ããªãŒãã€ã³ããçç¥ãããšãã©ã³ã¿ã€ã ããã¡ã€ã«æ§é ã«åºã¥ããŠã«ãŒãã£ã³ã°ããŸããããŒãžãã¬ã€ã¢ãŠãããã©ã¬ã«ã¢ãŠãã¬ãããAPIã«ãŒããããã«ãŠã§ã¢ããšã©ãŒããŠã³ããªãããŒãã£ã³ã°ã¹ããŒã â ãã¹ãŠãèŠçŽé§åã§ãã
åçã»ã°ã¡ã³ãããã£ãããªãŒã«ã«ãŒããã«ãŒãã°ã«ãŒããèªåçæãããTypeScriptåããã«ãã€ã³ã§æäŸãããŸãããã¹ãŠã®Linkãnavigateãã«ãŒããã©ã¡ãŒã¿ãåãã§ãã¯ãã â IDEãªãŒãã³ã³ããªãŒããšãŒãã³ã³ãã£ã°ã§å©çšå¯èœããŸãã¯ãå®å
šãªå¶åŸ¡ã®ããã«ã¹ããŒãããªããŒã·ã§ã³ä»ãã®ããã°ã©ã çãªcreateRoute APIã䜿çšã§ããŸãã
app/
âââ layout.jsx # ã«ãŒãã¬ã€ã¢ãŠã
âââ page.jsx # /
âââ about.jsx # /about
âââ posts/
â âââ page.jsx # /posts
â âââ [id].jsx # /posts/:id
â âââ [...slug].jsx # /posts/*
âââ (auth)/
â âââ login.jsx # /login (ã°ã«ãŒãå)
âââ @sidebar/
â âââ page.jsx # ãã©ã¬ã«ã¢ãŠãã¬ãã
âââ loading.jsx # Suspenseãã©ãŒã«ããã¯
âââ error.jsx # ãšã©ãŒããŠã³ããª
âââ (i18n).middleware.mjs # ããã«ãŠã§ã¢
âââ GET.api.server.mjs # GET /api ãšã³ããã€ã³ã
import { Link } from "@lazarv/react-server/navigation";
export default function Nav() {
return (
<nav>
<Link to="/" prefetch>Home</Link>
<Link to="/about" prefetch>About</Link>
<Link to="/posts" prefetch="viewport">
Posts
</Link>
</nav>
);
}
ããã²ãŒã·ã§ã³
ã¯ã©ã€ã¢ã³ããµã€ãããã²ãŒã·ã§ã³
LinkãFormãRefreshãReactServerComponent ã«ããSPAã©ã€ã¯ãªãã©ã³ãžã·ã§ã³ããããŒãŸãã¯ãã¥ãŒããŒãé²å
¥æã«ããªãã§ããããšã©ãŒæã«ããŒã«ããã¯ãç¹å®ã®ã¢ãŠãã¬ããã«æŽæ°ãã¹ã³ãŒãã
useClient() ã«ããããã°ã©ããã£ãã¯ããã²ãŒã·ã§ã³ã¯ã¹ããªãŒãã³ã°ã¢ãŒããã¯ãã£ãšçµ±åãããŠããã空çœç»é¢ã®ãã©ã³ãžã·ã§ã³ã¯ãããŸããã
ãã£ãã·ã³ã°
ãã£ãã·ã³ã°ãšåæ€èšŒ
ã¬ã¹ãã³ã¹ãããŒã¿ãã³ã³ããŒãã³ãã®åã¬ãã«ã§ã®å€å±€ãã£ãã·ã³ã°ããã©ã¬ãã«ãã©ã€ããåããUnstorageäžã«æ§ç¯ã
revalidate() ãš invalidate() ã§ãªã³ããã³ãã«ç¡å¹åã"use cache" ãã£ã¬ã¯ãã£ãã§å®£èšçãªTTLãšã¿ã°ããŒã¹ã®ç¡å¹åãå®çŸã
import { useResponseCache, useCache } from "@lazarv/react-server";
ã¬ã³ããªã³ã°æžã¿ã¬ã¹ãã³ã¹å šäœã30ç§éãã£ãã·ã¥ â åŸç¶ã®ãªã¯ãšã¹ãã¯ã¬ã³ããªã³ã°ãå®å šã«ã¹ãããã
useResponseCache(30000);
è€åããŒã§åå¥ã®ããŒã¿ãã§ããããã£ãã·ã¥ãåãšã³ããªã¯ç¬èªã®TTLãæã¡ãåå¥ã«ç¡å¹åå¯èœã
const post = await useCache(
["posts", id],
() => fetchPost(id),
60000
);
"use cache" ãã£ã¬ã¯ãã£ãããã¡ã€ã«ã®å
é ã«é
眮ããŠã宣èšçãªTTLãšã¿ã°ããŒã¹ã®ç¡å¹åãå®çŸã
"use cache; ttl=60; tags=posts"
ããŒãžã·ã§ã«ãšéçã³ã³ããŒãã³ãã¯ãã«ãæã«ããªã¬ã³ããªã³ã°ããããšããžããå³åº§ã«é ä¿¡ã
export default function ProductPage({ params }) {
return (
<main>
<StaticHeader />
<ProductInfo id={params.id} />
<DynamicReviews id={params.id} />
</main>
);
}
"use dynamic" ã§ããŒã¯ãããåçã³ã³ããŒãã³ãã¯ãéçã·ã§ã«ãæ¢ã«è¡šç€ºãããŠããéã«ãªã¯ãšã¹ãæã«ã¹ããªãŒãã³ã°ã€ã³ã
"use dynamic";
export default async function DynamicReviews({ id }) {
const reviews = await fetchReviews(id);
return (
<section>
{reviews.map((r) => <Review key={r.id} {...r} />)}
</section>
);
}
ãã€ããªããã¬ã³ããªã³ã°
éšåçããªã¬ã³ããªã³ã°
ã³ã³ããŒãã³ãã¬ãã«ã§éçã³ã³ãã³ããšåçã³ã³ãã³ããæ··åšãéçã·ã§ã«ã¯ãã«ãæã«ããªã¬ã³ããªã³ã°ãããåçéšåã¯ãªã¯ãšã¹ãæã«ã¹ããªãŒãã³ã°ã€ã³ã
"use dynamic" ãŸã㯠"use static" ã§ã³ã³ããŒãã³ããããŒã¯ãéçéšåã¯ãšããžããå³åº§ã«é
ä¿¡ãããåçããŒã¿ã¯èªã¿èŸŒãŸããŸãã
ã³ã³ãã³ã
Markdown & MDX
.md ãš .mdx ãã¡ã€ã«ãå®å
šãªremark/rehypeãã©ã°ã€ã³ãµããŒããšã«ã¹ã¿ã ã³ã³ããŒãã³ããªãŒããŒã©ã€ãä»ãã§ããŒãžãšããŠçŽæ¥äœ¿çšã
Markdownå ã«Reactã³ã³ããŒãã³ããåã蟌ã¿å¯èœãã«ã¹ã¿ã ã³ã³ããŒãã³ããäžåºŠå®çŸ©ããã°ããã¹ãŠã®ã¬ã³ããªã³ã°ã«ã°ããŒãã«ã«é©çšãããŸãã
mdx-components.jsx ã§ã«ã¹ã¿ã ã³ã³ããŒãã³ããªãŒããŒã©ã€ããäžåºŠå®çŸ© â ãã¹ãŠã®MDXã³ã³ãã³ãã«ã°ããŒãã«ã«é©çšã
export function useMDXComponents() {
return {
h1: (props) => (
<h1 className="text-4xl font-bold" {...props} />
),
code: ({ children, className }) => (
<SyntaxHighlighter language={className}>
{children}
</SyntaxHighlighter>
),
a: (props) => (
<Link className="text-blue-600" {...props} />
),
};
}
import { createServer, createTool }
from "@lazarv/react-server/mcp";
import { z } from "zod";
const search = createTool(
"search",
"Search the knowledge base",
{
query: z.string(),
limit: z.number().default(10),
},
async ({ query, limit }) => {
const results = await db.search(query, limit);
return results.map((r) => r.title).join("\n");
}
);
export default createServer({ tools: [search] });
AIçµ±å
MCPãµãŒããŒ
ããŒã«ããªãœãŒã¹ãããã³ãããã¹ããªãŒãã³ã°HTTPãšã³ããã€ã³ããšããŠæäŸããModel Context ProtocolãµãŒããŒãæ§ç¯ã
Zodã¹ããŒãã§ããŒã«ãå®çŸ©ãAIãšãŒãžã§ã³ããæšæºåããããããã³ã«ãéããŠããããæ€åºãåŒã³åºããŸããåããŒã«ã¯MCPã¯ã©ã€ã¢ã³ãäºæã®HTTPãšã³ããã€ã³ãã«ãªããŸãã
ã©ãã«ã§ããããã€
æ¬çªãã«ãã¯ã³ãã³ã1ã€ããã¹ãŠã®äž»èŠãã©ãããã©ãŒã åãã®ãã«ãã€ã³ã¢ããã¿ãŒ â ã«ã¹ã¿ã ãå¯èœã
ã¢ããã¿ãŒ
ãã©ãããã©ãŒã ã¢ããã¿ãŒ
VercelãNetlifyãCloudflareã«åäžã®èšå®ãªãã·ã§ã³ã§ãããã€ãåã¢ããã¿ãŒããã©ãããã©ãŒã åºæã®åºå â ãšããžé¢æ°ããµãŒããŒã¬ã¹é¢æ°ãWorkers â ãèªåçã«åŠçããŸãã
ã«ã¹ã¿ã ãå¿
èŠïŒã¢ããã¿ãŒã³ã¢ããã±ãŒãžã® createAdapter ã§ç¬èªã®ã¢ããã¿ãŒãæ§ç¯ã§ããŸããã¢ããã¿ãŒAPIã¯ãã«ãåºåãå®å
šã«å¶åŸ¡ã§ããŸãã
åäžã®ã³ãã³ãã§ã¢ããªããã«ããããã©ãããã©ãŒã åºæã®ãããã€åºåãçæã
react-server build --deploy
èšå®ãã¡ã€ã«ã§ã¢ããã¿ãŒãæå® â ãã«ãããšããžé¢æ°ããµãŒããŒã¬ã¹é¢æ°ãWorkersãèªåçã«åºåã
{ "adapter": "vercel" }
ãŸãã¯CLIåŒæ°ã§çŽæ¥ã¢ããã¿ãŒãæå® â èšå®ãã¡ã€ã«äžèŠã
react-server build --adapter vercel --deploy
ããã«ãŠã§ã¢ã¢ãŒãã§æ¢åã®ExpressãNestJSããŸãã¯connectäºæãµãŒããŒã«ã©ã³ã¿ã€ã ãçµã¿èŸŒã¿ã
import { reactServer } from
"@lazarv/react-server/node";
const { middlewares } = await reactServer();
app.use(middlewares);
ã¯ã©ã¹ã¿ãŒã¢ãŒãã§ãã¹ãŠã®CPUã³ã¢ã«æ°Žå¹³ã¹ã±ãŒãªã³ã°ããæå€§ã¹ã«ãŒããããå®çŸã
REACT_SERVER_CLUSTER=8 react-server start
äžè²«ããæ¬çªãããã€ã®ããã«èšå®ãã¡ã€ã«ã§ã¯ã©ã¹ã¿ãŒãµã€ãºãæ§æã
{ "cluster": 8 }
ã©ã³ã¿ã€ã
ãã«ãã©ã³ã¿ã€ã ïŒæ¬çªç°å¢
Node.js ⥠20.10ãBun ⥠1.2.9ããŸãã¯Deno ⥠2.0 ã§åäœ â @lazarv/react-serverãã©ã³ã¿ã€ã ãèªåæ€åºãããã«ãŠã§ã¢ã¢ãŒãã§æ¢åã®ãµãŒããŒã«çµã¿èŸŒã¿å¯èœã
ã¯ã©ã¹ã¿ãŒã¢ãŒãã§ãã¹ãŠã®CPUã³ã¢ã«ã¹ã±ãŒã«ãéçãšã¯ã¹ããŒãã¯åçã«ãŒããã©ã¡ãŒã¿ãå§çž®ããã£ãã·ã¥ããããŒãå«ãå®å šãªéçãµã€ããçæãHTTPSããœãŒã¹ããããgzip/brotliå§çž®ããã«ãã€ã³ã§æèŒã
æ§ç¯ãå§ããŸããã
ãµãŒããŒãµã€ãã¬ã³ããªã³ã°ãã¹ããªãŒãã³ã°ããšããžãããã€ã§ææ°ã®Reactã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããéçºè ã³ãã¥ããã£ã«åå ããŸãããããªãŒãã³ãœãŒã¹ãMITã©ã€ã»ã³ã¹ãç©æ¥µçã«ã¡ã³ããã³ã¹ãããŠããŸãã