diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 949c5773..edd7cf2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,13 +58,10 @@ jobs: os: [ubuntu-latest] node_version: [20, 22, 24] include: - # Active LTS + other OS - os: macos-latest node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test dev base ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -87,7 +84,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-dev-base-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-dev-base-${{ matrix.os }}-node${{ matrix.node_version }} path: test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -98,7 +95,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: test/test-results/junit.xml - flags: dev-base-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: dev-base-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-build-start-base: @@ -111,13 +108,10 @@ jobs: os: [ubuntu-latest] node_version: [20, 22, 24] include: - # Active LTS + other OS - os: macos-latest node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test prod base ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -140,7 +134,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-prod-base-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-prod-base-${{ matrix.os }}-node${{ matrix.node_version }} path: test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -151,7 +145,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: test/test-results/junit.xml - flags: prod-base-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: prod-base-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-build-start-base-edge: @@ -164,13 +158,10 @@ jobs: os: [ubuntu-latest] node_version: [20, 22, 24] include: - # Active LTS + other OS - os: macos-latest node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test prod base (edge+node) ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -195,7 +186,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-prod-base-edge-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-prod-base-edge-${{ matrix.os }}-node${{ matrix.node_version }} path: test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -206,7 +197,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: test/test-results/junit.xml - flags: prod-base-edge-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: prod-base-edge-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-build-start-base-edge-entry: @@ -219,13 +210,10 @@ jobs: os: [ubuntu-latest] node_version: [20, 22, 24] include: - # Active LTS + other OS - os: macos-latest node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test prod base (edge entry) ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -250,7 +238,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-prod-base-edge-entry-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-prod-base-edge-entry-${{ matrix.os }}-node${{ matrix.node_version }} path: test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -261,7 +249,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: test/test-results/junit.xml - flags: prod-base-edge-entry-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: prod-base-edge-entry-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-dev-apps: @@ -274,13 +262,10 @@ jobs: os: [ubuntu-latest] node_version: [20, 22, 24] include: - # Active LTS + other OS - os: macos-latest node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test dev apps ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -303,7 +288,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-dev-apps-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-dev-apps-${{ matrix.os }}-node${{ matrix.node_version }} path: test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -314,7 +299,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: test/test-results/junit.xml - flags: dev-apps-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: dev-apps-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-build-start-apps: @@ -327,13 +312,10 @@ jobs: os: [ubuntu-latest] node_version: [20, 22, 24] include: - # Active LTS + other OS - os: macos-latest node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test prod apps ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -356,7 +338,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-prod-apps-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-prod-apps-${{ matrix.os }}-node${{ matrix.node_version }} path: test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -367,7 +349,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: test/test-results/junit.xml - flags: prod-apps-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: prod-apps-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-build-start-apps-edge: @@ -380,13 +362,10 @@ jobs: os: [ubuntu-latest] node_version: [20, 22, 24] include: - # Active LTS + other OS - os: macos-latest node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test prod apps (edge+node) ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -411,7 +390,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-prod-apps-edge-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-prod-apps-edge-${{ matrix.os }}-node${{ matrix.node_version }} path: test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -422,7 +401,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: test/test-results/junit.xml - flags: prod-apps-edge-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: prod-apps-edge-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-build-start-apps-edge-entry: @@ -435,13 +414,10 @@ jobs: os: [ubuntu-latest] node_version: [20, 22, 24] include: - # Active LTS + other OS - os: macos-latest node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test prod apps (edge entry) ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -466,7 +442,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-prod-apps-edge-entry-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-prod-apps-edge-entry-${{ matrix.os }}-node${{ matrix.node_version }} path: test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -477,7 +453,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: test/test-results/junit.xml - flags: prod-apps-edge-entry-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: prod-apps-edge-entry-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-rsc: @@ -494,8 +470,6 @@ jobs: node_version: 24 - os: windows-latest node_version: 24 - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test rsc ๐Ÿงช node.js v${{ matrix.node_version }} on ${{ matrix.os }}" @@ -543,7 +517,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-rsc-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + name: test-results-rsc-${{ matrix.os }}-node${{ matrix.node_version }} path: packages/rsc/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -554,7 +528,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: packages/rsc/test-results/junit.xml - flags: rsc-${{ matrix.os }}-node${{ matrix.node_version }}-shard${{ matrix.shard }} + flags: rsc-${{ matrix.os }}-node${{ matrix.node_version }} report_type: test_results test-results: diff --git a/.github/workflows/create-react-server.yml b/.github/workflows/create-react-server.yml index b6c19ba8..3ff4b5c5 100644 --- a/.github/workflows/create-react-server.yml +++ b/.github/workflows/create-react-server.yml @@ -59,8 +59,6 @@ jobs: pkg_mgr: npm - runtime: deno pkg_mgr: pnpm - shard: [1, 2, 3, 4, 5, 6] - max-parallel: 2 fail-fast: false name: "Test create ${{ matrix.runtime }}/${{ matrix.pkg_mgr }} ๐Ÿงช" @@ -80,7 +78,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: test-results-create-${{ matrix.runtime }}-${{ matrix.pkg_mgr }}-shard${{ matrix.shard }} + name: test-results-create-${{ matrix.runtime }}-${{ matrix.pkg_mgr }} path: packages/create-react-server/test/test-results/junit.xml if-no-files-found: ignore retention-days: 7 @@ -91,7 +89,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: packages/create-react-server/test/test-results/junit.xml - flags: create-${{ matrix.runtime }}-${{ matrix.pkg_mgr }}-shard${{ matrix.shard }} + flags: create-${{ matrix.runtime }}-${{ matrix.pkg_mgr }} report_type: test_results test-results: diff --git a/docs/public/banner.png b/docs/public/banner.png new file mode 100644 index 00000000..0f884b11 Binary files /dev/null and b/docs/public/banner.png differ diff --git a/docs/public/banner.svg b/docs/public/banner.svg new file mode 100644 index 00000000..f629f643 --- /dev/null +++ b/docs/public/banner.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @lazarv + + + react-server + + + Run React anywhere + diff --git a/docs/public/devtools-cache-dark.webp b/docs/public/devtools-cache-dark.webp new file mode 100644 index 00000000..74527cdc Binary files /dev/null and b/docs/public/devtools-cache-dark.webp differ diff --git a/docs/public/devtools-cache-light.webp b/docs/public/devtools-cache-light.webp new file mode 100644 index 00000000..d05d8fbe Binary files /dev/null and b/docs/public/devtools-cache-light.webp differ diff --git a/docs/public/devtools-float-mode-dark.webp b/docs/public/devtools-float-mode-dark.webp new file mode 100644 index 00000000..6f94c26c Binary files /dev/null and b/docs/public/devtools-float-mode-dark.webp differ diff --git a/docs/public/devtools-float-mode-light.webp b/docs/public/devtools-float-mode-light.webp new file mode 100644 index 00000000..60341c72 Binary files /dev/null and b/docs/public/devtools-float-mode-light.webp differ diff --git a/docs/public/devtools-highlighting-dark.webp b/docs/public/devtools-highlighting-dark.webp new file mode 100644 index 00000000..768233f7 Binary files /dev/null and b/docs/public/devtools-highlighting-dark.webp differ diff --git a/docs/public/devtools-highlighting-light.webp b/docs/public/devtools-highlighting-light.webp new file mode 100644 index 00000000..c74c854d Binary files /dev/null and b/docs/public/devtools-highlighting-light.webp differ diff --git a/docs/public/devtools-live-dark.webp b/docs/public/devtools-live-dark.webp new file mode 100644 index 00000000..2d3a0fbe Binary files /dev/null and b/docs/public/devtools-live-dark.webp differ diff --git a/docs/public/devtools-live-light.webp b/docs/public/devtools-live-light.webp new file mode 100644 index 00000000..60b0f160 Binary files /dev/null and b/docs/public/devtools-live-light.webp differ diff --git a/docs/public/devtools-logs-dark.webp b/docs/public/devtools-logs-dark.webp new file mode 100644 index 00000000..a371c129 Binary files /dev/null and b/docs/public/devtools-logs-dark.webp differ diff --git a/docs/public/devtools-logs-light.webp b/docs/public/devtools-logs-light.webp new file mode 100644 index 00000000..02dc90db Binary files /dev/null and b/docs/public/devtools-logs-light.webp differ diff --git a/docs/public/devtools-outlets-dark.webp b/docs/public/devtools-outlets-dark.webp new file mode 100644 index 00000000..dd9d2f12 Binary files /dev/null and b/docs/public/devtools-outlets-dark.webp differ diff --git a/docs/public/devtools-outlets-light.webp b/docs/public/devtools-outlets-light.webp new file mode 100644 index 00000000..cc56dcba Binary files /dev/null and b/docs/public/devtools-outlets-light.webp differ diff --git a/docs/public/devtools-overview-dark.webp b/docs/public/devtools-overview-dark.webp new file mode 100644 index 00000000..91a7d103 Binary files /dev/null and b/docs/public/devtools-overview-dark.webp differ diff --git a/docs/public/devtools-overview-light.webp b/docs/public/devtools-overview-light.webp new file mode 100644 index 00000000..4d3731ea Binary files /dev/null and b/docs/public/devtools-overview-light.webp differ diff --git a/docs/public/devtools-payload-dark.webp b/docs/public/devtools-payload-dark.webp new file mode 100644 index 00000000..bee6b4bb Binary files /dev/null and b/docs/public/devtools-payload-dark.webp differ diff --git a/docs/public/devtools-payload-light.webp b/docs/public/devtools-payload-light.webp new file mode 100644 index 00000000..41a07ed8 Binary files /dev/null and b/docs/public/devtools-payload-light.webp differ diff --git a/docs/public/devtools-remotes-dark.webp b/docs/public/devtools-remotes-dark.webp new file mode 100644 index 00000000..f81f6d94 Binary files /dev/null and b/docs/public/devtools-remotes-dark.webp differ diff --git a/docs/public/devtools-remotes-light.webp b/docs/public/devtools-remotes-light.webp new file mode 100644 index 00000000..b9ab5ec1 Binary files /dev/null and b/docs/public/devtools-remotes-light.webp differ diff --git a/docs/public/devtools-routes-dark.webp b/docs/public/devtools-routes-dark.webp new file mode 100644 index 00000000..5a1bf291 Binary files /dev/null and b/docs/public/devtools-routes-dark.webp differ diff --git a/docs/public/devtools-routes-light.webp b/docs/public/devtools-routes-light.webp new file mode 100644 index 00000000..7903075e Binary files /dev/null and b/docs/public/devtools-routes-light.webp differ diff --git a/docs/public/devtools-status-dark.webp b/docs/public/devtools-status-dark.webp new file mode 100644 index 00000000..1f364d96 Binary files /dev/null and b/docs/public/devtools-status-dark.webp differ diff --git a/docs/public/devtools-status-light.webp b/docs/public/devtools-status-light.webp new file mode 100644 index 00000000..ed8eb36d Binary files /dev/null and b/docs/public/devtools-status-light.webp differ diff --git a/docs/public/devtools-workers-dark.webp b/docs/public/devtools-workers-dark.webp new file mode 100644 index 00000000..c7dc4a9a Binary files /dev/null and b/docs/public/devtools-workers-dark.webp differ diff --git a/docs/public/devtools-workers-light.webp b/docs/public/devtools-workers-light.webp new file mode 100644 index 00000000..67c91289 Binary files /dev/null and b/docs/public/devtools-workers-light.webp differ diff --git a/docs/public/opengraph.jpg b/docs/public/opengraph.jpg index ca33f92e..5baa6ec1 100644 Binary files a/docs/public/opengraph.jpg and b/docs/public/opengraph.jpg differ diff --git a/docs/src/components/DevToolsIcon.jsx b/docs/src/components/DevToolsIcon.jsx new file mode 100644 index 00000000..c8ad6bfb --- /dev/null +++ b/docs/src/components/DevToolsIcon.jsx @@ -0,0 +1,211 @@ +// Inline SVG icons matching the DevTools panel UI, sized for use in prose text. +// Each icon is a 16ร—16 inline SVG with currentColor so it inherits text color. + +const style = { + display: "inline-block", + verticalAlign: "middle", + position: "relative", + top: "-1px", +}; + +export function EyeIcon() { + return ( + + + + ); +} + +export function RefreshIcon() { + return ( + + โ†ป + + ); +} + +export function ExternalLinkIcon() { + return ( + + + + ); +} + +export function ArrowRightIcon() { + return ( + + + + ); +} + +export function SunIcon() { + return ( + + + + + ); +} + +export function MoonIcon() { + return ( + + + + + ); +} + +export function CloseIcon() { + return ( + + โœ• + + ); +} + +export function OverflowIcon() { + return ( + + ยป + + ); +} + +export function DockBottomIcon() { + return ( + + + + + ); +} + +export function DockLeftIcon() { + return ( + + + + + ); +} + +export function DockRightIcon() { + return ( + + + + + ); +} + +export function DockFloatIcon() { + return ( + + + + + ); +} diff --git a/docs/src/components/ThemedImage.jsx b/docs/src/components/ThemedImage.jsx new file mode 100644 index 00000000..3745d0a6 --- /dev/null +++ b/docs/src/components/ThemedImage.jsx @@ -0,0 +1,196 @@ +"use client"; + +import { useState, useCallback, useEffect, useRef } from "react"; +import { createPortal } from "react-dom"; + +/** + * Renders a theme-aware image that swaps between light and dark variants. + * Clicking the image opens a lightbox with a FLIP animation โ€” the image + * smoothly transitions from its inline position/size to centered in the + * viewport, and reverses on close. + * + * Usage in MDX: + * + */ +export default function ThemedImage({ light, dark, alt }) { + const [state, setState] = useState("closed"); // closed | opening | open | closing + const [sourceRect, setSourceRect] = useState(null); + const lightRef = useRef(null); + const darkRef = useRef(null); + + // Get the currently visible inline image element + const getVisibleRef = useCallback(() => { + // Check the parent button's computed display since Tailwind dark: classes are on the wrapper + const lightBtn = lightRef.current?.parentElement; + const darkBtn = darkRef.current?.parentElement; + if (lightBtn && getComputedStyle(lightBtn).display !== "none") { + return lightRef.current; + } + if (darkBtn && getComputedStyle(darkBtn).display !== "none") { + return darkRef.current; + } + return lightRef.current; + }, []); + + // Get the currently visible image src + const getVisibleSrc = useCallback(() => { + const el = getVisibleRef(); + return el === darkRef.current ? dark : light; + }, [getVisibleRef, light, dark]); + + const handleOpen = useCallback(() => { + const el = getVisibleRef(); + if (!el) return; + const rect = el.getBoundingClientRect(); + setSourceRect({ + top: rect.top, + left: rect.left, + width: rect.width, + height: rect.height, + }); + setState("opening"); + // Trigger the "open" state on next frame so CSS transition kicks in + requestAnimationFrame(() => { + requestAnimationFrame(() => setState("open")); + }); + }, [getVisibleRef]); + + const handleClose = useCallback(() => { + // Re-capture the inline image position (may have scrolled) + const el = getVisibleRef(); + if (el) { + const rect = el.getBoundingClientRect(); + setSourceRect({ + top: rect.top, + left: rect.left, + width: rect.width, + height: rect.height, + }); + } + setState("closing"); + }, [getVisibleRef]); + + // After close animation, unmount + const handleTransitionEnd = useCallback(() => { + if (state === "closing") { + setState("closed"); + setSourceRect(null); + } + }, [state]); + + // Close on Escape key + useEffect(() => { + if (state === "closed") return; + const onKeyDown = (e) => { + if (e.key === "Escape") handleClose(); + }; + document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); + }, [state, handleClose]); + + // Prevent body scroll when lightbox is active + useEffect(() => { + if (state !== "closed") { + document.body.style.overflow = "hidden"; + return () => { + document.body.style.overflow = ""; + }; + } + }, [state]); + + // Compute the target (centered) rect for the lightbox image + const getTargetStyle = useCallback(() => { + if (!sourceRect || sourceRect.width === 0 || sourceRect.height === 0) + return null; + const vw = window.innerWidth * 0.9; + const vh = window.innerHeight * 0.9; + const aspect = sourceRect.width / sourceRect.height; + let w, h; + if (vw / vh > aspect) { + h = vh; + w = h * aspect; + } else { + w = vw; + h = w / aspect; + } + return { + top: (window.innerHeight - h) / 2, + left: (window.innerWidth - w) / 2, + width: w, + height: h, + }; + }, [sourceRect]); + + const isActive = state !== "closed"; + const isExpanded = state === "open"; + + // The rect to apply: source when opening/closing, target when open + const currentStyle = isExpanded ? getTargetStyle() : sourceRect; + + return ( + <> + {/* Inline images with vertical margin and cursor hint */} + + + + {/* Lightbox โ€” portalled to body to escape stacking contexts */} + {isActive && + currentStyle && + createPortal( + , + document.body + )} + + ); +} diff --git a/docs/src/pages/en/(pages)/features/cli.mdx b/docs/src/pages/en/(pages)/features/cli.mdx index ecd99ec1..04a8707b 100644 --- a/docs/src/pages/en/(pages)/features/cli.mdx +++ b/docs/src/pages/en/(pages)/features/cli.mdx @@ -106,6 +106,22 @@ Watch for config changes. Default is `true`. To disable watching for config chan Clear screen on server start. Default is `false`. If you want to start with a clean terminal screen. + +### --devtools + + +Enable the built-in react-server DevTools. Default is `false`. + +When enabled, a DevTools panel is injected into your application during development. It provides real-time inspection of your app's internals including RSC payload, cache entries, routes, outlets, remote components, live components, workers, and server logs. + +You can also enable DevTools via your `react-server.config.mjs`: + +```js +export default { + devtools: true, +}; +``` + ### --no-color diff --git a/docs/src/pages/en/(pages)/features/comparison.mdx b/docs/src/pages/en/(pages)/features/comparison.mdx index 0c137f6d..f7ec0cac 100644 --- a/docs/src/pages/en/(pages)/features/comparison.mdx +++ b/docs/src/pages/en/(pages)/features/comparison.mdx @@ -157,7 +157,7 @@ This comparison summarizes the feature set described in the documentation. It fo | MDX Pages | โœ… | ๐Ÿ”ถ | ๐Ÿ›‘ | ๐Ÿ›‘ | ๐Ÿ›‘ | | Virtual Routes Module | โœ… | ๐Ÿ›‘ | ๐ŸŸก | ๐ŸŸก | ๐Ÿ›‘ | | Route-scoped Loading / Error / Fallback Files | โœ… | โœ… | ๐Ÿ›‘ | ๐Ÿ›‘ | ๐Ÿ›‘ | -| Route Devtools | ๐Ÿ›‘ | ๐Ÿ›‘ | โœ… | ๐ŸŸก | ๐Ÿ›‘ | +| Devtools | โœ… | ๐Ÿ›‘ | โœ…
*router only* | ๐ŸŸก | ๐Ÿ›‘ | | Route Masking | ๐Ÿ›‘ | ๐Ÿ›‘ | โœ… | ๐Ÿ›‘ | ๐Ÿ›‘ | | Route-level Typed Dependencies | โœ…
*Typesafe resources* | ๐Ÿ›‘ | โœ…
*Typesafe route context + loaders* | ๐Ÿ›‘ | ๐Ÿ›‘ | | Route Mount / Unmount Events | ๐Ÿ›‘ | ๐Ÿ›‘ | โœ… | ๐Ÿ›‘ | ๐Ÿ›‘ | @@ -175,6 +175,6 @@ This comparison summarizes the feature set described in the documentation. It fo - **Schema-agnostic validation**: Route params and search params can be validated with any schema library (Zod, ArkType, Valibot) or lightweight parse functions โ€” the runtime detects the validation strategy automatically. - **RSC + typed resources**: `@lazarv/react-server` offers two complementary data-fetching approaches: React Server Components with `async/await` (RSC as loaders), and **typed resources** โ€” schema-validated, reference-identified data descriptors with `.use()` (suspense), `.query()` (imperative), `.prefetch()`, and `.invalidate()`. Resources use `"use cache"` as the caching runtime โ€” no custom cache layer, no SWR boilerplate. Route-resource bindings enable parallel prefetching on navigation. - **Route-level dependencies**: TanStack Router exposes route context as a first-class typed primitive for passing dependencies (auth, DB clients, etc.) down the route tree. `@lazarv/react-server` models the same problem space through typed resources, request context, and native modules โ€” there is no separate route-context bag because resources already carry schema-validated, route-scoped data with full type safety. -- **No devtools yet**: Route devtools are planned. The tradeoff today is that the typed route system provides compile-time safety that catches most routing errors before they reach the browser. +- **Built-in devtools**: `@lazarv/react-server` ships with integrated devtools (`--devtools`) that go beyond route inspection โ€” they cover RSC payload, cache entries, routes, outlets, remote components, live components, workers, and server logs in a single panel. TanStack Router provides excellent router-focused devtools, but `@lazarv/react-server`'s devtools cover the full server component runtime. - **Outlets vs. parallel routes**: `@lazarv/react-server` uses named outlets (`@sidebar`, `@content`) rendered as typed props to layouts. This is functionally similar to Next.js parallel routes but with stronger typing โ€” each outlet is a branded React element that prevents accidentally swapping outlets. - **Waku**: Waku is another RSC-native framework built on Vite, but takes a deliberately minimal approach. It provides basic file-based routing with layouts and RSC support, but lacks the typed routing system, search param handling, scroll restoration, middleware, and most advanced routing features. Waku's `Link` component supports basic `scroll` and `prefetchOnEnter`/`prefetchOnView` props, but most of its router API is still marked as `unstable_`. Waku is a good choice for simple RSC applications that don't need advanced routing. diff --git a/docs/src/pages/en/(pages)/features/devtools.mdx b/docs/src/pages/en/(pages)/features/devtools.mdx new file mode 100644 index 00000000..0e634bb1 --- /dev/null +++ b/docs/src/pages/en/(pages)/features/devtools.mdx @@ -0,0 +1,399 @@ +--- +title: DevTools +category: Features +order: 17 +--- + +import Link from "../../../../components/Link.jsx"; +import { EyeIcon, RefreshIcon, ExternalLinkIcon, ArrowRightIcon, SunIcon, MoonIcon, CloseIcon, OverflowIcon, DockBottomIcon, DockLeftIcon, DockRightIcon, DockFloatIcon } from "../../../../components/DevToolsIcon.jsx"; +import ThemedImage from "../../../../components/ThemedImage.jsx"; + +# DevTools + +The built-in DevTools panel lets you inspect your running application during development. It covers server process health, RSC payload contents, cache behavior, the route tree, outlet layout, remote and live components, worker threads, and server logs โ€” all without leaving the browser. + +{/* Screenshot: the DevTools panel docked to the bottom of the browser, showing the Status tab with CPU, memory, and process gauges. The app content is visible above the panel. The toolbar shows the react-server logo, version, dock mode buttons (bottom/left/right/float), a dark mode toggle, and a close button. */} + + + +## Enabling DevTools + + +Pass `--devtools` when starting the development server: + +```sh +pnpm react-server ./App.jsx --devtools +``` + +Or add it to your config so it stays on across restarts: + +```js +// react-server.config.mjs +export default { + devtools: true, +}; +``` + +DevTools is development-only. It is never included in production builds. + + +## Opening the panel + + +Once enabled, a small floating button with the react-server logo appears in the bottom-right corner. Click it to open the panel, or use the keyboard shortcut: + +| Shortcut | Platform | +|----------|----------| +| Ctrl+Shift+D | Windows / Linux | +| Cmd+Shift+D | macOS | + +The same shortcut closes the panel again. + + +## Positioning the panel + + +The toolbar at the top of the panel has four dock-mode buttons. Click one to switch where the panel sits: + +- **Bottom** (default) โ€” the panel docks to the bottom edge. Your page shrinks vertically to make room. +- **Left** / **Right** โ€” the panel docks to the side. Your page shrinks horizontally. +- **Float** โ€” the panel becomes a free-floating window you can drag by its toolbar and resize from the bottom-right corner. + +In any docked mode, drag the handle between the panel and your page to resize it. + +All panel state โ€” open/closed, dock mode, size, and float position โ€” is saved to `localStorage` so it persists across page reloads. + +{/* Screenshot: the DevTools panel in float mode, positioned in the center of the screen as a draggable window with a resize grip in the bottom-right corner. The float dock mode icon in the toolbar is highlighted/active. */} + + + +## Dark mode + + +The / button in the toolbar toggles between light and dark mode. DevTools picks up your app's theme automatically by checking, in order: + +1. A `dark` or `light` class on `` +2. A `dark=1` or `dark=0` cookie +3. The system `prefers-color-scheme` media query + +Clicking the toggle updates both the panel and your host page, and persists the choice via a cookie. + + +## Tab overflow + + +The panel has nine tabs. If the panel is too narrow to fit them all, the ones that don't fit collapse into an overflow menu behind the button on the right side of the tab bar. The currently active tab is always kept visible, even if it would normally overflow. + + +## Status tab + + +The Status tab shows your development server's resource usage. It is a `"use live"` component that streams fresh numbers every second. + +Three cards are displayed: + +| Card | What it shows | +|------|---------------| +| Process | Node.js version, PID, platform/architecture, and uptime | +| CPU | Usage percentage as a gauge (green under 50 %, amber up to 80 %, red above), core count, and 1/5/15-minute load averages | +| Memory | Heap used versus heap total, RSS, external allocations, array buffers, and OS-level free/total memory โ€” each metric has its own gauge bar | + +Use this tab to watch for memory growth over time (a sign of leaks) or to check whether a rendering change is spiking CPU. + +{/* Screenshot: the Status tab showing three stat cards side by side โ€” "Process" (Node.js version, PID, platform, uptime), "CPU" (a green gauge at ~15 %, core count, load average), and "Memory" (heap used/total gauge, RSS gauge, external, array buffers, OS memory gauge). */} + + + +## Payload tab + + +The Payload tab captures every React Server Components Flight response and decodes it so you can see exactly what the server sent. + +{/* Screenshot: the Payload tab showing a page size bar at the top (green/purple/blue segments for RSC/Hydration/HTML), a payload selector dropdown showing "โšก initial /", summary badges (size, chunks, client refs, server refs, promises, duration), an expanded "Client References" list, and several chunk rows with colored type tags (Model, Module, Hint). */} + + + +### Reading the page size bar + + +At the top of the tab, a stacked bar breaks down the initial HTML document into three segments: + +| Segment | Color | What it contains | +|---------|-------|-----------------| +| RSC Payload | Green | The Flight stream embedded in the HTML | +| Hydration | Purple | Serialized `"use cache"` entries inlined for the client | +| HTML | Blue | Everything else (markup, scripts, styles) | + +Total and transferred sizes are shown below the bar. If the transferred size is smaller, the server is compressing the response. + + +### Selecting a payload + + +The dropdown lists every payload captured during the session. Each entry shows the URL, total byte size, and chunk count. Select one to inspect it. + +| Label | What it captures | +|-------|-----------------| +| โšก initial | The Flight data from the first page load | +| ๐Ÿ”„ stream | Additional chunks that arrived after the initial response (streamed through Suspense boundaries) | +| ๐Ÿงญ nav | Flight responses triggered by client-side navigations | + + +### Inspecting chunks + + +Below the summary badges, the chunk list shows every Flight protocol row: + +| Column | Meaning | +|--------|---------| +| ID | The chunk identifier | +| Type | A colored tag: Model (component tree data), Module (client component import), Error, Hint (resource preload), Debug, Text, Binary, or Console | +| Size | How many bytes that chunk occupies | +| Data | A truncated preview of the chunk content | + +Type the name of a tag (e.g., `module`) or any content substring into the filter field to narrow the list. + + +### Finding client components in the payload + + +Expand the **Client References** section to see every `"use client"` module the server referenced. Each entry shows the module path and exported name. Hover over a reference to highlight the corresponding DOM element on the page with a green overlay. Click a reference to scroll the chunk list to the matching Module chunk. + + +## Cache tab + + +The Cache tab records every `"use cache"` call โ€” both on the server and on the client โ€” and shows whether it was a hit, a miss, or a revalidation. + +{/* Screenshot: the Cache tab with a toolbar showing hit/miss counts and All/hit/miss/revalidate filter buttons, a collapsed "request cache" hydration section, and a list of cache events. Each event has a timestamp, a colored hit/miss tag, a request/default provider tag, a function name like "getUser(42)", a clickable source file link, and a TTL value. One entry shows the โœ• invalidate button. */} + + + +### Reading cache events + + +Each row in the event list contains: + +| Column | Meaning | +|--------|---------| +| Time | When the call happened | +| Type | `hit` (served from cache), `miss` (computed and stored), or `revalidate` (re-computed in the background) | +| Provider | `request` (lives for one request only) or `default` (persists across requests) | +| Function | The cached function name and the arguments it was called with | +| Source | File path and line number โ€” click to open in VS Code | +| TTL | Shown on misses and revalidations; how long the entry is valid | + +The toolbar at the top shows aggregate hit/miss/revalidation counts. Use the filter buttons to show only one type at a time. + + +### Invalidating a cache entry + + +Hover over any `default`-provider cache event and click on the right. This immediately invalidates the entry on the server and removes it from the event list. The next call to that function will be a miss. Request-scoped cache entries cannot be invalidated manually because they are discarded at the end of the request anyway. + + +### Viewing hydration data + + +If your page uses `"use cache"` with request-scoped caching, the server serializes those cache entries into the HTML so the client can hydrate without re-fetching. The collapsible **request cache** section at the top of the Cache tab lists these hydrated entries with their hashed keys, byte sizes, and a preview of the serialized data. + + +## Routes tab + + +The Routes tab shows the full route tree produced by the file-system router. Every page, layout, middleware, API route, error boundary, loading state, fallback, template, and named outlet is listed. + +{/* Screenshot: the Routes tab with a filter input, type filter buttons (active, all, page, layout, middleware, api, etc.) with count badges, a "12 of 28 routes" counter, and a table with Route, Type, file-type icon, and Source columns. Some rows have a green left border indicating they match the current path. */} + + + +### Filtering routes + + +| Control | How to use it | +|---------|---------------| +| Text filter | Type a path fragment, filename, outlet name, or HTTP method to narrow the list | +| Type buttons | Click a type (page, layout, middleware, api, error, loading, fallback, template, outlet) to show only that type. The count badge on each button tells you how many routes of that type exist | +| Active toggle | Click "active" to hide everything that does not match the current server pathname. This is useful to see exactly which layouts, middlewares, and pages are involved in rendering the current URL | + + +### Understanding active routes + + +Routes matching the current server pathname are highlighted with a green left border. The matching accounts for dynamic segments (`[param]`), optional parameters (`[[optional]]`), and catch-all patterns (`[...slug]`). Layouts, middlewares, and templates match as prefixes (they are active for all child paths), while pages must match exactly. + +> **Note:** the pathname used for matching is the **server pathname** (after any rewrites), not the client-visible URL. + + +### Opening source files + + +Each route row shows a file-type icon (React/JSX, TypeScript, MDX, etc.) and the relative source path. Click the source path to open the file in VS Code. + + +### Component routes + + +Below the file-router tree, a **Component Routes** section appears if your app registers routes via `` components in code. Each entry shows the route pattern, type (server, client, or fallback), and flags like `remote`, `exact`, or `loading`. Active component routes display the matched path parameters inline. + + +## Outlets tab + + +The Outlets tab lists every named outlet currently rendered on the page. Outlets are the named slots (`@sidebar`, `@content`, etc.) that layouts receive as props, plus `PAGE_ROOT` for the root rendering slot. + +{/* Screenshot: the Outlets tab showing "Host URL: http://localhost:3000/products" at the top and a grid of outlet cards. One card shows "@sidebar" with a "router" badge and eye/refresh icons. Another shows "PAGE_ROOT" with a "static" badge. A colored dashed overlay is visible on the page behind the panel, highlighting the "@sidebar" region with a label. */} + + + +### What each outlet card shows + + +| Field | Meaning | +|-------|---------| +| Name | The outlet identifier | +| URL | The URL the outlet content was loaded from (if applicable) | +| Badge | How the outlet is managed: `router` (file-router), `remote` (fetched from another server), `live` (streaming), `defer` (lazily loaded), or `static` | + + +### Highlighting outlets on the page + + +Hover over any outlet card. A colored dashed rectangle appears around that outlet's rendered area on the page, with a label showing the outlet name. Each outlet gets a distinct color so you can visually map the cards to sections of the page. + +The highlight overlay handles edge cases: hidden marker elements, outlets that render as multiple sibling elements (the overlay covers the union bounding rect), and full-screen backdrop elements (which are filtered out so the actual content is highlighted). + + +### Scrolling and refreshing + + +| Action | What it does | +|--------|-------------| +| | Scrolls the page to the outlet's position | +| | Re-renders that single outlet without a full page reload โ€” useful when you want to test server-side changes for one section of the page without navigating away | + + +## Remotes tab + + +If your app uses `` to load RSC content from other react-server instances, the Remotes tab shows each one. + +{/* Screenshot: the Remotes tab showing two remote component cards. Each card has a URL like "http://localhost:3001/widget", an outlet name, and property badges (TTL, live). Action buttons for external link, eye, and arrow-right are visible. */} + + +Each card shows: + +| Field | Meaning | +|-------|---------| +| Remote URL | The URL the component is fetched from. Click to open it in a new browser tab | +| Outlet name | The outlet this remote component renders into | +| TTL badge | How long the response is cached | +| `isolate` badge | The component is rendered in a sandbox | +| `defer` badge | The component is loaded lazily | +| `live` badge | The component streams real-time updates | + +Hover to highlight the remote component on the page. Click to scroll to it. Click to jump to its outlet in the Outlets tab. + + +## Live tab + + +The Live tab monitors every `"use live"` component โ€” async generator functions on the server that yield successive renders to the client. + +{/* Screenshot: the Live tab showing two component cards. The first shows "StatusPanel" with a green "running" badge, a "streaming" cyan badge, "yields: 42", "last yield: 2s ago", "started: 5m ago". The second shows "PriceTracker" with a violet "waiting" badge. */} + + +Each card shows the component's name, its current state, and runtime statistics: + +| State | Meaning | +|-------|---------| +| `starting` | The generator is being initialized | +| `waiting` | Waiting before the next yield | +| `running` / `connected` | Actively streaming to the client | +| `finished` | The generator returned normally | +| `aborted` | The client disconnected | +| `error` | The generator threw an exception (the error message is shown inline) | + +A `streaming` badge appears while data is being sent. The card also shows how many times the generator has yielded, when the last yield happened, and when the component started. If the component is loaded from a remote server, a `remote` badge appears. + + +## Workers tab + + +The Workers tab tracks `"use worker"` threads โ€” both server-side Worker Threads (Node.js) and client-side Web Workers. + +{/* Screenshot: the Workers tab with a toolbar showing "3 workers ยท 2 server ยท 1 client" and All/server/client filter buttons. Below, worker entries show type tags (server/client), state tags (Ready/Spawning), module paths, call counts, error counts, last function name, spawn time, and last call time. */} + + +The toolbar summarizes how many workers exist, broken down by type. Use the filter buttons to show only server or only client workers. + +Each worker entry shows: + +| Field | Meaning | +|-------|---------| +| Type | `server` or `client` | +| State | `Spawning` (thread starting), `Ready` (idle), `Error` (last call failed), or `Restarting` | +| Module path | The file that defines the worker, shortened to the last few path segments | +| Call count | Total invocations and how many are currently in flight | +| Error / restart counts | Helps spot unstable workers | +| Last function | The name of the most recently called export | +| Timing | When the worker was spawned and when it was last invoked | + + +## Logs tab + + +The Logs tab streams all server terminal output (`stdout` and `stderr`) into the browser so you don't need to switch back to the terminal. + +{/* Screenshot: the Logs tab with a toolbar showing "All (156)" / "stdout (140)" / "stderr (16)" filter buttons, a search input, and a "Clear" button. The log list shows timestamped entries with colorful ANSI-rendered text (green "[vite]" prefix, yellow warnings). A few stderr entries are tagged in red. */} + + + +### Filtering logs + + +| Control | How to use it | +|---------|---------------| +| Stream buttons | Click `stdout` or `stderr` to show only that stream. The count on each button tells you how many entries of that type exist | +| Search field | Type any text to filter log entries. The search matches against the plain text content with ANSI codes stripped | + + +### Reading ANSI output + + +Server logs often contain ANSI escape codes for colors and text styling. The Logs tab renders these faithfully โ€” 4-bit, 8-bit, and 24-bit (true-color) sequences are all supported, including bold, italic, underline, and dim. What you see in the panel matches what you'd see in a terminal. + + +### Auto-scroll behavior + + +By default the log view stays pinned to the bottom so new entries scroll into view as they arrive. Scroll up to pause auto-scrolling. A **Scroll to bottom** button appears at the bottom of the panel โ€” click it to resume. + +Click **Clear** in the toolbar to empty the log buffer. + + +## Element highlighting + + +Several tabs let you hover over an item to highlight the corresponding element on the page: + +- In the **Payload** tab, hovering a client reference highlights the DOM element rendered by that client component (green overlay). +- In the **Outlets** tab, hovering an outlet card highlights its rendered region (each outlet gets a unique color). +- In the **Remotes** tab, hovering a remote card highlights its rendered region the same way. + +The overlay is a colored dashed rectangle with a label badge showing the name. It repositions in real time as you scroll or resize the page. + +{/* Screenshot: a page with the Outlets tab open. One outlet card is being hovered, and on the page a colored dashed rectangle with rounded corners outlines the outlet's rendered area, with a label badge reading "@sidebar" above it. */} + + + +## How DevTools works + + +The panel UI runs inside an iframe served from `/__react_server_devtools__/*`. This keeps the DevTools React tree separate from your app โ€” it won't interfere with your component state or styling. + +Three communication channels connect the parts: + +- **Host page โ†’ Iframe** (`postMessage`): RSC payloads (captured by intercepting `fetch`), outlet registrations, component routes, page-level size stats, navigation events, and theme changes. +- **Server โ†’ Iframe** (Socket.IO, `/__devtools__` namespace): live component state, server-side cache events, worker status, server logs, and the file-router manifest. The connection is established automatically when the panel opens. +- **Iframe โ†’ Server** (Socket.IO): cache invalidation requests and outlet refresh commands travel back over the same socket. diff --git a/docs/src/pages/ja/(pages)/features/cli.mdx b/docs/src/pages/ja/(pages)/features/cli.mdx index 1b4726d6..4ad3f066 100644 --- a/docs/src/pages/ja/(pages)/features/cli.mdx +++ b/docs/src/pages/ja/(pages)/features/cli.mdx @@ -106,6 +106,22 @@ URLใฎใ‚ชใƒชใ‚ธใƒณ้ƒจๅˆ†ใ‚’ๅฎšๆ•ฐใซๆŒ‡ๅฎšใ—ใพใ™ใ€‚็’ฐๅขƒๅค‰ๆ•ฐ`ORIGIN`ใ‚’ไฝฟ ใ‚ตใƒผใƒใƒผ่ตทๅ‹•ๆ™‚ใซใ‚ฟใƒผใƒŸใƒŠใƒซใ‚’ใ‚ฏใƒชใ‚ขใ™ใ‚‹ใ‹ๆŒ‡ๅฎšใ—ใพใ™ใ€‚ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฏ`false`ใงใ™ใ€‚ใ‚ฟใƒผใƒŸใƒŠใƒซใ‚’ใ‚ฏใƒชใ‚ขใ—ใฆใ‚ตใƒผใƒใƒผใ‚’่ตทๅ‹•ใ—ใŸใ„ใจใใซไฝฟใ„ใพใ™ใ€‚ + +### --devtools + + +็ต„ใฟ่พผใฟใฎreact-server DevToolsใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฏ`false`ใงใ™ใ€‚ + +ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€้–‹็™บไธญใซใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใซDevToolsใƒ‘ใƒใƒซใŒๆŒฟๅ…ฅใ•ใ‚Œใพใ™ใ€‚RSCใƒšใ‚คใƒญใƒผใƒ‰ใ€ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚จใƒณใƒˆใƒชใ€ใƒซใƒผใƒˆใ€ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใ€ใƒชใƒขใƒผใƒˆใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ€ใƒฉใ‚คใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ€ใƒฏใƒผใ‚ซใƒผใ€ใ‚ตใƒผใƒใƒผใƒญใ‚ฐใชใฉใ€ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฎๅ†…้ƒจใ‚’ใƒชใ‚ขใƒซใ‚ฟใ‚คใƒ ใงๆคœๆŸปใงใใพใ™ใ€‚ + +`react-server.config.mjs`ใง่จญๅฎšใ™ใ‚‹ใ“ใจใ‚‚ใงใใพใ™๏ผš + +```js +export default { + devtools: true, +}; +``` + ### --no-color diff --git a/docs/src/pages/ja/(pages)/features/devtools.mdx b/docs/src/pages/ja/(pages)/features/devtools.mdx new file mode 100644 index 00000000..b14cdea4 --- /dev/null +++ b/docs/src/pages/ja/(pages)/features/devtools.mdx @@ -0,0 +1,399 @@ +--- +title: DevTools +category: Features +order: 17 +--- + +import Link from "../../../../components/Link.jsx"; +import { EyeIcon, RefreshIcon, ExternalLinkIcon, ArrowRightIcon, SunIcon, MoonIcon, CloseIcon, OverflowIcon, DockBottomIcon, DockLeftIcon, DockRightIcon, DockFloatIcon } from "../../../../components/DevToolsIcon.jsx"; +import ThemedImage from "../../../../components/ThemedImage.jsx"; + +# DevTools + +็ต„ใฟ่พผใฟใฎDevToolsใƒ‘ใƒใƒซใ‚’ไฝฟใ†ใจใ€้–‹็™บไธญใซใƒ–ใƒฉใ‚ฆใ‚ถใ‹ใ‚‰้›ขใ‚Œใ‚‹ใ“ใจใชใใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฎๅ‹•ไฝœใ‚’ๆคœๆŸปใงใใพใ™ใ€‚ใ‚ตใƒผใƒใƒผใƒ—ใƒญใ‚ปใ‚นใฎ็Šถๆ…‹ใ€RSCใƒšใ‚คใƒญใƒผใƒ‰ใฎๅ†…ๅฎนใ€ใ‚ญใƒฃใƒƒใ‚ทใƒฅใฎๅ‹•ไฝœใ€ใƒซใƒผใƒˆใƒ„ใƒชใƒผใ€ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใฎใƒฌใ‚คใ‚ขใ‚ฆใƒˆใ€ใƒชใƒขใƒผใƒˆ/ใƒฉใ‚คใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ€ใƒฏใƒผใ‚ซใƒผใ‚นใƒฌใƒƒใƒ‰ใ€ใ‚ตใƒผใƒใƒผใƒญใ‚ฐใ‚’ใ‚ซใƒใƒผใ—ใฆใ„ใพใ™ใ€‚ + +{/* Screenshot: the DevTools panel docked to the bottom of the browser, showing the Status tab with CPU, memory, and process gauges. The app content is visible above the panel. The toolbar shows the react-server logo, version, dock mode buttons (bottom/left/right/float), a dark mode toggle, and a close button. */} + + + +## DevToolsใฎๆœ‰ๅŠนๅŒ– + + +้–‹็™บใ‚ตใƒผใƒใƒผใฎ่ตทๅ‹•ๆ™‚ใซ`--devtools`ใ‚’ๆธกใ—ใพใ™๏ผš + +```sh +pnpm react-server ./App.jsx --devtools +``` + +ใพใŸใฏ่จญๅฎšใƒ•ใ‚กใ‚คใƒซใซ่ฟฝๅŠ ใ—ใฆๅ†่ตทๅ‹•ๆ™‚ใ‚‚็ถญๆŒใ•ใ‚Œใ‚‹ใ‚ˆใ†ใซใ—ใพใ™๏ผš + +```js +// react-server.config.mjs +export default { + devtools: true, +}; +``` + +DevToolsใฏ้–‹็™บๅฐ‚็”จใงใ™ใ€‚ใƒ—ใƒญใƒ€ใ‚ฏใ‚ทใƒงใƒณใƒ“ใƒซใƒ‰ใซใฏๅซใพใ‚Œใพใ›ใ‚“ใ€‚ + + +## ใƒ‘ใƒใƒซใ‚’้–‹ใ + + +ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ๅณไธ‹ใซreact-serverใฎใƒญใ‚ดใŒๅ…ฅใฃใŸๅฐใ•ใชใƒ•ใƒญใƒผใƒ†ใ‚ฃใƒณใ‚ฐใƒœใ‚ฟใƒณใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจใƒ‘ใƒใƒซใŒ้–‹ใใพใ™ใ€‚ใ‚ญใƒผใƒœใƒผใƒ‰ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ‚‚ไฝฟใˆใพใ™๏ผš + +| ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆ | ใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ  | +|----------|----------| +| Ctrl+Shift+D | Windows / Linux | +| Cmd+Shift+D | macOS | + +ๅŒใ˜ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใงใƒ‘ใƒใƒซใ‚’้–‰ใ˜ใพใ™ใ€‚ + + +## ใƒ‘ใƒใƒซใฎ้…็ฝฎ + + +ใƒ‘ใƒใƒซไธŠ้ƒจใฎใƒ„ใƒผใƒซใƒใƒผใซ4ใคใฎใƒ‰ใƒƒใ‚ญใƒณใ‚ฐใƒขใƒผใƒ‰ใƒœใ‚ฟใƒณใŒใ‚ใ‚Šใพใ™ใ€‚ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใƒ‘ใƒใƒซใฎไฝ็ฝฎใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใพใ™๏ผš + +- **Bottom**๏ผˆใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ๏ผ‰โ€” ใƒ‘ใƒใƒซใŒไธ‹็ซฏใซใƒ‰ใƒƒใ‚ญใƒณใ‚ฐใ—ใ€ใƒšใƒผใ‚ธใŒๅž‚็›ดๆ–นๅ‘ใซ็ธฎๅฐใ—ใพใ™ใ€‚ +- **Left** / **Right** โ€” ใƒ‘ใƒใƒซใŒๅด้ขใซใƒ‰ใƒƒใ‚ญใƒณใ‚ฐใ—ใ€ใƒšใƒผใ‚ธใŒๆฐดๅนณๆ–นๅ‘ใซ็ธฎๅฐใ—ใพใ™ใ€‚ +- **Float** โ€” ใƒ‘ใƒใƒซใŒใƒ•ใƒชใƒผใƒ•ใƒญใƒผใƒ†ใ‚ฃใƒณใ‚ฐใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใซใชใ‚Šใ€ใƒ„ใƒผใƒซใƒใƒผใ‚’ใƒ‰ใƒฉใƒƒใ‚ฐใ—ใฆ็งปๅ‹•ใ€ๅณไธ‹ใฎใ‚ณใƒผใƒŠใƒผใ‹ใ‚‰ใƒชใ‚ตใ‚คใ‚บใงใใพใ™ใ€‚ + +ใƒ‰ใƒƒใ‚ญใƒณใ‚ฐใƒขใƒผใƒ‰ใงใฏใ€ใƒ‘ใƒใƒซใจใƒšใƒผใ‚ธใฎ้–“ใฎใƒใƒณใƒ‰ใƒซใ‚’ใƒ‰ใƒฉใƒƒใ‚ฐใ—ใฆใ‚ตใ‚คใ‚บใ‚’่ชฟๆ•ดใงใใพใ™ใ€‚ + +ใƒ‘ใƒใƒซใฎ็Šถๆ…‹๏ผˆ้–‹้–‰ใ€ใƒ‰ใƒƒใ‚ญใƒณใ‚ฐใƒขใƒผใƒ‰ใ€ใ‚ตใ‚คใ‚บใ€ใƒ•ใƒญใƒผใƒˆไฝ็ฝฎ๏ผ‰ใฏใ™ในใฆ`localStorage`ใซไฟๅญ˜ใ•ใ‚Œใ€ใƒšใƒผใ‚ธใƒชใƒญใƒผใƒ‰ๅพŒใ‚‚็ถญๆŒใ•ใ‚Œใพใ™ใ€‚ + +{/* Screenshot: the DevTools panel in float mode, positioned in the center of the screen as a draggable window with a resize grip in the bottom-right corner. The float dock mode icon in the toolbar is highlighted/active. */} + + + +## ใƒ€ใƒผใ‚ฏใƒขใƒผใƒ‰ + + +ใƒ„ใƒผใƒซใƒใƒผใฎ / ใƒœใ‚ฟใƒณใงใƒฉใ‚คใƒˆใƒขใƒผใƒ‰ใจใƒ€ใƒผใ‚ฏใƒขใƒผใƒ‰ใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใพใ™ใ€‚DevToolsใฏไปฅไธ‹ใฎ้ †ๅบใงใ‚ขใƒ—ใƒชใฎใƒ†ใƒผใƒžใ‚’่‡ชๅ‹•ๆคœๅ‡บใ—ใพใ™๏ผš + +1. ``ใฎ`dark`ใพใŸใฏ`light`ใ‚ฏใƒฉใ‚น +2. `dark=1`ใพใŸใฏ`dark=0` Cookie +3. ใ‚ทใ‚นใƒ†ใƒ ใฎ`prefers-color-scheme`ใƒกใƒ‡ใ‚ฃใ‚ขใ‚ฏใ‚จใƒช + +ใƒˆใ‚ฐใƒซใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจใƒ‘ใƒใƒซใจใƒ›ใ‚นใƒˆใƒšใƒผใ‚ธใฎไธกๆ–นใŒๆ›ดๆ–ฐใ•ใ‚Œใ€Cookieใง่จญๅฎšใŒไฟๅญ˜ใ•ใ‚Œใพใ™ใ€‚ + + +## ใ‚ฟใƒ–ใฎใ‚ชใƒผใƒใƒผใƒ•ใƒญใƒผ + + +ใƒ‘ใƒใƒซใซใฏ9ใคใฎใ‚ฟใƒ–ใŒใ‚ใ‚Šใพใ™ใ€‚ใƒ‘ใƒใƒซใฎๅน…ใŒ็‹ญใใฆใ™ในใฆ่กจ็คบใงใใชใ„ๅ ดๅˆใ€ๅŽใพใ‚‰ใชใ„ใ‚ฟใƒ–ใฏใ‚ฟใƒ–ใƒใƒผใฎๅณๅดใซใ‚ใ‚‹ ใƒœใ‚ฟใƒณใฎใ‚ชใƒผใƒใƒผใƒ•ใƒญใƒผใƒกใƒ‹ใƒฅใƒผใซๆŠ˜ใ‚ŠใŸใŸใพใ‚Œใพใ™ใ€‚็พๅœจใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใ‚ฟใƒ–ใฏใ€้€šๅธธใ‚ชใƒผใƒใƒผใƒ•ใƒญใƒผใ™ใ‚‹ๅ ดๅˆใงใ‚‚ๅธธใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ + + +## Statusใ‚ฟใƒ– + + +Statusใ‚ฟใƒ–ใฏ้–‹็™บใ‚ตใƒผใƒใƒผใฎใƒชใ‚ฝใƒผใ‚นไฝฟ็”จ็Šถๆณใ‚’่กจ็คบใ—ใพใ™ใ€‚ๆฏŽ็ง’ๆ–ฐใ—ใ„ใƒ‡ใƒผใ‚ฟใ‚’ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใ™ใ‚‹`"use live"`ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใงใ™ใ€‚ + +3ใคใฎใ‚ซใƒผใƒ‰ใŒ่กจ็คบใ•ใ‚Œใพใ™๏ผš + +| ใ‚ซใƒผใƒ‰ | ่กจ็คบๅ†…ๅฎน | +|--------|----------| +| Process | Node.jsใƒใƒผใ‚ธใƒงใƒณใ€PIDใ€ใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ /ใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃใ€็จผๅƒๆ™‚้–“ | +| CPU | ไฝฟ็”จ็އใฎใ‚ฒใƒผใ‚ธ๏ผˆ50%ๆœชๆบ€ใฏ็ท‘ใ€80%ใพใง้ป„ใ€ใใ‚ŒไปฅไธŠใฏ่ตค๏ผ‰ใ€ใ‚ณใ‚ขๆ•ฐใ€1/5/15ๅˆ†ใฎใƒญใƒผใƒ‰ใ‚ขใƒ™ใƒฌใƒผใ‚ธ | +| Memory | ใƒ’ใƒผใƒ—ไฝฟ็”จ้‡/ๅˆ่จˆใ€RSSใ€ๅค–้ƒจใƒกใƒขใƒชใ€ArrayBuffersใ€OSๅ…จไฝ“ใฎใƒกใƒขใƒช โ€” ๅ„ใƒกใƒˆใƒชใ‚ฏใ‚นใซใ‚ฒใƒผใ‚ธใƒใƒผไป˜ใ | + +ใ“ใฎใ‚ฟใƒ–ใงๆ™‚้–“็ตŒ้Žใซใ‚ˆใ‚‹ใƒกใƒขใƒชใฎๅข—ๅŠ ๏ผˆใƒชใƒผใ‚ฏใฎๅ…†ๅ€™๏ผ‰ใ‚’็›ฃ่ฆ–ใ—ใŸใ‚Šใ€ใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใฎๅค‰ๆ›ดใŒCPUใซ่ฒ ่ทใ‚’ใ‹ใ‘ใฆใ„ใชใ„ใ‹็ขบ่ชใงใใพใ™ใ€‚ + +{/* Screenshot: the Status tab showing three stat cards side by side โ€” "Process" (Node.js version, PID, platform, uptime), "CPU" (a green gauge at ~15 %, core count, load average), and "Memory" (heap used/total gauge, RSS gauge, external, array buffers, OS memory gauge). */} + + + +## Payloadใ‚ฟใƒ– + + +Payloadใ‚ฟใƒ–ใฏใ™ในใฆใฎ React Server Components Flightใƒฌใ‚นใƒใƒณใ‚นใ‚’ใ‚ญใƒฃใƒ—ใƒใƒฃใ—ใฆใƒ‡ใ‚ณใƒผใƒ‰ใ—ใ€ใ‚ตใƒผใƒใƒผใŒ้€ไฟกใ—ใŸๅ†…ๅฎนใ‚’ๆญฃ็ขบใซ็ขบ่ชใงใใพใ™ใ€‚ + +{/* Screenshot: the Payload tab showing a page size bar at the top (green/purple/blue segments for RSC/Hydration/HTML), a payload selector dropdown showing "โšก initial /", summary badges (size, chunks, client refs, server refs, promises, duration), an expanded "Client References" list, and several chunk rows with colored type tags (Model, Module, Hint). */} + + + +### ใƒšใƒผใ‚ธใ‚ตใ‚คใ‚บใƒใƒผใฎ่ฆ‹ๆ–น + + +ใ‚ฟใƒ–ใฎไธŠ้ƒจใซใ‚ใ‚‹็ฉใฟไธŠใ’ใƒใƒผใฏใ€ๅˆๆœŸHTMLใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’3ใคใฎใ‚ปใ‚ฐใƒกใƒณใƒˆใซๅˆ†่งฃใ—ใพใ™๏ผš + +| ใ‚ปใ‚ฐใƒกใƒณใƒˆ | ่‰ฒ | ๅ†…ๅฎน | +|---------|-------|-----------------| +| RSC Payload | ็ท‘ | HTMLใซๅŸ‹ใ‚่พผใพใ‚ŒใŸFlightใ‚นใƒˆใƒชใƒผใƒ  | +| Hydration | ็ดซ | ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ็”จใซใ‚คใƒณใƒฉใ‚คใƒณๅŒ–ใ•ใ‚ŒใŸ`"use cache"`ใ‚จใƒณใƒˆใƒช | +| HTML | ้’ | ใใฎไป–ใ™ในใฆ๏ผˆใƒžใƒผใ‚ฏใ‚ขใƒƒใƒ—ใ€ใ‚นใ‚ฏใƒชใƒ—ใƒˆใ€ใ‚นใ‚ฟใ‚คใƒซ๏ผ‰ | + +ๅˆ่จˆใ‚ตใ‚คใ‚บใจ่ปข้€ใ‚ตใ‚คใ‚บใŒใƒใƒผใฎไธ‹ใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚่ปข้€ใ‚ตใ‚คใ‚บใŒๅฐใ•ใ„ๅ ดๅˆใ€ใ‚ตใƒผใƒใƒผใŒใƒฌใ‚นใƒใƒณใ‚นใ‚’ๅœง็ธฎใ—ใฆใ„ใพใ™ใ€‚ + + +### ใƒšใ‚คใƒญใƒผใƒ‰ใฎ้ธๆŠž + + +ใƒ‰ใƒญใƒƒใƒ—ใƒ€ใ‚ฆใƒณใซใ‚ปใƒƒใ‚ทใƒงใƒณไธญใซใ‚ญใƒฃใƒ—ใƒใƒฃใ•ใ‚ŒใŸใ™ในใฆใฎใƒšใ‚คใƒญใƒผใƒ‰ใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ๅ„ใ‚จใƒณใƒˆใƒชใซใฏURLใ€ๅˆ่จˆใƒใ‚คใƒˆใ‚ตใ‚คใ‚บใ€ใƒใƒฃใƒณใ‚ฏๆ•ฐใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚้ธๆŠžใ—ใฆๆคœๆŸปใ—ใพใ™ใ€‚ + +| ใƒฉใƒ™ใƒซ | ใ‚ญใƒฃใƒ—ใƒใƒฃๅ†…ๅฎน | +|--------|---------------| +| โšก initial | ๆœ€ๅˆใฎใƒšใƒผใ‚ธใƒญใƒผใƒ‰ใฎFlightใƒ‡ใƒผใ‚ฟ | +| ๐Ÿ”„ stream | ๅˆๆœŸใƒฌใ‚นใƒใƒณใ‚นใฎๅพŒใซๅˆฐ็€ใ—ใŸ่ฟฝๅŠ ใƒใƒฃใƒณใ‚ฏ๏ผˆSuspenseใƒใ‚ฆใƒณใƒ€ใƒชใ‚’้€šใ˜ใฆใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐ๏ผ‰ | +| ๐Ÿงญ nav | ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚ตใ‚คใƒ‰ใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณใง็™บ็”Ÿใ—ใŸFlightใƒฌใ‚นใƒใƒณใ‚น | + + +### ใƒใƒฃใƒณใ‚ฏใฎๆคœๆŸป + + +ใ‚ตใƒžใƒชใƒผใƒใƒƒใ‚ธใฎไธ‹ใซใ€ใ™ในใฆใฎFlightใƒ—ใƒญใƒˆใ‚ณใƒซ่กŒใŒใƒใƒฃใƒณใ‚ฏใƒชใ‚นใƒˆใจใ—ใฆ่กจ็คบใ•ใ‚Œใพใ™๏ผš + +| ใ‚ซใƒฉใƒ  | ๆ„ๅ‘ณ | +|--------|---------| +| ID | ใƒใƒฃใƒณใ‚ฏ่ญ˜ๅˆฅๅญ | +| Type | ่‰ฒไป˜ใใ‚ฟใ‚ฐ๏ผšModel๏ผˆใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใƒ„ใƒชใƒผใƒ‡ใƒผใ‚ฟ๏ผ‰ใ€Module๏ผˆใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ‚คใƒณใƒใƒผใƒˆ๏ผ‰ใ€Errorใ€Hint๏ผˆใƒชใ‚ฝใƒผใ‚นใƒ—ใƒชใƒญใƒผใƒ‰๏ผ‰ใ€Debugใ€Textใ€Binaryใ€Console | +| Size | ใใฎใƒใƒฃใƒณใ‚ฏใŒๅ ใ‚ใ‚‹ใƒใ‚คใƒˆๆ•ฐ | +| Data | ใƒใƒฃใƒณใ‚ฏๅ†…ๅฎนใฎๅˆ‡ใ‚Š่ฉฐใ‚ใ‚‰ใ‚ŒใŸใƒ—ใƒฌใƒ“ใƒฅใƒผ | + +ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใƒ•ใ‚ฃใƒผใƒซใƒ‰ใซใ‚ฟใ‚ฐๅ๏ผˆไพ‹๏ผš`module`๏ผ‰ใ‚„ใ‚ณใƒณใƒ†ใƒณใƒ„ใฎ้ƒจๅˆ†ๆ–‡ๅญ—ๅˆ—ใ‚’ๅ…ฅๅŠ›ใ—ใฆใƒชใ‚นใƒˆใ‚’็ตžใ‚Š่พผใ‚ใพใ™ใ€‚ + + +### ใƒšใ‚คใƒญใƒผใƒ‰ๅ†…ใฎใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ‚’่ฆ‹ใคใ‘ใ‚‹ + + +**Client References**ใ‚ปใ‚ฏใ‚ทใƒงใƒณใ‚’ๅฑ•้–‹ใ™ใ‚‹ใจใ€ใ‚ตใƒผใƒใƒผใŒๅ‚็…งใ—ใŸใ™ในใฆใฎ`"use client"`ใƒขใ‚ธใƒฅใƒผใƒซใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ๅ„ใ‚จใƒณใƒˆใƒชใซใฏใƒขใ‚ธใƒฅใƒผใƒซใƒ‘ใ‚นใจใ‚จใ‚ฏใ‚นใƒใƒผใƒˆๅใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ๅ‚็…งใซใƒ›ใƒใƒผใ™ใ‚‹ใจใ€ใƒšใƒผใ‚ธไธŠใฎๅฏพๅฟœใ™ใ‚‹DOM่ฆ็ด ใŒ็ท‘ใฎใ‚ชใƒผใƒใƒผใƒฌใ‚คใงใƒใ‚คใƒฉใ‚คใƒˆใ•ใ‚Œใพใ™ใ€‚ๅ‚็…งใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจใ€ใƒใƒฃใƒณใ‚ฏใƒชใ‚นใƒˆใฎๅฏพๅฟœใ™ใ‚‹Moduleใƒใƒฃใƒณใ‚ฏใซใ‚นใ‚ฏใƒญใƒผใƒซใ—ใพใ™ใ€‚ + + +## Cacheใ‚ฟใƒ– + + +Cacheใ‚ฟใƒ–ใฏใ‚ตใƒผใƒใƒผใจใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใฎไธกๆ–นใง็™บ็”Ÿใ™ใ‚‹ใ™ในใฆใฎ`"use cache"`ๅ‘ผใณๅ‡บใ—ใ‚’่จ˜้Œฒใ—ใ€ใƒ’ใƒƒใƒˆใ€ใƒŸใ‚นใ€ๅ†ๆคœ่จผใฎใ„ใšใ‚Œใ‹ใ‚’่กจ็คบใ—ใพใ™ใ€‚ + +{/* Screenshot: the Cache tab with a toolbar showing hit/miss counts and All/hit/miss/revalidate filter buttons, a collapsed "request cache" hydration section, and a list of cache events. Each event has a timestamp, a colored hit/miss tag, a request/default provider tag, a function name like "getUser(42)", a clickable source file link, and a TTL value. One entry shows the โœ• invalidate button. */} + + + +### ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚คใƒ™ใƒณใƒˆใฎ่ชญใฟๆ–น + + +ใ‚คใƒ™ใƒณใƒˆใƒชใ‚นใƒˆใฎๅ„่กŒใซใฏไปฅไธ‹ใŒๅซใพใ‚Œใพใ™๏ผš + +| ใ‚ซใƒฉใƒ  | ๆ„ๅ‘ณ | +|--------|---------| +| Time | ๅ‘ผใณๅ‡บใ—ใŒ็™บ็”Ÿใ—ใŸๆ™‚ๅˆป | +| Type | `hit`๏ผˆใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‹ใ‚‰ๆไพ›๏ผ‰ใ€`miss`๏ผˆ่จˆ็ฎ—ใ—ใฆไฟๅญ˜๏ผ‰ใ€`revalidate`๏ผˆใƒใƒƒใ‚ฏใ‚ฐใƒฉใ‚ฆใƒณใƒ‰ใงๅ†่จˆ็ฎ—๏ผ‰ | +| Provider | `request`๏ผˆ1ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎใฟๆœ‰ๅŠน๏ผ‰ใพใŸใฏ`default`๏ผˆใƒชใ‚ฏใ‚จใ‚นใƒˆ้–“ใงๆฐธ็ถš๏ผ‰ | +| Function | ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ•ใ‚ŒใŸ้–ขๆ•ฐๅใจๅ‘ผใณๅ‡บใ—ๅผ•ๆ•ฐ | +| Source | ใƒ•ใ‚กใ‚คใƒซใƒ‘ใ‚นใจ่กŒ็•ชๅท โ€” ใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจVS Codeใง้–‹ใ | +| TTL | ใƒŸใ‚นใจๅ†ๆคœ่จผใง่กจ็คบใ€‚ใ‚จใƒณใƒˆใƒชใฎๆœ‰ๅŠนๆœŸ้–“ | + +ใƒ„ใƒผใƒซใƒใƒผใฎไธŠ้ƒจใซใƒ’ใƒƒใƒˆ/ใƒŸใ‚น/ๅ†ๆคœ่จผใฎ้›†่จˆใ‚ซใ‚ฆใƒณใƒˆใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใƒœใ‚ฟใƒณใง1ใคใฎใ‚ฟใ‚คใƒ—ใฎใฟ่กจ็คบใงใใพใ™ใ€‚ + + +### ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚จใƒณใƒˆใƒชใฎ็„กๅŠนๅŒ– + + +`default`ใƒ—ใƒญใƒใ‚คใƒ€ใƒผใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚คใƒ™ใƒณใƒˆใซใƒ›ใƒใƒผใ—ใ€ๅณๅดใฎ ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใพใ™ใ€‚ใ‚ตใƒผใƒใƒผไธŠใฎใ‚จใƒณใƒˆใƒชใŒๅณๅบงใซ็„กๅŠนๅŒ–ใ•ใ‚Œใ€ใ‚คใƒ™ใƒณใƒˆใƒชใ‚นใƒˆใ‹ใ‚‰ๅ‰Š้™คใ•ใ‚Œใพใ™ใ€‚ใใฎ้–ขๆ•ฐใธใฎๆฌกๅ›žใฎๅ‘ผใณๅ‡บใ—ใฏใƒŸใ‚นใซใชใ‚Šใพใ™ใ€‚ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚นใ‚ณใƒผใƒ—ใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚จใƒณใƒˆใƒชใฏใƒชใ‚ฏใ‚จใ‚นใƒˆ็ต‚ไบ†ๆ™‚ใซ็ ดๆฃ„ใ•ใ‚Œใ‚‹ใŸใ‚ใ€ๆ‰‹ๅ‹•ใง็„กๅŠนๅŒ–ใงใใพใ›ใ‚“ใ€‚ + + +### ใƒใ‚คใƒ‰ใƒฌใƒผใ‚ทใƒงใƒณใƒ‡ใƒผใ‚ฟใฎ็ขบ่ช + + +ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚นใ‚ณใƒผใƒ—ใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅใง`"use cache"`ใ‚’ไฝฟ็”จใ—ใฆใ„ใ‚‹ๅ ดๅˆใ€ใ‚ตใƒผใƒใƒผใฏใใ‚Œใ‚‰ใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚จใƒณใƒˆใƒชใ‚’HTMLใซใ‚ทใƒชใ‚ขใƒฉใ‚คใ‚บใ—ใ€ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใŒๅ†ใƒ•ใ‚งใƒƒใƒใชใ—ใงใƒใ‚คใƒ‰ใƒฌใƒผใ‚ทใƒงใƒณใงใใ‚‹ใ‚ˆใ†ใซใ—ใพใ™ใ€‚Cacheใ‚ฟใƒ–ใฎไธŠ้ƒจใซใ‚ใ‚‹ๆŠ˜ใ‚ŠใŸใŸใฟๅฏ่ƒฝใช**request cache**ใ‚ปใ‚ฏใ‚ทใƒงใƒณใซใ€ใƒใƒƒใ‚ทใƒฅใ‚ญใƒผใ€ใƒใ‚คใƒˆใ‚ตใ‚คใ‚บใ€ใ‚ทใƒชใ‚ขใƒฉใ‚คใ‚บใ•ใ‚ŒใŸใƒ‡ใƒผใ‚ฟใฎใƒ—ใƒฌใƒ“ใƒฅใƒผใจใจใ‚‚ใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ + + +## Routesใ‚ฟใƒ– + + +Routesใ‚ฟใƒ–ใฏใƒ•ใ‚กใ‚คใƒซใ‚ทใ‚นใƒ†ใƒ ใƒซใƒผใ‚ฟใƒผใŒ็”Ÿๆˆใ—ใŸๅฎŒๅ…จใชใƒซใƒผใƒˆใƒ„ใƒชใƒผใ‚’่กจ็คบใ—ใพใ™ใ€‚ใƒšใƒผใ‚ธใ€ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใ€ใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใ€APIใƒซใƒผใƒˆใ€ใ‚จใƒฉใƒผใƒใ‚ฆใƒณใƒ€ใƒชใ€ใƒญใƒผใƒ‡ใ‚ฃใƒณใ‚ฐ็Šถๆ…‹ใ€ใƒ•ใ‚ฉใƒผใƒซใƒใƒƒใ‚ฏใ€ใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใ€ๅๅ‰ไป˜ใใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใŒใ™ในใฆใƒชใ‚นใƒˆใ•ใ‚Œใพใ™ใ€‚ + +{/* Screenshot: the Routes tab with a filter input, type filter buttons (active, all, page, layout, middleware, api, etc.) with count badges, a "12 of 28 routes" counter, and a table with Route, Type, file-type icon, and Source columns. Some rows have a green left border indicating they match the current path. */} + + + +### ใƒซใƒผใƒˆใฎใƒ•ใ‚ฃใƒซใ‚ฟใƒชใƒณใ‚ฐ + + +| ใ‚ณใƒณใƒˆใƒญใƒผใƒซ | ไฝฟใ„ๆ–น | +|-------------|--------| +| ใƒ†ใ‚ญใ‚นใƒˆใƒ•ใ‚ฃใƒซใ‚ฟใƒผ | ใƒ‘ใ‚นใฎๆ–ญ็‰‡ใ€ใƒ•ใ‚กใ‚คใƒซๅใ€ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆๅใ€HTTPใƒกใ‚ฝใƒƒใƒ‰ใ‚’ๅ…ฅๅŠ›ใ—ใฆใƒชใ‚นใƒˆใ‚’็ตžใ‚Š่พผใ‚€ | +| ใ‚ฟใ‚คใƒ—ใƒœใ‚ฟใƒณ | ใ‚ฟใ‚คใƒ—๏ผˆpageใ€layoutใ€middlewareใ€apiใ€errorใ€loadingใ€fallbackใ€templateใ€outlet๏ผ‰ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใใฎใ‚ฟใ‚คใƒ—ใฎใฟ่กจ็คบใ€‚ๅ„ใƒœใ‚ฟใƒณใฎใ‚ซใ‚ฆใƒณใƒˆใƒใƒƒใ‚ธใซใใฎใ‚ฟใ‚คใƒ—ใฎใƒซใƒผใƒˆๆ•ฐใŒ่กจ็คบใ•ใ‚Œใ‚‹ | +| Activeใƒˆใ‚ฐใƒซ | ใ€Œactiveใ€ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจใ€็พๅœจใฎใ‚ตใƒผใƒใƒผใƒ‘ใ‚นๅใซไธ€่‡ดใ—ใชใ„ใ‚‚ใฎใ‚’ใ™ในใฆ้ž่กจ็คบใซใ™ใ‚‹ใ€‚็พๅœจใฎURLใฎใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใซ้–ขไธŽใ—ใฆใ„ใ‚‹ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใ€ใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใ€ใƒšใƒผใ‚ธใ‚’ๆญฃ็ขบใซ็ขบ่ชใ™ใ‚‹ใฎใซไพฟๅˆฉ | + + +### ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใƒซใƒผใƒˆใฎ็†่งฃ + + +็พๅœจใฎใ‚ตใƒผใƒใƒผใƒ‘ใ‚นๅใซไธ€่‡ดใ™ใ‚‹ใƒซใƒผใƒˆใฏ็ท‘ใฎๅทฆใƒœใƒผใƒ€ใƒผใงใƒใ‚คใƒฉใ‚คใƒˆใ•ใ‚Œใพใ™ใ€‚ใƒžใƒƒใƒใƒณใ‚ฐใฏๅ‹•็š„ใ‚ปใ‚ฐใƒกใƒณใƒˆ๏ผˆ`[param]`๏ผ‰ใ€ใ‚ชใƒ—ใ‚ทใƒงใƒŠใƒซใƒ‘ใƒฉใƒกใƒผใ‚ฟ๏ผˆ`[[optional]]`๏ผ‰ใ€ใ‚ญใƒฃใƒƒใƒใ‚ชใƒผใƒซใƒ‘ใ‚ฟใƒผใƒณ๏ผˆ`[...slug]`๏ผ‰ใ‚’่€ƒๆ…ฎใ—ใพใ™ใ€‚ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใ€ใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใ€ใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใฏใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นใจใ—ใฆใƒžใƒƒใƒใ—๏ผˆใ™ในใฆใฎๅญใƒ‘ใ‚นใซๅฏพใ—ใฆใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–๏ผ‰ใ€ใƒšใƒผใ‚ธใฏๆญฃ็ขบใซใƒžใƒƒใƒใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ + +> ๆณจๆ„๏ผšใƒžใƒƒใƒใƒณใ‚ฐใซไฝฟ็”จใ•ใ‚Œใ‚‹ใƒ‘ใ‚นๅใฏใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใซ่กจ็คบใ•ใ‚Œใ‚‹URLใงใฏใชใใ€**ใ‚ตใƒผใƒใƒผใƒ‘ใ‚นๅ**๏ผˆใƒชใƒฉใ‚คใƒˆ้ฉ็”จๅพŒ๏ผ‰ใงใ™ใ€‚ + + +### ใ‚ฝใƒผใ‚นใƒ•ใ‚กใ‚คใƒซใ‚’้–‹ใ + + +ๅ„ใƒซใƒผใƒˆ่กŒใซใฏใƒ•ใ‚กใ‚คใƒซใ‚ฟใ‚คใƒ—ใ‚ขใ‚คใ‚ณใƒณ๏ผˆReact/JSXใ€TypeScriptใ€MDXใชใฉ๏ผ‰ใจ็›ธๅฏพใ‚ฝใƒผใ‚นใƒ‘ใ‚นใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ใ‚ฝใƒผใ‚นใƒ‘ใ‚นใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจVS Codeใงใƒ•ใ‚กใ‚คใƒซใŒ้–‹ใใพใ™ใ€‚ + + +### ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใƒซใƒผใƒˆ + + +ใƒ•ใ‚กใ‚คใƒซใƒซใƒผใ‚ฟใƒผใƒ„ใƒชใƒผใฎไธ‹ใซใ€ใ‚ณใƒผใƒ‰ๅ†…ใฎ``ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใงใƒซใƒผใƒˆใ‚’็™ป้Œฒใ—ใฆใ„ใ‚‹ๅ ดๅˆใ€**Component Routes**ใ‚ปใ‚ฏใ‚ทใƒงใƒณใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ๅ„ใ‚จใƒณใƒˆใƒชใซใฏใƒซใƒผใƒˆใƒ‘ใ‚ฟใƒผใƒณใ€ใ‚ฟใ‚คใƒ—๏ผˆserverใ€clientใ€fallback๏ผ‰ใ€`remote`ใ€`exact`ใ€`loading`ใชใฉใฎใƒ•ใƒฉใ‚ฐใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใƒซใƒผใƒˆใฏใƒžใƒƒใƒใ—ใŸใƒ‘ใ‚นใƒ‘ใƒฉใƒกใƒผใ‚ฟใŒใ‚คใƒณใƒฉใ‚คใƒณใง่กจ็คบใ•ใ‚Œใพใ™ใ€‚ + + +## Outletsใ‚ฟใƒ– + + +Outletsใ‚ฟใƒ–ใฏใƒšใƒผใ‚ธไธŠใซ็พๅœจใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ•ใ‚Œใฆใ„ใ‚‹ใ™ในใฆใฎๅๅ‰ไป˜ใใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใ‚’ไธ€่ฆง่กจ็คบใ—ใพใ™ใ€‚ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใจใฏใ€ใƒฌใ‚คใ‚ขใ‚ฆใƒˆใŒpropsใจใ—ใฆๅ—ใ‘ๅ–ใ‚‹ๅๅ‰ไป˜ใใ‚นใƒญใƒƒใƒˆ๏ผˆ`@sidebar`ใ€`@content`ใชใฉ๏ผ‰ใจใ€ใƒซใƒผใƒˆใฎใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ‚นใƒญใƒƒใƒˆใงใ‚ใ‚‹`PAGE_ROOT`ใงใ™ใ€‚ + +{/* Screenshot: the Outlets tab showing "Host URL: http://localhost:3000/products" at the top and a grid of outlet cards. One card shows "@sidebar" with a "router" badge and eye/refresh icons. Another shows "PAGE_ROOT" with a "static" badge. A colored dashed overlay is visible on the page behind the panel, highlighting the "@sidebar" region with a label. */} + + + +### ๅ„ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใ‚ซใƒผใƒ‰ใฎ่กจ็คบๅ†…ๅฎน + + +| ใƒ•ใ‚ฃใƒผใƒซใƒ‰ | ๆ„ๅ‘ณ | +|-----------|---------| +| Name | ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆ่ญ˜ๅˆฅๅญ | +| URL | ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใ‚ณใƒณใƒ†ใƒณใƒ„ใฎใƒญใƒผใƒ‰ๅ…ƒURL๏ผˆ่ฉฒๅฝ“ใ™ใ‚‹ๅ ดๅˆ๏ผ‰ | +| Badge | ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใฎ็ฎก็†ๆ–นๆณ•๏ผš`router`๏ผˆใƒ•ใ‚กใ‚คใƒซใƒซใƒผใ‚ฟใƒผ๏ผ‰ใ€`remote`๏ผˆๅˆฅใฎใ‚ตใƒผใƒใƒผใ‹ใ‚‰ๅ–ๅพ—๏ผ‰ใ€`live`๏ผˆใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐ๏ผ‰ใ€`defer`๏ผˆ้…ๅปถใƒญใƒผใƒ‰๏ผ‰ใ€`static` | + + +### ใƒšใƒผใ‚ธไธŠใฎใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใ‚’ใƒใ‚คใƒฉใ‚คใƒˆใ™ใ‚‹ + + +ไปปๆ„ใฎใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใ‚ซใƒผใƒ‰ใซใƒ›ใƒใƒผใ—ใพใ™ใ€‚ใƒšใƒผใ‚ธไธŠใฎใใฎใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใฎใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐ้ ˜ๅŸŸใฎๅ‘จใ‚Šใซใ€ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆๅใฎใƒฉใƒ™ใƒซไป˜ใใฎ่‰ฒไป˜ใ็ ด็ทšใฎ็ŸฉๅฝขใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ๅ„ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใซ็•ฐใชใ‚‹่‰ฒใŒๅ‰ฒใ‚Šๅฝ“ใฆใ‚‰ใ‚Œใ‚‹ใฎใงใ€ใ‚ซใƒผใƒ‰ใจใƒšใƒผใ‚ธใฎใ‚ปใ‚ฏใ‚ทใƒงใƒณใ‚’่ฆ–่ฆš็š„ใซใƒžใƒƒใƒ”ใƒณใ‚ฐใงใใพใ™ใ€‚ + +ใƒใ‚คใƒฉใ‚คใƒˆใ‚ชใƒผใƒใƒผใƒฌใ‚คใฏใ‚จใƒƒใ‚ธใ‚ฑใƒผใ‚นใ‚‚ๅ‡ฆ็†ใ—ใพใ™๏ผš้š ใ—ใƒžใƒผใ‚ซใƒผ่ฆ็ด ใ€่ค‡ๆ•ฐใฎๅ…„ๅผŸ่ฆ็ด ใจใ—ใฆใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ•ใ‚Œใ‚‹ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆ๏ผˆใ‚ชใƒผใƒใƒผใƒฌใ‚คใฏใƒฆใƒ‹ใ‚ชใƒณๅขƒ็•Œ็Ÿฉๅฝขใ‚’ใ‚ซใƒใƒผ๏ผ‰ใ€ใƒ•ใƒซใ‚นใ‚ฏใƒชใƒผใƒณใฎใƒใƒƒใ‚ฏใƒ‰ใƒญใƒƒใƒ—่ฆ็ด ๏ผˆๅฎŸ้š›ใฎใ‚ณใƒณใƒ†ใƒณใƒ„ใŒใƒใ‚คใƒฉใ‚คใƒˆใ•ใ‚Œใ‚‹ใ‚ˆใ†ใซใƒ•ใ‚ฃใƒซใ‚ฟใƒชใƒณใ‚ฐ๏ผ‰ใ€‚ + + +### ใ‚นใ‚ฏใƒญใƒผใƒซใจใƒชใƒ•ใƒฌใƒƒใ‚ทใƒฅ + + +| ใ‚ขใ‚ฏใ‚ทใƒงใƒณ | ๅ‹•ไฝœ | +|-----------|------| +| | ใƒšใƒผใ‚ธใ‚’ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใฎไฝ็ฝฎใซใ‚นใ‚ฏใƒญใƒผใƒซใ™ใ‚‹ | +| | ใƒšใƒผใ‚ธๅ…จไฝ“ใ‚’ใƒชใƒญใƒผใƒ‰ใ›ใšใซใใฎใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใฎใฟใ‚’ๅ†ใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ™ใ‚‹ โ€” ใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณใ›ใšใซใƒšใƒผใ‚ธใฎไธ€้ƒจๅˆ†ใฎใ‚ตใƒผใƒใƒผใ‚ตใ‚คใƒ‰ใฎๅค‰ๆ›ดใ‚’ใƒ†ใ‚นใƒˆใ—ใŸใ„ๅ ดๅˆใซไพฟๅˆฉ | + + +## Remotesใ‚ฟใƒ– + + +ใ‚ขใƒ—ใƒชใŒ``ใ‚’ไฝฟใฃใฆไป–ใฎreact-serverใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นใ‹ใ‚‰RSCใ‚ณใƒณใƒ†ใƒณใƒ„ใ‚’ใƒญใƒผใƒ‰ใ—ใฆใ„ใ‚‹ๅ ดๅˆใ€Remotesใ‚ฟใƒ–ใซๅ„ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ + +{/* Screenshot: the Remotes tab showing two remote component cards. Each card has a URL like "http://localhost:3001/widget", an outlet name, and property badges (TTL, live). Action buttons for external link, eye, and arrow-right are visible. */} + + +ๅ„ใ‚ซใƒผใƒ‰ใซใฏไปฅไธ‹ใŒ่กจ็คบใ•ใ‚Œใพใ™๏ผš + +| ใƒ•ใ‚ฃใƒผใƒซใƒ‰ | ๆ„ๅ‘ณ | +|-----------|---------| +| ใƒชใƒขใƒผใƒˆURL | ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใฎๅ–ๅพ—ๅ…ƒURLใ€‚ ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆๆ–ฐใ—ใ„ใƒ–ใƒฉใ‚ฆใ‚ถใ‚ฟใƒ–ใง้–‹ใ‘ใ‚‹ | +| ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆๅ | ใ“ใฎใƒชใƒขใƒผใƒˆใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใŒใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ•ใ‚Œใ‚‹ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆ | +| TTLใƒใƒƒใ‚ธ | ใƒฌใ‚นใƒใƒณใ‚นใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅๆœŸ้–“ | +| `isolate`ใƒใƒƒใ‚ธ | ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใŒใ‚ตใƒณใƒ‰ใƒœใƒƒใ‚ฏใ‚นใงใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ•ใ‚Œใ‚‹ | +| `defer`ใƒใƒƒใ‚ธ | ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใŒ้…ๅปถใƒญใƒผใƒ‰ใ•ใ‚Œใ‚‹ | +| `live`ใƒใƒƒใ‚ธ | ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใŒใƒชใ‚ขใƒซใ‚ฟใ‚คใƒ ๆ›ดๆ–ฐใ‚’ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใ™ใ‚‹ | + +ใƒ›ใƒใƒผใ™ใ‚‹ใจใƒšใƒผใ‚ธไธŠใฎใƒชใƒขใƒผใƒˆใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใŒใƒใ‚คใƒฉใ‚คใƒˆใ•ใ‚Œใพใ™ใ€‚ ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใ‚นใ‚ฏใƒญใƒผใƒซใ€ ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆOutletsใ‚ฟใƒ–ใฎใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใซใ‚ธใƒฃใƒณใƒ—ใ—ใพใ™ใ€‚ + + +## Liveใ‚ฟใƒ– + + +Liveใ‚ฟใƒ–ใฏใ™ในใฆใฎ`"use live"`ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆ โ€” ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใซ้€ฃ็ถš็š„ใชใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ‚’yieldใ™ใ‚‹ใ‚ตใƒผใƒใƒผไธŠใฎ้žๅŒๆœŸใ‚ธใ‚งใƒใƒฌใƒผใ‚ฟใƒผ้–ขๆ•ฐ โ€” ใ‚’็›ฃ่ฆ–ใ—ใพใ™ใ€‚ + +{/* Screenshot: the Live tab showing two component cards. The first shows "StatusPanel" with a green "running" badge, a "streaming" cyan badge, "yields: 42", "last yield: 2s ago", "started: 5m ago". The second shows "PriceTracker" with a violet "waiting" badge. */} + + +ๅ„ใ‚ซใƒผใƒ‰ใซใฏใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใฎๅๅ‰ใ€็พๅœจใฎ็Šถๆ…‹ใ€ใƒฉใƒณใ‚ฟใ‚คใƒ ็ตฑ่จˆใŒ่กจ็คบใ•ใ‚Œใพใ™๏ผš + +| ็Šถๆ…‹ | ๆ„ๅ‘ณ | +|-------|---------| +| `starting` | ใ‚ธใ‚งใƒใƒฌใƒผใ‚ฟใƒผใ‚’ๅˆๆœŸๅŒ–ไธญ | +| `waiting` | ๆฌกใฎyieldใพใงๅพ…ๆฉŸไธญ | +| `running` / `connected` | ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใซใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใซใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐไธญ | +| `finished` | ใ‚ธใ‚งใƒใƒฌใƒผใ‚ฟใƒผใŒๆญฃๅธธใซ็ต‚ไบ† | +| `aborted` | ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใŒๅˆ‡ๆ–ญ | +| `error` | ใ‚ธใ‚งใƒใƒฌใƒผใ‚ฟใƒผใŒไพ‹ๅค–ใ‚’ใ‚นใƒญใƒผ๏ผˆใ‚จใƒฉใƒผใƒกใƒƒใ‚ปใƒผใ‚ธใŒใ‚คใƒณใƒฉใ‚คใƒณใง่กจ็คบ๏ผ‰ | + +ใƒ‡ใƒผใ‚ฟ้€ไฟกไธญใฏ`streaming`ใƒใƒƒใ‚ธใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ใ‚ซใƒผใƒ‰ใซใฏใ‚ธใ‚งใƒใƒฌใƒผใ‚ฟใƒผใฎyieldๅ›žๆ•ฐใ€ๆœ€ๅพŒใฎyieldๆ™‚ๅˆปใ€ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใฎ้–‹ๅง‹ๆ™‚ๅˆปใ‚‚่กจ็คบใ•ใ‚Œใพใ™ใ€‚ใƒชใƒขใƒผใƒˆใ‚ตใƒผใƒใƒผใ‹ใ‚‰ใƒญใƒผใƒ‰ใ•ใ‚ŒใŸใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใซใฏ`remote`ใƒใƒƒใ‚ธใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ + + +## Workersใ‚ฟใƒ– + + +Workersใ‚ฟใƒ–ใฏ`"use worker"`ใ‚นใƒฌใƒƒใƒ‰ โ€” ใ‚ตใƒผใƒใƒผใ‚ตใ‚คใƒ‰ใฎWorker Threads๏ผˆNode.js๏ผ‰ใจใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚ตใ‚คใƒ‰ใฎWeb Workers โ€” ใ‚’่ฟฝ่ทกใ—ใพใ™ใ€‚ + +{/* Screenshot: the Workers tab with a toolbar showing "3 workers ยท 2 server ยท 1 client" and All/server/client filter buttons. Below, worker entries show type tags (server/client), state tags (Ready/Spawning), module paths, call counts, error counts, last function name, spawn time, and last call time. */} + + +ใƒ„ใƒผใƒซใƒใƒผใซใƒฏใƒผใ‚ซใƒผใฎ็ทๆ•ฐใŒใ‚ฟใ‚คใƒ—ๅˆฅใฎๅ†…่จณใจใจใ‚‚ใซ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใƒœใ‚ฟใƒณใงใ‚ตใƒผใƒใƒผใพใŸใฏใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใฎใƒฏใƒผใ‚ซใƒผใฎใฟใ‚’่กจ็คบใงใใพใ™ใ€‚ + +ๅ„ใƒฏใƒผใ‚ซใƒผใ‚จใƒณใƒˆใƒชใซใฏไปฅไธ‹ใŒ่กจ็คบใ•ใ‚Œใพใ™๏ผš + +| ใƒ•ใ‚ฃใƒผใƒซใƒ‰ | ๆ„ๅ‘ณ | +|-----------|---------| +| Type | `server`ใพใŸใฏ`client` | +| State | `Spawning`๏ผˆใ‚นใƒฌใƒƒใƒ‰่ตทๅ‹•ไธญ๏ผ‰ใ€`Ready`๏ผˆใ‚ขใ‚คใƒ‰ใƒซ๏ผ‰ใ€`Error`๏ผˆๆœ€ๅพŒใฎๅ‘ผใณๅ‡บใ—ใŒๅคฑๆ•—๏ผ‰ใ€`Restarting` | +| Module path | ใƒฏใƒผใ‚ซใƒผใ‚’ๅฎš็พฉใ™ใ‚‹ใƒ•ใ‚กใ‚คใƒซใ€‚ๆœ€ๅพŒใฎใ„ใใคใ‹ใฎใƒ‘ใ‚นใ‚ปใ‚ฐใƒกใƒณใƒˆใซ็Ÿญ็ธฎใ•ใ‚Œใ‚‹ | +| Call count | ๅˆ่จˆๅ‘ผใณๅ‡บใ—ๆ•ฐใจ็พๅœจๅฎŸ่กŒไธญใฎๆ•ฐ | +| Error / restart counts | ไธๅฎ‰ๅฎšใชใƒฏใƒผใ‚ซใƒผใ‚’็™บ่ฆ‹ใ™ใ‚‹ใŸใ‚ | +| Last function | ๆœ€ๅพŒใซๅ‘ผใณๅ‡บใ•ใ‚ŒใŸใ‚จใ‚ฏใ‚นใƒใƒผใƒˆ้–ขๆ•ฐใฎๅๅ‰ | +| Timing | ใƒฏใƒผใ‚ซใƒผใฎใ‚นใƒใƒผใƒณๆ™‚ๅˆปใจๆœ€็ต‚ๅ‘ผใณๅ‡บใ—ๆ™‚ๅˆป | + + +## Logsใ‚ฟใƒ– + + +Logsใ‚ฟใƒ–ใฏใ™ในใฆใฎใ‚ตใƒผใƒใƒผใ‚ฟใƒผใƒŸใƒŠใƒซๅ‡บๅŠ›๏ผˆ`stdout`ใจ`stderr`๏ผ‰ใ‚’ใƒ–ใƒฉใ‚ฆใ‚ถใซใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใ™ใ‚‹ใฎใงใ€ใ‚ฟใƒผใƒŸใƒŠใƒซใซๅˆ‡ใ‚Šๆ›ฟใˆใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚ + +{/* Screenshot: the Logs tab with a toolbar showing "All (156)" / "stdout (140)" / "stderr (16)" filter buttons, a search input, and a "Clear" button. The log list shows timestamped entries with colorful ANSI-rendered text (green "[vite]" prefix, yellow warnings). A few stderr entries are tagged in red. */} + + + +### ใƒญใ‚ฐใฎใƒ•ใ‚ฃใƒซใ‚ฟใƒชใƒณใ‚ฐ + + +| ใ‚ณใƒณใƒˆใƒญใƒผใƒซ | ไฝฟใ„ๆ–น | +|-------------|--------| +| ใ‚นใƒˆใƒชใƒผใƒ ใƒœใ‚ฟใƒณ | `stdout`ใพใŸใฏ`stderr`ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใใฎใ‚นใƒˆใƒชใƒผใƒ ใฎใฟ่กจ็คบใ™ใ‚‹ใ€‚ๅ„ใƒœใ‚ฟใƒณใฎใ‚ซใ‚ฆใƒณใƒˆใซใใฎใ‚ฟใ‚คใƒ—ใฎใ‚จใƒณใƒˆใƒชๆ•ฐใŒ่กจ็คบใ•ใ‚Œใ‚‹ | +| ๆคœ็ดขใƒ•ใ‚ฃใƒผใƒซใƒ‰ | ใƒ†ใ‚ญใ‚นใƒˆใ‚’ๅ…ฅๅŠ›ใ—ใฆใƒญใ‚ฐใ‚จใƒณใƒˆใƒชใ‚’ใƒ•ใ‚ฃใƒซใ‚ฟใƒชใƒณใ‚ฐใ™ใ‚‹ใ€‚ๆคœ็ดขใฏANSIใ‚ณใƒผใƒ‰ใ‚’้™คๅŽปใ—ใŸใƒ—ใƒฌใƒผใƒณใƒ†ใ‚ญใ‚นใƒˆใซๅฏพใ—ใฆใƒžใƒƒใƒใ™ใ‚‹ | + + +### ANSIๅ‡บๅŠ›ใฎ่กจ็คบ + + +ใ‚ตใƒผใƒใƒผใƒญใ‚ฐใซใฏ่‰ฒใ‚„ใƒ†ใ‚ญใ‚นใƒˆใ‚นใ‚ฟใ‚คใƒชใƒณใ‚ฐใฎใŸใ‚ใฎANSIใ‚จใ‚นใ‚ฑใƒผใƒ—ใ‚ณใƒผใƒ‰ใŒๅซใพใ‚Œใ‚‹ใ“ใจใŒใ‚ˆใใ‚ใ‚Šใพใ™ใ€‚Logsใ‚ฟใƒ–ใฏใ“ใ‚Œใ‚‰ใ‚’ๅฟ ๅฎŸใซใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ—ใพใ™ โ€” 4ใƒ“ใƒƒใƒˆใ€8ใƒ“ใƒƒใƒˆใ€24ใƒ“ใƒƒใƒˆ๏ผˆใƒˆใ‚ฅใƒซใƒผใ‚ซใƒฉใƒผ๏ผ‰ใ‚ทใƒผใ‚ฑใƒณใ‚นใŒใ™ในใฆใ‚ตใƒใƒผใƒˆใ•ใ‚Œใ€ๅคชๅญ—ใ€ใ‚คใ‚ฟใƒชใƒƒใ‚ฏใ€ไธ‹็ทšใ€่–„ๅญ—ใ‚‚ๅซใพใ‚Œใพใ™ใ€‚ใƒ‘ใƒใƒซใซ่กจ็คบใ•ใ‚Œใ‚‹ๅ†…ๅฎนใฏใ‚ฟใƒผใƒŸใƒŠใƒซใง่ฆ‹ใˆใ‚‹ใ‚‚ใฎใจๅŒใ˜ใงใ™ใ€‚ + + +### ่‡ชๅ‹•ใ‚นใ‚ฏใƒญใƒผใƒซใฎๅ‹•ไฝœ + + +ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงใฏใƒญใ‚ฐใƒ“ใƒฅใƒผใฏๆœ€ไธ‹้ƒจใซๅ›บๅฎšใ•ใ‚Œใ€ๆ–ฐใ—ใ„ใ‚จใƒณใƒˆใƒชใŒๅˆฐ็€ใ™ใ‚‹ใจใ‚นใ‚ฏใƒญใƒผใƒซใ—ใพใ™ใ€‚ไธŠใซใ‚นใ‚ฏใƒญใƒผใƒซใ™ใ‚‹ใจ่‡ชๅ‹•ใ‚นใ‚ฏใƒญใƒผใƒซใŒไธ€ๆ™‚ๅœๆญขใ—ใพใ™ใ€‚ใƒ‘ใƒใƒซใฎไธ‹้ƒจใซ**Scroll to bottom**ใƒœใ‚ฟใƒณใŒ่กจ็คบใ•ใ‚Œใพใ™ โ€” ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆๅ†้–‹ใ—ใพใ™ใ€‚ + +ใƒ„ใƒผใƒซใƒใƒผใฎ**Clear**ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใƒญใ‚ฐใƒใƒƒใƒ•ใ‚กใ‚’็ฉบใซใ—ใพใ™ใ€‚ + + +## ่ฆ็ด ใƒใ‚คใƒฉใ‚คใƒˆ + + +ใ„ใใคใ‹ใฎใ‚ฟใƒ–ใงใฏใ€ใ‚ขใ‚คใƒ†ใƒ ใซใƒ›ใƒใƒผใ—ใฆใƒšใƒผใ‚ธไธŠใฎๅฏพๅฟœใ™ใ‚‹่ฆ็ด ใ‚’ใƒใ‚คใƒฉใ‚คใƒˆใงใใพใ™๏ผš + +- **Payload**ใ‚ฟใƒ–ใงใ€ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆๅ‚็…งใซใƒ›ใƒใƒผใ™ใ‚‹ใจใ€ใใฎใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใŒใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ—ใŸDOM่ฆ็ด ใŒใƒใ‚คใƒฉใ‚คใƒˆใ•ใ‚Œใพใ™๏ผˆ็ท‘ใฎใ‚ชใƒผใƒใƒผใƒฌใ‚ค๏ผ‰ใ€‚ +- **Outlets**ใ‚ฟใƒ–ใงใ€ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใ‚ซใƒผใƒ‰ใซใƒ›ใƒใƒผใ™ใ‚‹ใจใ€ใใฎใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐ้ ˜ๅŸŸใŒใƒใ‚คใƒฉใ‚คใƒˆใ•ใ‚Œใพใ™๏ผˆๅ„ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใซๅ›บๆœ‰ใฎ่‰ฒ๏ผ‰ใ€‚ +- **Remotes**ใ‚ฟใƒ–ใงใ€ใƒชใƒขใƒผใƒˆใ‚ซใƒผใƒ‰ใซใƒ›ใƒใƒผใ™ใ‚‹ใจใ€ๅŒใ˜ๆ–นๆณ•ใงใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐ้ ˜ๅŸŸใŒใƒใ‚คใƒฉใ‚คใƒˆใ•ใ‚Œใพใ™ใ€‚ + +ใ‚ชใƒผใƒใƒผใƒฌใ‚คใฏๅๅ‰ใ‚’็คบใ™ใƒฉใƒ™ใƒซใƒใƒƒใ‚ธไป˜ใใฎ่‰ฒไป˜ใ็ ด็ทšใฎ็Ÿฉๅฝขใงใ™ใ€‚ใƒšใƒผใ‚ธใ‚’ใ‚นใ‚ฏใƒญใƒผใƒซใ‚„ใƒชใ‚ตใ‚คใ‚บใ™ใ‚‹ใจใƒชใ‚ขใƒซใ‚ฟใ‚คใƒ ใงไฝ็ฝฎใŒๆ›ดๆ–ฐใ•ใ‚Œใพใ™ใ€‚ + +{/* Screenshot: a page with the Outlets tab open. One outlet card is being hovered, and on the page a colored dashed rectangle with rounded corners outlines the outlet's rendered area, with a label badge reading "@sidebar" above it. */} + + + +## DevToolsใฎไป•็ต„ใฟ + + +ใƒ‘ใƒใƒซUIใฏ`/__react_server_devtools__/*`ใ‹ใ‚‰ๆไพ›ใ•ใ‚Œใ‚‹iframeๅ†…ใงๅฎŸ่กŒใ•ใ‚Œใพใ™ใ€‚ใ“ใ‚Œใซใ‚ˆใ‚ŠDevToolsใฎReactใƒ„ใƒชใƒผใŒใ‚ขใƒ—ใƒชใ‹ใ‚‰ๅˆ†้›ขใ•ใ‚Œใ€ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใฎ็Šถๆ…‹ใ‚„ใ‚นใ‚ฟใ‚คใƒชใƒณใ‚ฐใซๅนฒๆธ‰ใ—ใพใ›ใ‚“ใ€‚ + +3ใคใฎ้€šไฟกใƒใƒฃใƒใƒซใŒๅ„้ƒจๅˆ†ใ‚’ๆŽฅ็ถšใ—ใพใ™๏ผš + +- **ใƒ›ใ‚นใƒˆใƒšใƒผใ‚ธ โ†’ Iframe**๏ผˆ`postMessage`๏ผ‰๏ผšRSCใƒšใ‚คใƒญใƒผใƒ‰๏ผˆ`fetch`ใฎใ‚คใƒณใ‚ฟใƒผใ‚ปใƒ—ใƒˆใซใ‚ˆใ‚Šใ‚ญใƒฃใƒ—ใƒใƒฃ๏ผ‰ใ€ใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆ็™ป้Œฒใ€ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใƒซใƒผใƒˆใ€ใƒšใƒผใ‚ธใƒฌใƒ™ใƒซใฎใ‚ตใ‚คใ‚บ็ตฑ่จˆใ€ใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณใ‚คใƒ™ใƒณใƒˆใ€ใƒ†ใƒผใƒžๅค‰ๆ›ดใ€‚ +- **ใ‚ตใƒผใƒใƒผ โ†’ Iframe**๏ผˆSocket.IOใ€`/__devtools__`ใƒใƒผใƒ ใ‚นใƒšใƒผใ‚น๏ผ‰๏ผšใƒฉใ‚คใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใฎ็Šถๆ…‹ใ€ใ‚ตใƒผใƒใƒผใ‚ตใ‚คใƒ‰ใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚คใƒ™ใƒณใƒˆใ€ใƒฏใƒผใ‚ซใƒผใฎ็Šถๆ…‹ใ€ใ‚ตใƒผใƒใƒผใƒญใ‚ฐใ€ใƒ•ใ‚กใ‚คใƒซใƒซใƒผใ‚ฟใƒผใƒžใƒ‹ใƒ•ใ‚งใ‚นใƒˆใ€‚ใƒ‘ใƒใƒซใŒ้–‹ใใจ่‡ชๅ‹•็š„ใซๆŽฅ็ถšใŒ็ขบ็ซ‹ใ•ใ‚Œใพใ™ใ€‚ +- **Iframe โ†’ ใ‚ตใƒผใƒใƒผ**๏ผˆSocket.IO๏ผ‰๏ผšใ‚ญใƒฃใƒƒใ‚ทใƒฅ็„กๅŠนๅŒ–ใƒชใ‚ฏใ‚จใ‚นใƒˆใจใ‚ขใ‚ฆใƒˆใƒฌใƒƒใƒˆใƒชใƒ•ใƒฌใƒƒใ‚ทใƒฅใ‚ณใƒžใƒณใƒ‰ใŒๅŒใ˜ใ‚ฝใ‚ฑใƒƒใƒˆใ‚’้€šใ˜ใฆ้€ไฟกใ•ใ‚Œใพใ™ใ€‚ diff --git a/examples/pokemon/src/components/Modal.module.css b/examples/pokemon/src/components/Modal.module.css index 8dfb1b39..f4dc3c70 100644 --- a/examples/pokemon/src/components/Modal.module.css +++ b/examples/pokemon/src/components/Modal.module.css @@ -4,11 +4,11 @@ @apply block fixed inset-x-8 sm:inset-x-10 md:inset-x-20 lg:inset-x-40 xl:inset-x-1/4 top-20 max-h-[calc(100vh-10rem)] bg-white rounded-lg shadow-lg overflow-auto z-30 transition-all duration-300; } -.content:not(:empty)::after { +.content:has(> :not([hidden]))::after { @apply content-[''] block sticky bottom-0 w-full h-8 bg-gradient-to-t from-white; } -.content:empty, +.content:not(:has(> :not([hidden]))), .content.closing { @apply opacity-0 translate-y-[100vh] pointer-events-none; } @@ -17,6 +17,6 @@ @apply fixed inset-0 z-20 opacity-0 transition-all duration-300 pointer-events-none; } -.content:not(:empty):not(.closing) + .overlay { +.content:has(> :not([hidden])):not(.closing) + .overlay { @apply bg-black opacity-50 pointer-events-auto; } diff --git a/examples/pokemon/src/lib/pokemon.ts b/examples/pokemon/src/lib/pokemon.ts index 075c7761..9d7af277 100644 --- a/examples/pokemon/src/lib/pokemon.ts +++ b/examples/pokemon/src/lib/pokemon.ts @@ -17,10 +17,11 @@ export type Pokemon = { species: Species; }; -export async function getAllPokemons(): Promise { +export async function getAllPokemons(): Promise<{ name: string }[]> { "use cache"; - let pokemons: Pokemon[] = []; - let next = `https://pokeapi.co/api/v2/pokemon?limit=${process.env.POKEMON_LIMIT || 1000}`; + let pokemons: { name: string }[] = []; + let next: string | null = + `https://pokeapi.co/api/v2/pokemon-species?limit=${process.env.POKEMON_LIMIT || 1000}`; while (next) { const response = await fetch(next); if (!response.ok) { @@ -53,15 +54,13 @@ export async function getPokemons( pokemon.name.toLowerCase().includes(search.trim().toLowerCase()) ) : allPokemons; - const fullPokemons = await Promise.all( - filteredPokemons.map((pokemon) => getPokemon(pokemon.name)) - ); - const defaultPokemons = fullPokemons.filter( - (pokemon) => pokemon?.is_default + const page = filteredPokemons.slice(offset, offset + limit); + const data = await Promise.all( + page.map((pokemon) => getPokemon(pokemon.name)) ); return { - data: defaultPokemons.slice(offset, offset + limit), - count: defaultPokemons.length, + data, + count: filteredPokemons.length, }; } catch { return { data: [], count: 0 }; diff --git a/examples/remote/package.json b/examples/remote/package.json index 7b124f69..c6a2f4d0 100644 --- a/examples/remote/package.json +++ b/examples/remote/package.json @@ -6,11 +6,11 @@ "license": "ISC", "author": "", "scripts": { - "dev:host": "react-server ./index.jsx --port 3000 --name host", + "dev:host": "react-server ./index.jsx --port 3000 --name host --devtools", "dev:remote": "react-server ./remote.jsx --host ::1 --port 3001 --name remote", "dev:static": "react-server ./static.jsx --port 3002 --name static", "dev:streaming": "react-server ./streaming.jsx --port 3003 --name streaming", - "dev:live": "react-server ./live.jsx --port 3004 --name live", + "dev:live": "react-server ./live.jsx --port 3004 --name live --devtools", "dev:navigation": "react-server ./navigation.jsx --port 3005 --name navigation", "dev:form": "react-server ./form.jsx --port 3006 --name form", "dev:context": "react-server ./context.jsx --port 3007 --name context", diff --git a/packages/react-server/README.md b/packages/react-server/README.md index 15d851dc..8979a6eb 100644 --- a/packages/react-server/README.md +++ b/packages/react-server/README.md @@ -1,4 +1,4 @@ -![@lazarv/react-server](https://github.com/lazarv/react-server/blob/7f56153ae10f304a2777c652c82d394c7560cf91/docs/public/opengraph.jpg?raw=true "@lazarv/react-server") +![@lazarv/react-server](https://raw.githubusercontent.com/lazarv/react-server/refs/heads/main/docs/public/banner.png "@lazarv/react-server") Run [React](https://react.dev) anywhere diff --git a/packages/react-server/bin/commands/dev.mjs b/packages/react-server/bin/commands/dev.mjs index e0d2092c..023eb59d 100644 --- a/packages/react-server/bin/commands/dev.mjs +++ b/packages/react-server/bin/commands/dev.mjs @@ -31,6 +31,7 @@ export default (cli) => .option("-n, --name ", "[string] server name", { default: "react-server", }) + .option("--devtools", "enable built-in devtools", { default: false }) .option("--inspect", "enable inspector", { default: false }) .option("--mode ", "[string] mode", { default: "development" }) .action(async (...args) => { diff --git a/packages/react-server/cache/client.mjs b/packages/react-server/cache/client.mjs index 87ef2654..da89c89b 100644 --- a/packages/react-server/cache/client.mjs +++ b/packages/react-server/cache/client.mjs @@ -9,6 +9,16 @@ import { CACHE_KEY, CACHE_MISS, CACHE_PROVIDER } from "../server/symbols.mjs"; export { StorageCache, memoryDriver as default, CACHE_MISS }; +function emitCacheEvent(type, keys, provider, ttl) { + if (typeof window !== "undefined") { + window.dispatchEvent( + new CustomEvent("__react_server_cache_event__", { + detail: { type, keys, provider, ttl }, + }) + ); + } +} + // Stub for client/SSR โ€” the real implementation lives in cache/index.mjs // and relies on AsyncLocalStorage which is not available in the browser. export function getCacheContext() { @@ -143,6 +153,9 @@ async function useCacheAsync(keys, value, ttl, force, provider) { typeof value === "function" ? value() : value, ttl ); + emitCacheEvent(force ? "revalidate" : "miss", keys, provider.name, ttl); + } else { + emitCacheEvent("hit", keys, provider.name); } lock.delete(key); @@ -156,6 +169,13 @@ async function useCacheAsync(keys, value, ttl, force, provider) { } } +export async function invalidateExact(keys, provider) { + const cache = cacheInstances.get(provider ?? "default"); + if (cache) { + await cache.deleteExact(keys); + } +} + export function invalidate(key, provider) { if (provider && !cacheInstances.has(provider)) { console.warn( diff --git a/packages/react-server/cache/index.mjs b/packages/react-server/cache/index.mjs index 1b65bf16..2edce57a 100644 --- a/packages/react-server/cache/index.mjs +++ b/packages/react-server/cache/index.mjs @@ -15,6 +15,7 @@ import { CACHE_KEY, CACHE_MISS, CACHE_PROVIDER, + DEVTOOLS_CONTEXT, HTTP_CONTEXT, LOGGER_CONTEXT, MEMORY_CACHE_CONTEXT, @@ -23,6 +24,7 @@ import { REQUEST_CACHE_SHARED, } from "../server/symbols.mjs"; import { getTracer, getOtelContext } from "../server/telemetry.mjs"; +import { getRuntime } from "../server/runtime.mjs"; export { StorageCache, memoryDriver as default, CACHE_MISS }; @@ -48,6 +50,22 @@ export async function init$() { ); } const cache = cacheInstances.get("default"); + + // Register devtools invalidation handler + if (typeof import.meta.env !== "undefined" && import.meta.env.DEV) { + try { + const devtools = getRuntime(DEVTOOLS_CONTEXT); + devtools?.onCacheInvalidate(async (keys, provider) => { + const instance = cacheInstances.get(provider ?? "default"); + if (instance) { + await instance.deleteExact(keys); + } + }); + } catch { + // devtools not available + } + } + try { return context$(CACHE_CONTEXT, cache); } catch { @@ -56,6 +74,15 @@ export async function init$() { } export function dispose$(provider) { + // Bump devtools generation so next request's events replace old ones + if (provider === "request") { + try { + getRuntime(DEVTOOLS_CONTEXT)?.disposeRequestCache(); + } catch { + // devtools not available + } + } + if (provider && cacheInstances.has(provider)) { const cache = cacheInstances.get(provider); cacheInstances.delete(provider); @@ -87,6 +114,22 @@ export async function useCache( // cached values would leak across requests. let cache; if (provider?.name === "request") { + // โ”€โ”€ Devtools helper for request-scoped fast paths โ”€โ”€ + let _devtools; + function devtoolsRecord(type) { + try { + _devtools ??= getRuntime(DEVTOOLS_CONTEXT); + _devtools?.recordCacheEvent({ + type, + keys, + provider: "request", + ...(type !== "hit" ? { ttl: ttl ?? Infinity } : {}), + }); + } catch { + // devtools not available + } + } + cache = getContext(REQUEST_CACHE_CONTEXT); if (!cache) { // Edge SSR mode: no per-request StorageCache available (the SSR runs @@ -99,10 +142,12 @@ export async function useCache( const key = rawCanonicalKey(keys); const result = sharedCache.read(key); if (result !== CACHE_MISS) { + devtoolsRecord("hit"); return result; } } // Shared cache miss โ€” compute synchronously + devtoolsRecord("miss"); return typeof promise === "function" ? promise() : promise; } @@ -120,7 +165,10 @@ export async function useCache( // The stored value may be a thenable (async case) โ€” returning it // from this async function automatically awaits it. const hit = sharedCache.read(key); - if (hit !== CACHE_MISS) return hit; + if (hit !== CACHE_MISS) { + devtoolsRecord("hit"); + return hit; + } // Compute value โ€” synchronous when the plugin preserves the original // function's non-async nature (see use-cache-inline.mjs). @@ -146,6 +194,7 @@ export async function useCache( }); pending.status = "pending"; sharedCache.write(key, pending); + devtoolsRecord("miss"); return pending; } @@ -158,6 +207,7 @@ export async function useCache( // Fire-and-forget write to per-request StorageCache cache.set(keys, value, ttl ?? Infinity).catch(() => {}); + devtoolsRecord("miss"); return value; } } else if (provider) { @@ -255,9 +305,38 @@ export async function useCache( ttl: ttl ?? Infinity, provider, }); + + // โ”€โ”€ Devtools: record cache miss โ”€โ”€ + if (typeof import.meta.env !== "undefined" && import.meta.env.DEV) { + try { + const devtools = getRuntime(DEVTOOLS_CONTEXT); + devtools?.recordCacheEvent({ + type: force ? "revalidate" : "miss", + keys, + provider: providerName, + ttl: ttl ?? Infinity, + }); + } catch { + // devtools not available + } + } } else { cacheSpan.setAttribute("react_server.cache.hit", true); cacheSpan.updateName("Cache Hit"); + + // โ”€โ”€ Devtools: record cache hit โ”€โ”€ + if (typeof import.meta.env !== "undefined" && import.meta.env.DEV) { + try { + const devtools = getRuntime(DEVTOOLS_CONTEXT); + devtools?.recordCacheEvent({ + type: "hit", + keys, + provider: providerName, + }); + } catch { + // devtools not available + } + } } lock.delete(key); diff --git a/packages/react-server/client/ClientProvider.jsx b/packages/react-server/client/ClientProvider.jsx index 9b9575af..fc16163e 100644 --- a/packages/react-server/client/ClientProvider.jsx +++ b/packages/react-server/client/ClientProvider.jsx @@ -14,6 +14,7 @@ import { canNavigateClientOnly, hasLoadingForPath, loadRouteResources, + getAllRoutes, } from "./client-route-store.mjs"; import { runNavigationGuards } from "./client-navigation.mjs"; import { @@ -34,6 +35,7 @@ const activeChunk = new Map(); const cache = new Map(); const listeners = new Map(); const outlets = new Map(); +const outletMeta = new Map(); const outletAbortControllers = new Map(); const prefetching = new Map(); const flightCache = new Map(); @@ -41,6 +43,24 @@ const liveOutlets = new Set(); const liveIO = new Map(); const outletTemporaryReferences = new Map(); +if (import.meta.env.DEV) { + window.__react_server_devtools_outlets__ = () => + Array.from(outlets.entries()).map(([name, url]) => { + const meta = outletMeta.get(name) || {}; + return { + name, + url: typeof url === "string" ? url : (url?.toString?.() ?? ""), + remote: meta.remote || false, + live: meta.live || false, + defer: meta.defer || false, + isolate: meta.isolate || false, + ttl: meta.ttl ?? null, + }; + }); + window.__react_server_devtools_routes__ = getAllRoutes; + window.__react_server_devtools_refresh__ = (...args) => refresh(...args); +} + const connectLiveIO = async (origin) => { if (!liveIO.has(origin)) { liveIO.set( @@ -74,9 +94,18 @@ const registerOutlet = ( remote, remoteProps, defer, - live = false + live = false, + isolate = false, + ttl ) => { outlets.set(outlet, url); + outletMeta.set(outlet, { + remote: !!remote, + defer: !!defer, + live: !!live, + isolate: !!isolate, + ttl: ttl ?? null, + }); if (live) { liveOutlets.add(outlet); connectLiveIO(typeof live === "string" ? live : url.origin).then( @@ -152,6 +181,7 @@ const registerOutlet = ( } return () => { outlets.delete(outlet); + outletMeta.delete(outlet); liveOutlets.delete(outlet); outletTemporaryReferences.delete(outlet); }; diff --git a/packages/react-server/client/ClientRouteGuard.jsx b/packages/react-server/client/ClientRouteGuard.jsx index a1b4454b..dcc3b887 100644 --- a/packages/react-server/client/ClientRouteGuard.jsx +++ b/packages/react-server/client/ClientRouteGuard.jsx @@ -1,12 +1,20 @@ "use client"; -import { Activity, Suspense, createElement, useEffect, useMemo } from "react"; +import { + Activity, + Suspense, + createElement, + useContext, + useEffect, + useMemo, +} from "react"; import { match } from "../lib/route-match.mjs"; import { registerServerRoute, registerRouteResources, } from "./client-route-store.mjs"; +import { FlightContext } from "./context.mjs"; import { usePathname, usePendingNavigation, @@ -33,13 +41,17 @@ export default function ClientRouteGuard({ [loadingComponent, loadingElement] ); + const { remote, outlet } = useContext(FlightContext); + useEffect(() => { return registerServerRoute(path, { exact, fallback: fallback ?? false, hasLoading: !!(loadingComponent || loadingElement), + remote: remote || false, + outlet: outlet || null, }); - }, [path, exact, fallback, loadingComponent, loadingElement]); + }, [path, exact, fallback, loadingComponent, loadingElement, remote, outlet]); // Register route-resource bindings for client-only navigation. // Flatten: each entry may be an array (resolved client reference) @@ -61,13 +73,18 @@ export default function ClientRouteGuard({ // it's always correct for the tree being rendered. // For client-only navigation, pushState fires emitLocationChange // synchronously, so clientPathname is always in sync. + // For remote components, window.location.pathname reflects the host app, + // not the remote's URL โ€” always trust serverPathname in that case. const clientPathname = usePathname(); const browserPathname = typeof window !== "undefined" ? decodeURIComponent(window.location.pathname) : serverPathname; - const pathname = - clientPathname !== browserPathname ? serverPathname : clientPathname; + const pathname = remote + ? serverPathname + : clientPathname !== browserPathname + ? serverPathname + : clientPathname; // Fallback routes (global or scoped) โ€” active when no other route matches. // On the client, treat them as always active (server already determined the match). // Scoped fallbacks have a path ending with "/*" โ€” match via prefix. diff --git a/packages/react-server/client/ClientRouteRegistration.jsx b/packages/react-server/client/ClientRouteRegistration.jsx index b12733a4..cd7b11f1 100644 --- a/packages/react-server/client/ClientRouteRegistration.jsx +++ b/packages/react-server/client/ClientRouteRegistration.jsx @@ -4,6 +4,7 @@ import { Activity, Suspense, createElement, + useContext, useEffect, useMemo, useRef, @@ -17,6 +18,7 @@ import { registerRouteResources, isFallbackActive, } from "./client-route-store.mjs"; +import { FlightContext } from "./context.mjs"; import { usePathname, @@ -100,14 +102,22 @@ export default function ClientRouteRegistration({ [loadingComponent, loadingElement] ); + const { remote, outlet } = useContext(FlightContext); + // Register the route in the client store. // For fallback routes, also trigger a re-render via state so the component // transitions from server-rendered content to client-managed behaviour. useEffect(() => { hydrated.current = true; if (fallback) setIsHydrated(true); - return registerClientRoute(path, { exact, component, fallback }); - }, [path, exact, component, fallback]); + return registerClientRoute(path, { + exact, + component, + fallback, + remote: remote || false, + outlet: outlet || null, + }); + }, [path, exact, component, fallback, remote, outlet]); // Register route-resource bindings for client-only navigation. // `resources` may be: @@ -134,13 +144,18 @@ export default function ClientRouteRegistration({ // Determine which pathname to trust for visibility (same logic as // ClientRouteGuard โ€” see comments there for full explanation). + // For remote components, window.location.pathname reflects the host app, + // not the remote's URL โ€” always trust serverPathname in that case. const clientPathname = usePathname(); const browserPathname = typeof window !== "undefined" ? decodeURIComponent(window.location.pathname) : serverPathname; - const pathname = - clientPathname !== browserPathname ? serverPathname : clientPathname; + const pathname = remote + ? serverPathname + : clientPathname !== browserPathname + ? serverPathname + : clientPathname; // Fallback routes are active only after hydration (when route maps are // populated by effects). During SSR the maps are empty, so we skip. diff --git a/packages/react-server/client/ReactServerComponent.jsx b/packages/react-server/client/ReactServerComponent.jsx index 7723b608..e65239c9 100644 --- a/packages/react-server/client/ReactServerComponent.jsx +++ b/packages/react-server/client/ReactServerComponent.jsx @@ -90,6 +90,7 @@ function FlightComponent({ defer = false, isolate = false, live = false, + ttl, request, remoteProps = {}, children, @@ -149,7 +150,9 @@ function FlightComponent({ remote, remoteProps, defer, - live + live, + isolate, + ttl ); const unsubscribe = subscribe( outlet || url, @@ -409,6 +412,7 @@ export default function ReactServerComponent({ isolate, request, live, + ttl, remoteProps = {}, children, }) { @@ -446,6 +450,7 @@ export default function ReactServerComponent({ () => ({ url: contextUrl, outlet, + remote: remote || false, live, refresh: refreshFn, prefetch: prefetchFn, @@ -456,6 +461,7 @@ export default function ReactServerComponent({ [ contextUrl, outlet, + remote, live, refreshFn, prefetchFn, @@ -467,6 +473,9 @@ export default function ReactServerComponent({ return ( + {import.meta.env?.DEV && outlet && outlet !== PAGE_ROOT ? ( + ); } diff --git a/packages/react-server/client/client-route-store.mjs b/packages/react-server/client/client-route-store.mjs index 76558fe1..a5e4a5b2 100644 --- a/packages/react-server/client/client-route-store.mjs +++ b/packages/react-server/client/client-route-store.mjs @@ -7,23 +7,26 @@ const serverRoutes = new Map(); const clientFallbackRoutes = new Map(); // path -> { component } (path = "/user/*" or "*") const routeResources = new Map(); // path -> [{ resource, mapFn }] -export function registerClientRoute(path, { exact, component, fallback }) { +export function registerClientRoute( + path, + { exact, component, fallback, remote = false, outlet = null } +) { if (fallback) { const key = path || "*"; // global fallback uses "*" - clientFallbackRoutes.set(key, { component }); + clientFallbackRoutes.set(key, { component, remote, outlet }); return () => { clientFallbackRoutes.delete(key); }; } - clientRoutes.set(path, { exact, component }); + clientRoutes.set(path, { exact, component, remote, outlet }); return () => clientRoutes.delete(path); } export function registerServerRoute( path, - { exact, fallback = false, hasLoading = false } + { exact, fallback = false, hasLoading = false, remote = false, outlet = null } ) { - serverRoutes.set(path, { exact, fallback, hasLoading }); + serverRoutes.set(path, { exact, fallback, hasLoading, remote, outlet }); return () => serverRoutes.delete(path); } @@ -128,6 +131,38 @@ export function getClientRoutes() { return clientRoutes; } +export function getAllRoutes() { + const routes = []; + for (const [path, route] of serverRoutes) { + routes.push({ + path: path || "/", + type: route.fallback ? "fallback" : "server", + exact: route.exact, + hasLoading: route.hasLoading, + remote: route.remote || false, + outlet: route.outlet || null, + }); + } + for (const [path, route] of clientRoutes) { + routes.push({ + path, + type: "client", + exact: route.exact, + remote: route.remote || false, + outlet: route.outlet || null, + }); + } + for (const [key, route] of clientFallbackRoutes) { + routes.push({ + path: key === "*" ? "*" : key, + type: "fallback", + remote: route.remote || false, + outlet: route.outlet || null, + }); + } + return routes; +} + /** * Register resource bindings for a route. * Called from client code to enable resource loading on client-only navigation. diff --git a/packages/react-server/client/worker-proxy.mjs b/packages/react-server/client/worker-proxy.mjs index 97424b68..a21ff247 100644 --- a/packages/react-server/client/worker-proxy.mjs +++ b/packages/react-server/client/worker-proxy.mjs @@ -1,5 +1,29 @@ import { toStream, fromStream } from "@lazarv/react-server/rsc/browser"; +// Global registry so devtools can poll for client workers regardless of timing. +const registry = + typeof globalThis !== "undefined" + ? (globalThis.__react_server_devtools_client_workers__ = + globalThis.__react_server_devtools_client_workers__ || new Map()) + : new Map(); + +function getOrCreateEntry(id) { + if (!registry.has(id)) { + registry.set(id, { + id, + type: "client", + state: "ready", + invocations: 0, + activeInvocations: 0, + errors: 0, + spawnedAt: Date.now(), + lastInvokedAt: null, + lastFn: null, + }); + } + return registry.get(id); +} + export default function createWorkerProxy(workerModuleId, mode) { return (fn) => { let worker; @@ -12,19 +36,37 @@ export default function createWorkerProxy(workerModuleId, mode) { type: "module", } ); + + getOrCreateEntry(workerModuleId); } return (...args) => { return new Promise(async (resolve, reject) => { const messageId = crypto.randomUUID(); + const entry = getOrCreateEntry(workerModuleId); + entry.invocations++; + entry.activeInvocations++; + entry.lastInvokedAt = Date.now(); + entry.lastFn = fn; + function handleMessage(event) { const { id, result, error } = event.data; if (id === messageId) { worker.removeEventListener("message", handleMessage); if (error) { + entry.activeInvocations = Math.max( + 0, + entry.activeInvocations - 1 + ); + entry.errors++; + entry.lastError = error; reject(new Error(error)); } else { + entry.activeInvocations = Math.max( + 0, + entry.activeInvocations - 1 + ); resolve(result ? fromStream(result) : undefined); } } diff --git a/packages/react-server/devtools/app/actions.mjs b/packages/react-server/devtools/app/actions.mjs new file mode 100644 index 00000000..04eaec89 --- /dev/null +++ b/packages/react-server/devtools/app/actions.mjs @@ -0,0 +1,14 @@ +"use server"; + +import { invalidate } from "@lazarv/react-server/memory-cache"; +import { getRuntime } from "@lazarv/react-server"; +import { DEVTOOLS_CONTEXT } from "../context.mjs"; + +export async function invalidateEntry(key, provider) { + await invalidate(key, provider); +} + +export async function clearCacheProvider(providerName) { + const devtools = getRuntime(DEVTOOLS_CONTEXT); + await devtools?.clearProvider?.(providerName); +} diff --git a/packages/react-server/devtools/app/index.jsx b/packages/react-server/devtools/app/index.jsx new file mode 100644 index 00000000..c6773e35 --- /dev/null +++ b/packages/react-server/devtools/app/index.jsx @@ -0,0 +1,42 @@ +import { Suspense } from "react"; + +import { cookie } from "@lazarv/react-server/server/cookies.mjs"; +import DevToolsShell from "../client/DevToolsShell.jsx"; +import StatusPanel from "./panels/StatusPanel.jsx"; +import RouteInspector from "./panels/RouteInspector.jsx"; + +// Inline script that runs before React hydrates to set the dark class, +// preventing a light โ†’ dark flash. Reads the parent page's cookie. +const THEME_SCRIPT = `(function(){try{var d=parent.document.cookie;if(d.indexOf("dark=1")!==-1){document.documentElement.classList.add("dark")}else if(d.indexOf("dark=0")===-1&&matchMedia("(prefers-color-scheme:dark)").matches){document.documentElement.classList.add("dark")}}catch(e){}})()`; + +export default async function DevToolsApp() { + // Read cookie on the server so SSR output already has the right theme + const cookies = cookie(); + const serverDark = cookies.dark === "1"; + + // Fetch route manifest on the server so we can pass it as serializable data + const routeManifest = await RouteInspector.getManifest(); + + return ( + + +