From 438ee7abfd6523eb5aa2ad8d1aa789dbb4645f8a Mon Sep 17 00:00:00 2001 From: Chris Coyier Date: Tue, 11 Jun 2024 11:09:42 -0700 Subject: [PATCH 001/983] Update to CodePen URL (#6452) --- src/content/learn/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/installation.md b/src/content/learn/installation.md index c5426ea94..7251fc31b 100644 --- a/src/content/learn/installation.md +++ b/src/content/learn/installation.md @@ -37,7 +37,7 @@ export default function App() { You can edit it directly or open it in a new tab by pressing the "Fork" button in the upper right corner. -Most pages in the React documentation contain sandboxes like this. Outside of the React documentation, there are many online sandboxes that support React: for example, [CodeSandbox](https://codesandbox.io/s/new), [StackBlitz](https://stackblitz.com/fork/react), or [CodePen.](https://codepen.io/pen?&editors=0010&layout=left&prefill_data_id=3f4569d1-1b11-4bce-bd46-89090eed5ddb) +Most pages in the React documentation contain sandboxes like this. Outside of the React documentation, there are many online sandboxes that support React: for example, [CodeSandbox](https://codesandbox.io/s/new), [StackBlitz](https://stackblitz.com/fork/react), or [CodePen.](https://codepen.io/pen?template=QWYVwWN) ### Try React locally {/*try-react-locally*/} From c116c426d197aed6ebafe7f8455f90e3242be628 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Wed, 12 Jun 2024 10:28:39 +0200 Subject: [PATCH 002/983] Remove mention of Suspensey ` @@ -27,7 +27,7 @@ React의 ` @@ -38,16 +38,16 @@ React의 ` +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, ""]] +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, ); +``` + +This will attach event listeners to the static server-generated HTML and make it interactive. + + + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + + + My app + + + ... + + ); +} +``` + +On the server, render `` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const {prelude} = await prerender(, { + bootstrapScripts: [assetMap['/main.js']] + }); + return new Response(prelude, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +Since your server is now rendering ``, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const {prelude} = await prerender(, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['/main.js']], + }); + return new Response(prelude, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline ` +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, ""]] +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, ); +``` + +This will attach event listeners to the static server-generated HTML and make it interactive. + + + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + + + My app + + + ... + + ); +} +``` + +On the server, render `` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', async (request, response) => { + const { prelude } = await prerenderToNodeStream(, { + bootstrapScripts: [assetMap['/main.js']] + }); + + response.setHeader('Content-Type', 'text/html'); + prelude.pipe(response); +}); +``` + +Since your server is now rendering ``, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', async (request, response) => { + const { prelude } = await prerenderToNodeStream(, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['/main.js']], + }); + + response.setHeader('Content-Type', 'text/html'); + prelude.pipe(response); +}); +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline ` ``` -클라이언트에선, 추가된 bootstrap 스크립트는 [`hydrateRoot`를 호출해 `document` 전체를 hydrate 해야합니다:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) +클라이언트에선, 추가된 bootstrap 스크립트는 [`hydrateRoot`를 호출해 `document` 전체를 hydrate 해야합니다](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document). ```js [[1, 4, ""]] import { hydrateRoot } from 'react-dom/client'; @@ -139,9 +139,9 @@ hydrateRoot(document, ); #### 빌드 결과물에서 CSS와 JS의 경로 읽어오기 {/*reading-css-and-js-asset-paths-from-the-build-output*/} -JS와 CSS같은 최종 에셋들에 대한 URL들은 종종 빌드 후에 해시됩니다. 예를 들어, `styles.css` 대신 `styles.123456.css`와 같은 형태로 끝날 수 있습니다. 에셋들의 파일명을 해시하는 것은 모든 빌드의 결과물이 각각 다른 파일명을 가지도록 보장합니다. 이는 정적 에셋들에 대한 장기 캐싱을 안전하게 활성화할 수 있도록 해줍니다: 즉, 특정 이름의 파일 내용은 절대 바뀌지 않는 다는 것을 보장합니다. +JS와 CSS같은 최종 에셋들에 대한 URL들은 종종 빌드 후에 해시됩니다. 예를 들어, `styles.css` 대신 `styles.123456.css`와 같은 형태로 끝날 수 있습니다. 에셋들의 파일명을 해시하는 것은 모든 빌드의 결과물이 각각 다른 파일명을 가지도록 보장합니다. 이는 정적 에셋들에 대한 장기 캐싱을 안전하게 활성화할 수 있도록 해줍니다. 즉, 특정 이름의 파일 내용은 절대 바뀌지 않는 다는 것을 보장합니다. -하지만, 빌드 후에 에셋들의 URL을 알 수 없다면, 소스 코드에 URL을 넣을 수 없습니다. 예를 들어, JSX에 `"/styles.css"`를 하드코딩하는 것은 작동하지 않습니다. 소스 코드에 URL을 넣지 않으려면, 루트 컴포넌트는 props로 전달된 맵에서 실제 파일명을 읽어야합니다: +하지만, 빌드 후에 에셋들의 URL을 알 수 없다면, 소스 코드에 URL을 넣을 수 없습니다. 예를 들어, JSX에 `"/styles.css"`를 하드코딩하는 것은 작동하지 않습니다. 소스 코드에 URL을 넣지 않으려면, 루트 컴포넌트는 props로 전달된 맵에서 실제 파일명을 읽어야합니다. ```js {1,6} export default function App({ assetMap }) { @@ -157,7 +157,7 @@ export default function App({ assetMap }) { } ``` -서버에선 ``을 렌더링하고, 에셋 URL들과 함께 `assetMap`을 전달합니다: +서버에선 ``을 렌더링하고, 에셋 URL들과 함께 `assetMap`을 전달합니다. ```js {1-5,8,9} // 빌드 도구로부터 이 JSON을 얻어야합니다. 예를 들어, 빌드 결과물에서 읽어올 수 있습니다. @@ -176,7 +176,7 @@ async function handler(request) { } ``` -서버가 ``를 렌더링한 이후엔, 클라이언트에서도 hydration 에러를 피하기 위해 `assetMap`과 함께 렌더링해야합니다. `assetMap`을 직렬화하고 클라이언트에 전달하기 위해 다음과 같이 할 수 있습니다: +서버가 ``를 렌더링한 이후엔, 클라이언트에서도 hydration 오류를 피하기 위해 `assetMap`과 함께 렌더링해야합니다. `assetMap`을 직렬화하고 클라이언트에 전달하기 위해 다음과 같이 할 수 있습니다. ```js {9-10} // 빌드 도구로부터 이 JSON을 얻어야합니다. @@ -197,7 +197,7 @@ async function handler(request) { } ``` -위의 예시에서, `bootstrapScriptContent` 옵션은 클라이언트에서 `window.assetMap` 전역 변수를 설정하는 인라인 ` + + ); +} diff --git a/src/components/DevContentRefresher.tsx b/src/components/DevContentRefresher.tsx new file mode 100644 index 000000000..31a0961ed --- /dev/null +++ b/src/components/DevContentRefresher.tsx @@ -0,0 +1,29 @@ +'use client'; + +import {useRouter} from 'next/navigation'; +import {useRef, useEffect} from 'react'; + +export function DevContentRefresher() { + const router = useRouter(); + const wsRef = useRef(null); + + useEffect(() => { + wsRef.current = new WebSocket('ws://localhost:3001'); + + wsRef.current.onmessage = (event) => { + const message = JSON.parse(event.data); + + if (message.event === 'refresh') { + console.log('Refreshing content...'); + // @ts-ignore + router.hmrRefresh(); // Triggers client-side refresh + } + }; + + return () => { + wsRef.current?.close(); + }; + }, [router]); + + return null; +} diff --git a/src/components/ErrorDecoderProvider.tsx b/src/components/ErrorDecoderProvider.tsx new file mode 100644 index 000000000..bad1ed2d0 --- /dev/null +++ b/src/components/ErrorDecoderProvider.tsx @@ -0,0 +1,19 @@ +'use client'; + +import {ErrorDecoderContext} from './ErrorDecoderContext'; + +export function ErrorDecoderProvider({ + children, + errorMessage, + errorCode, +}: { + children: React.ReactNode; + errorMessage: string | null; + errorCode: string | null; +}) { + return ( + + {children} + + ); +} diff --git a/src/components/Layout/Feedback.tsx b/src/components/Layout/Feedback.tsx index 34db728ce..16b974c10 100644 --- a/src/components/Layout/Feedback.tsx +++ b/src/components/Layout/Feedback.tsx @@ -3,12 +3,12 @@ */ import {useState} from 'react'; -import {useRouter} from 'next/router'; import cn from 'classnames'; +import {usePathname} from 'next/navigation'; export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) { - const {asPath} = useRouter(); - const cleanedPath = asPath.split(/[\?\#]/)[0]; + const pathname = usePathname(); + const cleanedPath = pathname.split(/[\?\#]/)[0]; // Reset on route changes. return ; } diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index 24d379589..ea0c53e1c 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -1,16 +1,16 @@ +'use client'; + /* * Copyright (c) Facebook, Inc. and its affiliates. */ -import {Suspense} from 'react'; import * as React from 'react'; -import {useRouter} from 'next/router'; +import {Suspense} from 'react'; import {SidebarNav} from './SidebarNav'; import {Footer} from './Footer'; import {Toc} from './Toc'; -// import SocialBanner from '../SocialBanner'; import {DocsPageFooter} from 'components/DocsFooter'; -import {Seo} from 'components/Seo'; + import PageHeading from 'components/PageHeading'; import {getRouteMeta} from './getRouteMeta'; import {TocContext} from '../MDX/TocContext'; @@ -20,8 +20,8 @@ import type {RouteItem} from 'components/Layout/getRouteMeta'; import {HomeContent} from './HomeContent'; import {TopNav} from './TopNav'; import cn from 'classnames'; -import Head from 'next/head'; +// Prefetch the code block component import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock'); interface PageProps { @@ -36,6 +36,7 @@ interface PageProps { }; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; languages?: Languages | null; + pathname: string; } export function Page({ @@ -44,11 +45,11 @@ export function Page({ routeTree, meta, section, + pathname, languages = null, }: PageProps) { - const {asPath} = useRouter(); - const cleanedPath = asPath.split(/[\?\#]/)[0]; - const {route, nextRoute, prevRoute, breadcrumbs, order} = getRouteMeta( + const cleanedPath = pathname.split(/[\?\#]/)[0]; + const {route, nextRoute, prevRoute, breadcrumbs} = getRouteMeta( cleanedPath, routeTree ); @@ -113,31 +114,17 @@ export function Page({ showSidebar = false; } - let searchOrder; - if (section === 'learn' || (section === 'blog' && !isBlogIndex)) { - searchOrder = order; - } - return ( <> - {(isHomePage || isBlogIndex) && ( - - - + // RSS Feed link is now handled by metadata in layout.tsx + )} - {/**/}
-
+
{content}
- {showToc && toc.length > 0 && } + {showToc && toc.length > 0 && }
diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 72003df74..f67b0ed2b 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -5,12 +5,12 @@ import {useRef, useLayoutEffect, Fragment} from 'react'; import cn from 'classnames'; -import {useRouter} from 'next/router'; import {SidebarLink} from './SidebarLink'; import {useCollapse} from 'react-collapsed'; import usePendingRoute from 'hooks/usePendingRoute'; import type {RouteItem} from 'components/Layout/getRouteMeta'; import {siteConfig} from 'siteConfig'; +import {usePathname} from 'next/navigation'; interface SidebarRouteTreeProps { isForceExpanded: boolean; @@ -77,7 +77,7 @@ export function SidebarRouteTree({ routeTree, level = 0, }: SidebarRouteTreeProps) { - const slug = useRouter().asPath.split(/[\?\#]/)[0]; + const slug = usePathname().split(/[\?\#]/)[0]; const pendingRoute = usePendingRoute(); const currentRoutes = routeTree.routes as RouteItem[]; return ( diff --git a/src/components/Layout/Toc.tsx b/src/components/Layout/Toc.tsx index 5308c602c..a8d269898 100644 --- a/src/components/Layout/Toc.tsx +++ b/src/components/Layout/Toc.tsx @@ -11,7 +11,11 @@ export function Toc({headings}: {headings: Toc}) { // TODO: We currently have a mismatch between the headings in the document // and the headings we find in MarkdownPage (i.e. we don't find Recap or Challenges). // Select the max TOC item we have here for now, but remove this after the fix. - const selectedIndex = Math.min(currentIndex, headings.length - 1); + const selectedIndex = + currentIndex !== undefined + ? Math.min(currentIndex, headings.length - 1) + : -1; + return (