From 110004717ed7e1f6f11d87dc344a7019436c4007 Mon Sep 17 00:00:00 2001 From: xiaoyu2er Date: Wed, 21 May 2025 21:10:41 +0000 Subject: [PATCH 1/2] docs: update documentation translations --- .../01-next-config-js/assetPrefix.mdx | 19 +- .../05-config/01-next-config-js/headers.mdx | 105 +- .../01-next-config-js/htmlLimitedBots.mdx | 20 +- .../incrementalCacheHandlerPath.mdx | 42 +- .../05-config/01-next-config-js/redirects.mdx | 101 +- .../05-config/01-next-config-js/rewrites.mdx | 99 +- .../01-routing/01-defining-routes.mdx | 60 + .../01-routing/02-pages-and-layouts.mdx | 275 ++++ .../01-routing/03-linking-and-navigating.mdx | 246 ++++ .../01-routing/04-route-groups.mdx | 80 ++ .../01-routing/05-dynamic-routes.mdx | 127 ++ .../06-loading-ui-and-streaming.mdx | 182 +++ .../01-routing/07-error-handling.mdx | 228 ++++ .../01-routing/08-parallel-routes.mdx | 328 +++++ .../01-routing/09-intercepting-routes.mdx | 81 ++ .../01-routing/10-route-handlers.mdx | 692 ++++++++++ .../01-routing/11-middleware.mdx | 401 ++++++ .../01-routing/12-colocation.mdx | 182 +++ .../01-routing/13-internationalization.mdx | 154 +++ .../01-routing/index.mdx | 162 +++ .../01-fetching-caching-and-revalidating.mdx | 375 ++++++ .../02-data-fetching/02-patterns.mdx | 314 +++++ .../03-forms-and-mutations.mdx | 983 ++++++++++++++ .../02-data-fetching/index.mdx | 6 + .../03-rendering/01-server-components.mdx | 138 ++ .../03-rendering/02-client-components.mdx | 108 ++ .../03-rendering/03-composition-patterns.mdx | 556 ++++++++ .../04-edge-and-nodejs-runtimes.mdx | 86 ++ .../03-rendering/index.mdx | 80 ++ .../04-caching/index.mdx | 603 +++++++++ .../05-styling/01-css-modules.mdx | 297 ++++ .../05-styling/02-tailwind-css.mdx | 183 +++ .../05-styling/03-css-in-js.mdx | 313 +++++ .../05-styling/04-sass.mdx | 82 ++ .../05-styling/index.mdx | 18 + .../06-optimizing/01-images.mdx | 359 +++++ .../06-optimizing/02-fonts.mdx | 644 +++++++++ .../06-optimizing/03-scripts.mdx | 377 ++++++ .../06-optimizing/04-metadata.mdx | 369 +++++ .../06-optimizing/05-static-assets.mdx | 37 + .../06-optimizing/06-lazy-loading.mdx | 246 ++++ .../06-optimizing/07-analytics.mdx | 211 +++ .../06-optimizing/08-open-telemetry.mdx | 330 +++++ .../06-optimizing/09-instrumentation.mdx | 96 ++ .../06-optimizing/index.mdx | 48 + .../07-configuring/01-typescript.mdx | 304 +++++ .../07-configuring/02-eslint.mdx | 301 +++++ .../03-environment-variables.mdx | 184 +++ ...04-absolute-imports-and-module-aliases.mdx | 167 +++ .../07-configuring/05-mdx.mdx | 453 +++++++ .../07-configuring/06-src-directory.mdx | 34 + .../07-configuring/11-draft-mode.mdx | 246 ++++ .../15-content-security-policy.mdx | 199 +++ .../07-configuring/index.mdx | 10 + .../08-deploying/01-static-exports.mdx | 371 +++++ .../08-deploying/index.mdx | 170 +++ .../08-upgrading/03-from-vite.mdx | 281 ++++ .../09-upgrading/01-codemods.mdx | 321 +++++ .../09-upgrading/02-app-router-migration.mdx | 917 +++++++++++++ .../09-upgrading/index.mdx | 9 + .../02-api-reference/01-components/font.mdx | 356 +++++ .../02-api-reference/01-components/image.mdx | 815 +++++++++++ .../02-api-reference/01-components/index.mdx | 8 + .../02-api-reference/01-components/link.mdx | 457 +++++++ .../02-api-reference/01-components/script.mdx | 456 +++++++ .../01-metadata/app-icons.mdx | 286 ++++ .../02-file-conventions/01-metadata/index.mdx | 13 + .../01-metadata/manifest.mdx | 72 + .../01-metadata/opengraph-image.mdx | 430 ++++++ .../01-metadata/robots.mdx | 88 ++ .../01-metadata/sitemap.mdx | 143 ++ .../02-file-conventions/default.mdx | 8 + .../02-file-conventions/error.mdx | 161 +++ .../02-file-conventions/index.mdx | 6 + .../02-file-conventions/layout.mdx | 135 ++ .../02-file-conventions/loading.mdx | 36 + .../02-file-conventions/not-found.mdx | 93 ++ .../02-file-conventions/page.mdx | 59 + .../route-segment-config.mdx | 226 ++++ .../02-file-conventions/route.mdx | 80 ++ .../02-file-conventions/template.mdx | 57 + .../02-api-reference/04-functions/cookies.mdx | 159 +++ .../04-functions/draft-mode.mdx | 28 + .../02-api-reference/04-functions/fetch.mdx | 109 ++ .../04-functions/generate-image-metadata.mdx | 230 ++++ .../04-functions/generate-metadata.mdx | 1160 ++++++++++++++++ .../04-functions/generate-static-params.mdx | 310 +++++ .../02-api-reference/04-functions/headers.mdx | 90 ++ .../04-functions/image-response.mdx | 46 + .../02-api-reference/04-functions/index.mdx | 8 + .../04-functions/next-request.mdx | 91 ++ .../04-functions/next-response.mdx | 146 ++ .../04-functions/not-found.mdx | 40 + .../04-functions/permanentRedirect.mdx | 60 + .../04-functions/redirect.mdx | 64 + .../04-functions/revalidatePath.mdx | 113 ++ .../04-functions/revalidateTag.mdx | 69 + .../04-functions/server-actions.mdx | 177 +++ .../04-functions/use-params.mdx | 75 ++ .../04-functions/use-pathname.mdx | 96 ++ .../04-functions/use-report-web-vitals.mdx | 224 +++ .../04-functions/use-router.mdx | 163 +++ .../04-functions/use-search-params.mdx | 367 +++++ .../use-selected-layout-segment.mdx | 174 +++ .../use-selected-layout-segments.mdx | 79 ++ .../05-next-config-js/appDir.mdx | 14 + .../05-next-config-js/assetPrefix.mdx | 69 + .../05-next-config-js/basePath.mdx | 81 ++ .../05-next-config-js/compress.mdx | 18 + .../05-next-config-js/devIndicators.mdx | 56 + .../05-next-config-js/distDir.mdx | 22 + .../05-next-config-js/env.mdx | 73 + .../05-next-config-js/eslint.mdx | 24 + .../05-next-config-js/exportPathMap.mdx | 96 ++ .../05-next-config-js/generateBuildId.mdx | 21 + .../05-next-config-js/generateEtags.mdx | 18 + .../05-next-config-js/headers.mdx | 514 +++++++ .../05-next-config-js/httpAgentOptions.mdx | 20 + .../05-next-config-js/images.mdx | 187 +++ .../incrementalCacheHandlerPath.mdx | 69 + .../05-next-config-js/index.mdx | 88 ++ .../05-next-config-js/mdxRs.mdx | 22 + .../05-next-config-js/onDemandEntries.mdx | 23 + .../optimizePackageImports.mdx | 22 + .../05-next-config-js/output.mdx | 143 ++ .../05-next-config-js/pageExtensions.mdx | 62 + .../05-next-config-js/poweredByHeader.mdx | 16 + .../productionBrowserSourceMaps.mdx | 23 + .../05-next-config-js/reactStrictMode.mdx | 24 + .../05-next-config-js/redirects.mdx | 322 +++++ .../05-next-config-js/rewrites.mdx | 477 +++++++ .../serverComponentsExternalPackages.mdx | 71 + .../05-next-config-js/trailingSlash.mdx | 26 + .../05-next-config-js/transpilePackages.mdx | 25 + .../05-next-config-js/turbo.mdx | 74 + .../05-next-config-js/typedRoutes.mdx | 20 + .../05-next-config-js/typescript.mdx | 27 + .../05-next-config-js/urlImports.mdx | 93 ++ .../webVitalsAttribution.mdx | 26 + .../05-next-config-js/webpack.mdx | 92 ++ .../01-routing/01-pages-and-layouts.mdx | 215 +++ .../01-routing/02-dynamic-routes.mdx | 66 + .../01-routing/03-linking-and-navigating.mdx | 200 +++ .../01-routing/04-custom-app.mdx | 91 ++ .../01-routing/05-custom-document.mdx | 149 ++ .../01-routing/06-custom-error.mdx | 100 ++ .../01-routing/07-api-routes.mdx | 425 ++++++ .../01-routing/08-internationalization.mdx | 351 +++++ .../01-routing/09-authenticating.mdx | 143 ++ .../01-routing/10-middleware.mdx | 9 + .../01-routing/index.mdx | 8 + .../02-rendering/01-server-side-rendering.mdx | 34 + .../02-static-site-generation.mdx | 176 +++ .../03-incremental-static-regeneration.mdx | 197 +++ .../04-automatic-static-optimization.mdx | 50 + .../02-rendering/05-client-side-rendering.mdx | 74 + .../06-edge-and-nodejs-runtimes.mdx | 9 + .../02-rendering/index.mdx | 23 + .../03-data-fetching/01-get-static-props.mdx | 199 +++ .../03-data-fetching/02-get-static-paths.mdx | 143 ++ .../03-forms-and-mutations.mdx | 10 + .../03-get-server-side-props.mdx | 146 ++ .../04-incremental-static-regeneration.mdx | 197 +++ .../03-data-fetching/05-client-side.mdx | 72 + .../03-data-fetching/index.mdx | 28 + .../04-styling/01-css-modules.mdx | 9 + .../04-styling/02-tailwind-css.mdx | 9 + .../04-styling/03-css-in-js.mdx | 9 + .../04-styling/04-sass.mdx | 9 + .../04-styling/index.mdx | 9 + .../05-optimizing/01-images.mdx | 10 + .../05-optimizing/02-fonts.mdx | 10 + .../05-optimizing/03-scripts.mdx | 10 + .../05-optimizing/05-static-assets.mdx | 9 + .../05-optimizing/06-lazy-loading.mdx | 9 + .../05-optimizing/07-analytics.mdx | 9 + .../05-optimizing/08-open-telemetry.mdx | 9 + .../05-optimizing/09-instrumentation.mdx | 9 + .../05-optimizing/10-testing.mdx | 543 ++++++++ .../05-optimizing/index.mdx | 10 + .../06-configuring/01-typescript.mdx | 9 + .../06-configuring/02-eslint.mdx | 9 + .../03-environment-variables.mdx | 9 + ...04-absolute-imports-and-module-aliases.mdx | 9 + .../06-configuring/05-src-directory.mdx | 9 + .../06-configuring/06-mdx.mdx | 9 + .../06-configuring/07-amp.mdx | 155 +++ .../06-configuring/08-babel.mdx | 67 + .../06-configuring/09-post-css.mdx | 164 +++ .../06-configuring/10-custom-server.mdx | 120 ++ .../06-configuring/11-draft-mode.mdx | 182 +++ .../06-configuring/12-error-handling.mdx | 104 ++ .../06-configuring/13-debugging.mdx | 116 ++ .../06-configuring/14-preview-mode.mdx | 241 ++++ .../15-content-security-policy.mdx | 9 + .../06-configuring/index.mdx | 9 + .../07-deploying/01-production-checklist.mdx | 145 ++ .../07-deploying/02-static-exports.mdx | 9 + .../07-deploying/03-multi-zones.mdx | 37 + .../07-deploying/04-ci-build-caching.mdx | 130 ++ .../07-deploying/index.mdx | 9 + .../08-upgrading/01-codemods.mdx | 9 + .../08-upgrading/02-app-router-migration.mdx | 9 + .../08-upgrading/03-version-13.mdx | 81 ++ .../08-upgrading/04-version-12.mdx | 146 ++ .../08-upgrading/05-version-11.mdx | 138 ++ .../08-upgrading/06-version-10.mdx | 16 + .../08-upgrading/07-version-9.mdx | 222 +++ .../08-upgrading/index.mdx | 9 + .../02-api-reference/01-components/font.mdx | 10 + .../02-api-reference/01-components/head.mdx | 64 + .../01-components/image-legacy.mdx | 578 ++++++++ .../02-api-reference/01-components/image.mdx | 9 + .../02-api-reference/01-components/index.mdx | 9 + .../02-api-reference/01-components/link.mdx | 9 + .../02-api-reference/01-components/script.mdx | 9 + .../02-functions/get-initial-props.mdx | 60 + .../02-functions/get-server-side-props.mdx | 132 ++ .../02-functions/get-static-paths.mdx | 260 ++++ .../02-functions/get-static-props.mdx | 229 ++++ .../02-api-reference/02-functions/index.mdx | 9 + .../02-functions/next-server.mdx | 171 +++ .../02-api-reference/02-functions/use-amp.mdx | 88 ++ .../02-functions/use-report-web-vitals.mdx | 9 + .../02-functions/use-router.mdx | 530 ++++++++ .../03-next-config-js/assetPrefix.mdx | 9 + .../03-next-config-js/basePath.mdx | 9 + .../03-next-config-js/compress.mdx | 9 + .../03-next-config-js/devIndicators.mdx | 9 + .../03-next-config-js/distDir.mdx | 9 + .../03-next-config-js/env.mdx | 9 + .../03-next-config-js/eslint.mdx | 9 + .../03-next-config-js/exportPathMap.mdx | 9 + .../03-next-config-js/generateBuildId.mdx | 9 + .../03-next-config-js/generateEtags.mdx | 9 + .../03-next-config-js/headers.mdx | 9 + .../03-next-config-js/httpAgentOptions.mdx | 9 + .../03-next-config-js/images.mdx | 9 + .../03-next-config-js/index.mdx | 9 + .../03-next-config-js/onDemandEntries.mdx | 9 + .../03-next-config-js/output.mdx | 9 + .../03-next-config-js/pageExtensions.mdx | 9 + .../03-next-config-js/poweredByHeader.mdx | 9 + .../productionBrowserSourceMaps.mdx | 9 + .../03-next-config-js/reactStrictMode.mdx | 9 + .../03-next-config-js/redirects.mdx | 9 + .../03-next-config-js/rewrites.mdx | 9 + .../runtime-configuration.mdx | 58 + .../03-next-config-js/trailingSlash.mdx | 9 + .../03-next-config-js/transpilePackages.mdx | 9 + .../03-next-config-js/turbo.mdx | 10 + .../03-next-config-js/typescript.mdx | 9 + .../03-next-config-js/urlImports.mdx | 9 + .../webVitalsAttribution.mdx | 9 + .../03-next-config-js/webpack.mdx | 10 + .../01-routing/01-defining-routes.mdx | 60 + .../01-routing/02-pages-and-layouts.mdx | 271 ++++ .../01-routing/03-linking-and-navigating.mdx | 417 ++++++ .../04-loading-ui-and-streaming.mdx | 186 +++ .../01-routing/05-error-handling.mdx | 232 ++++ .../01-routing/06-redirecting.mdx | 623 +++++++++ .../01-routing/07-route-groups.mdx | 80 ++ .../01-routing/08-colocation.mdx | 182 +++ .../01-routing/09-dynamic-routes.mdx | 128 ++ .../01-routing/10-parallel-routes.mdx | 466 +++++++ .../01-routing/11-intercepting-routes.mdx | 83 ++ .../01-routing/12-route-handlers.mdx | 730 ++++++++++ .../01-routing/13-middleware.mdx | 584 ++++++++ .../01-routing/14-internationalization.mdx | 155 +++ .../01-routing/index.mdx | 162 +++ .../01-fetching-caching-and-revalidating.mdx | 310 +++++ .../02-server-actions-and-mutations.mdx | 1000 ++++++++++++++ .../02-data-fetching/03-patterns.mdx | 405 ++++++ .../02-data-fetching/index.mdx | 6 + .../03-rendering/01-server-components.mdx | 135 ++ .../03-rendering/02-client-components.mdx | 108 ++ .../03-rendering/03-composition-patterns.mdx | 560 ++++++++ .../04-edge-and-nodejs-runtimes.mdx | 86 ++ .../03-rendering/index.mdx | 80 ++ .../04-caching/index.mdx | 583 ++++++++ .../05-styling/01-css-modules.mdx | 357 +++++ .../05-styling/02-tailwind-css.mdx | 183 +++ .../05-styling/03-css-in-js.mdx | 324 +++++ .../05-styling/04-sass.mdx | 82 ++ .../05-styling/index.mdx | 18 + .../06-optimizing/01-images.mdx | 375 ++++++ .../06-optimizing/02-videos.mdx | 275 ++++ .../06-optimizing/03-fonts.mdx | 644 +++++++++ .../06-optimizing/04-metadata.mdx | 370 +++++ .../06-optimizing/05-scripts.mdx | 377 ++++++ .../06-optimizing/06-bundle-analyzer.mdx | 51 + .../06-optimizing/07-lazy-loading.mdx | 246 ++++ .../06-optimizing/08-analytics.mdx | 216 +++ .../06-optimizing/09-instrumentation.mdx | 99 ++ .../06-optimizing/10-open-telemetry.mdx | 363 +++++ .../06-optimizing/11-static-assets.mdx | 52 + .../12-third-party-libraries.mdx | 384 ++++++ .../06-optimizing/13-memory-usage.mdx | 98 ++ .../06-optimizing/index.mdx | 48 + .../07-configuring/01-typescript.mdx | 325 +++++ .../07-configuring/02-eslint.mdx | 314 +++++ .../03-environment-variables.mdx | 235 ++++ ...04-absolute-imports-and-module-aliases.mdx | 167 +++ .../07-configuring/05-mdx.mdx | 451 +++++++ .../07-configuring/06-src-directory.mdx | 36 + .../07-configuring/11-draft-mode.mdx | 242 ++++ .../15-content-security-policy.mdx | 249 ++++ .../07-configuring/index.mdx | 10 + .../08-testing/01-vitest.mdx | 208 +++ .../08-testing/02-jest.mdx | 387 ++++++ .../08-testing/03-playwright.mdx | 134 ++ .../08-testing/04-cypress.mdx | 290 ++++ .../08-testing/index.mdx | 28 + .../09-authentication/index.mdx | 887 ++++++++++++ .../10-deploying/01-production-checklist.mdx | 166 +++ .../10-deploying/02-static-exports.mdx | 372 +++++ .../10-deploying/03-multi-zones.mdx | 91 ++ .../10-deploying/index.mdx | 284 ++++ .../11-upgrading/01-codemods.mdx | 380 ++++++ .../11-upgrading/02-app-router-migration.mdx | 919 +++++++++++++ .../11-upgrading/03-version-14.mdx | 38 + .../11-upgrading/04-from-vite.mdx | 551 ++++++++ .../11-upgrading/05-from-create-react-app.mdx | 549 ++++++++ .../11-upgrading/index.mdx | 9 + .../02-api-reference/01-components/font.mdx | 356 +++++ .../02-api-reference/01-components/image.mdx | 1086 +++++++++++++++ .../02-api-reference/01-components/index.mdx | 8 + .../02-api-reference/01-components/link.mdx | 511 +++++++ .../02-api-reference/01-components/script.mdx | 470 +++++++ .../01-metadata/app-icons.mdx | 285 ++++ .../02-file-conventions/01-metadata/index.mdx | 13 + .../01-metadata/manifest.mdx | 72 + .../01-metadata/opengraph-image.mdx | 431 ++++++ .../01-metadata/robots.mdx | 148 ++ .../01-metadata/sitemap.mdx | 300 +++++ .../02-file-conventions/default.mdx | 41 + .../02-file-conventions/error.mdx | 161 +++ .../02-file-conventions/index.mdx | 6 + .../02-file-conventions/instrumentation.mdx | 55 + .../02-file-conventions/layout.mdx | 133 ++ .../02-file-conventions/loading.mdx | 36 + .../02-file-conventions/middleware.mdx | 137 ++ .../02-file-conventions/not-found.mdx | 95 ++ .../02-file-conventions/page.mdx | 59 + .../route-segment-config.mdx | 227 ++++ .../02-file-conventions/route.mdx | 94 ++ .../02-file-conventions/template.mdx | 57 + .../02-api-reference/04-functions/cookies.mdx | 159 +++ .../04-functions/draft-mode.mdx | 28 + .../02-api-reference/04-functions/fetch.mdx | 109 ++ .../04-functions/generate-image-metadata.mdx | 230 ++++ .../04-functions/generate-metadata.mdx | 1199 +++++++++++++++++ .../04-functions/generate-sitemaps.mdx | 76 ++ .../04-functions/generate-static-params.mdx | 319 +++++ .../04-functions/generate-viewport.mdx | 240 ++++ .../02-api-reference/04-functions/headers.mdx | 129 ++ .../04-functions/image-response.mdx | 47 + .../02-api-reference/04-functions/index.mdx | 8 + .../04-functions/next-request.mdx | 153 +++ .../04-functions/next-response.mdx | 148 ++ .../04-functions/not-found.mdx | 40 + .../04-functions/permanentRedirect.mdx | 63 + .../04-functions/redirect.mdx | 145 ++ .../04-functions/revalidatePath.mdx | 125 ++ .../04-functions/revalidateTag.mdx | 80 ++ .../04-functions/unstable_cache.mdx | 50 + .../04-functions/unstable_noStore.mdx | 45 + .../04-functions/use-params.mdx | 75 ++ .../04-functions/use-pathname.mdx | 96 ++ .../04-functions/use-report-web-vitals.mdx | 230 ++++ .../04-functions/use-router.mdx | 163 +++ .../04-functions/use-search-params.mdx | 369 +++++ .../use-selected-layout-segment.mdx | 174 +++ .../use-selected-layout-segments.mdx | 79 ++ .../04-functions/userAgent.mdx | 73 + .../05-next-config-js/appDir.mdx | 12 + .../05-next-config-js/assetPrefix.mdx | 74 + .../05-next-config-js/basePath.mdx | 81 ++ .../05-next-config-js/compress.mdx | 37 + .../05-next-config-js/crossOrigin.mdx | 21 + .../05-next-config-js/devIndicators.mdx | 56 + .../05-next-config-js/distDir.mdx | 22 + .../05-next-config-js/env.mdx | 66 + .../05-next-config-js/eslint.mdx | 24 + .../05-next-config-js/exportPathMap.mdx | 96 ++ .../05-next-config-js/generateBuildId.mdx | 21 + .../05-next-config-js/generateEtags.mdx | 18 + .../05-next-config-js/headers.mdx | 542 ++++++++ .../05-next-config-js/httpAgentOptions.mdx | 20 + .../05-next-config-js/images.mdx | 229 ++++ .../incrementalCacheHandlerPath.mdx | 61 + .../05-next-config-js/index.mdx | 98 ++ .../05-next-config-js/instrumentationHook.mdx | 23 + .../05-next-config-js/logging.mdx | 22 + .../05-next-config-js/mdxRs.mdx | 22 + .../05-next-config-js/onDemandEntries.mdx | 23 + .../optimizePackageImports.mdx | 48 + .../05-next-config-js/output.mdx | 143 ++ .../05-next-config-js/pageExtensions.mdx | 62 + .../partial-prerendering.mdx | 36 + .../05-next-config-js/poweredByHeader.mdx | 16 + .../productionBrowserSourceMaps.mdx | 23 + .../05-next-config-js/reactStrictMode.mdx | 24 + .../05-next-config-js/redirects.mdx | 322 +++++ .../05-next-config-js/rewrites.mdx | 476 +++++++ .../05-next-config-js/serverActions.mdx | 57 + .../serverComponentsExternalPackages.mdx | 75 ++ .../05-next-config-js/staleTimes.mdx | 49 + .../05-next-config-js/trailingSlash.mdx | 28 + .../05-next-config-js/transpilePackages.mdx | 25 + .../05-next-config-js/turbo.mdx | 106 ++ .../05-next-config-js/typedRoutes.mdx | 20 + .../05-next-config-js/typescript.mdx | 27 + .../05-next-config-js/urlImports.mdx | 93 ++ .../webVitalsAttribution.mdx | 26 + .../05-next-config-js/webpack.mdx | 90 ++ .../06-cli/create-next-app.mdx | 87 ++ .../02-app/02-api-reference/06-cli/index.mdx | 13 + .../02-app/02-api-reference/06-cli/next.mdx | 239 ++++ .../01-routing/01-pages-and-layouts.mdx | 215 +++ .../01-routing/02-dynamic-routes.mdx | 66 + .../01-routing/03-linking-and-navigating.mdx | 193 +++ .../01-routing/04-redirecting.mdx | 9 + .../01-routing/05-custom-app.mdx | 91 ++ .../01-routing/06-custom-document.mdx | 149 ++ .../01-routing/07-api-routes.mdx | 425 ++++++ .../01-routing/08-custom-error.mdx | 100 ++ .../01-routing/10-internationalization.mdx | 350 +++++ .../01-routing/11-middleware.mdx | 9 + .../01-routing/index.mdx | 8 + .../02-rendering/01-server-side-rendering.mdx | 34 + .../02-static-site-generation.mdx | 176 +++ .../04-automatic-static-optimization.mdx | 50 + .../02-rendering/05-client-side-rendering.mdx | 74 + .../06-edge-and-nodejs-runtimes.mdx | 9 + .../02-rendering/index.mdx | 23 + .../03-data-fetching/01-get-static-props.mdx | 200 +++ .../03-data-fetching/02-get-static-paths.mdx | 143 ++ .../03-forms-and-mutations.mdx | 408 ++++++ .../03-get-server-side-props.mdx | 125 ++ .../04-incremental-static-regeneration.mdx | 181 +++ .../03-data-fetching/05-client-side.mdx | 72 + .../03-data-fetching/index.mdx | 28 + .../04-styling/01-css-modules.mdx | 9 + .../04-styling/02-tailwind-css.mdx | 9 + .../04-styling/03-css-in-js.mdx | 9 + .../04-styling/04-sass.mdx | 9 + .../04-styling/index.mdx | 9 + .../05-optimizing/01-images.mdx | 10 + .../05-optimizing/02-fonts.mdx | 10 + .../05-optimizing/03-scripts.mdx | 10 + .../05-optimizing/05-static-assets.mdx | 9 + .../05-optimizing/06-bundle-analyzer.mdx | 13 + .../05-optimizing/07-analytics.mdx | 9 + .../05-optimizing/08-lazy-loading.mdx | 9 + .../05-optimizing/09-instrumentation.mdx | 9 + .../05-optimizing/10-open-telemetry.mdx | 9 + .../11-third-party-libraries.mdx | 9 + .../05-optimizing/index.mdx | 10 + .../06-configuring/01-typescript.mdx | 9 + .../06-configuring/02-eslint.mdx | 9 + .../03-environment-variables.mdx | 9 + ...04-absolute-imports-and-module-aliases.mdx | 9 + .../06-configuring/05-src-directory.mdx | 9 + .../06-configuring/06-mdx.mdx | 10 + .../06-configuring/07-amp.mdx | 158 +++ .../06-configuring/08-babel.mdx | 67 + .../06-configuring/09-post-css.mdx | 164 +++ .../06-configuring/10-custom-server.mdx | 121 ++ .../06-configuring/11-draft-mode.mdx | 182 +++ .../06-configuring/12-error-handling.mdx | 104 ++ .../06-configuring/13-debugging.mdx | 120 ++ .../06-configuring/14-preview-mode.mdx | 241 ++++ .../15-content-security-policy.mdx | 9 + .../06-configuring/index.mdx | 9 + .../07-testing/01-vitest.mdx | 10 + .../07-testing/02-jest.mdx | 10 + .../07-testing/03-playwright.mdx | 10 + .../07-testing/04-cypress.mdx | 10 + .../07-testing/index.mdx | 9 + .../08-authentication/index.mdx | 9 + .../09-deploying/01-production-checklist.mdx | 9 + .../09-deploying/02-static-exports.mdx | 9 + .../09-deploying/03-multi-zones.mdx | 9 + .../09-deploying/04-ci-build-caching.mdx | 170 +++ .../09-deploying/index.mdx | 9 + .../10-upgrading/01-codemods.mdx | 9 + .../10-upgrading/02-app-router-migration.mdx | 9 + .../10-upgrading/03-from-vite.mdx | 9 + .../10-upgrading/04-from-create-react-app.mdx | 9 + .../10-upgrading/05-version-14.mdx | 9 + .../10-upgrading/06-version-13.mdx | 89 ++ .../10-upgrading/07-version-12.mdx | 154 +++ .../10-upgrading/08-version-11.mdx | 150 +++ .../10-upgrading/09-version-10.mdx | 28 + .../10-upgrading/10-version-9.mdx | 234 ++++ .../10-upgrading/index.mdx | 9 + .../02-api-reference/01-components/font.mdx | 10 + .../02-api-reference/01-components/head.mdx | 74 + .../01-components/image-legacy.mdx | 603 +++++++++ .../02-api-reference/01-components/image.mdx | 9 + .../02-api-reference/01-components/index.mdx | 9 + .../02-api-reference/01-components/link.mdx | 9 + .../02-api-reference/01-components/script.mdx | 9 + .../02-functions/get-initial-props.mdx | 60 + .../02-functions/get-server-side-props.mdx | 142 ++ .../02-functions/get-static-paths.mdx | 260 ++++ .../02-functions/get-static-props.mdx | 230 ++++ .../02-api-reference/02-functions/index.mdx | 9 + .../02-functions/next-request.mdx | 9 + .../02-functions/next-response.mdx | 9 + .../02-api-reference/02-functions/use-amp.mdx | 88 ++ 512 files changed, 73333 insertions(+), 189 deletions(-) create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/01-defining-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/02-pages-and-layouts.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/03-linking-and-navigating.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/04-route-groups.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/05-dynamic-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/06-loading-ui-and-streaming.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/07-error-handling.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/08-parallel-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/09-intercepting-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/10-route-handlers.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/11-middleware.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/12-colocation.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/13-internationalization.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/02-patterns.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/03-forms-and-mutations.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/01-server-components.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/02-client-components.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/03-composition-patterns.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/04-caching/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/01-css-modules.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/02-tailwind-css.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/03-css-in-js.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/04-sass.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/01-images.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/02-fonts.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/03-scripts.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/04-metadata.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/05-static-assets.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/06-lazy-loading.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/07-analytics.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/08-open-telemetry.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/09-instrumentation.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/01-typescript.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/02-eslint.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/03-environment-variables.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/04-absolute-imports-and-module-aliases.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/05-mdx.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/06-src-directory.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/11-draft-mode.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/15-content-security-policy.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/07-configuring/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/08-deploying/01-static-exports.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/08-deploying/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/08-upgrading/03-from-vite.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/09-upgrading/01-codemods.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/09-upgrading/02-app-router-migration.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/09-upgrading/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/01-components/font.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/01-components/image.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/01-components/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/01-components/link.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/01-components/script.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/01-metadata/app-icons.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/01-metadata/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/01-metadata/manifest.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/01-metadata/opengraph-image.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/01-metadata/robots.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/01-metadata/sitemap.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/default.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/error.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/layout.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/loading.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/not-found.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/page.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/route-segment-config.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/route.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/02-file-conventions/template.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/cookies.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/draft-mode.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/fetch.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/generate-image-metadata.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/generate-metadata.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/generate-static-params.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/headers.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/image-response.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/next-request.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/next-response.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/not-found.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/permanentRedirect.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/redirect.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/revalidatePath.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/revalidateTag.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/server-actions.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/use-params.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/use-pathname.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/use-report-web-vitals.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/use-router.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/use-search-params.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/use-selected-layout-segment.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/04-functions/use-selected-layout-segments.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/appDir.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/assetPrefix.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/basePath.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/compress.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/devIndicators.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/distDir.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/env.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/eslint.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/exportPathMap.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/generateBuildId.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/generateEtags.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/headers.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/httpAgentOptions.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/images.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/incrementalCacheHandlerPath.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/mdxRs.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/onDemandEntries.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/optimizePackageImports.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/output.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/pageExtensions.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/poweredByHeader.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/productionBrowserSourceMaps.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/reactStrictMode.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/redirects.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/rewrites.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/serverComponentsExternalPackages.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/trailingSlash.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/transpilePackages.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/turbo.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/typedRoutes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/typescript.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/urlImports.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/webVitalsAttribution.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/02-app/02-api-reference/05-next-config-js/webpack.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/01-pages-and-layouts.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/02-dynamic-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/03-linking-and-navigating.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/04-custom-app.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/05-custom-document.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/06-custom-error.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/07-api-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/08-internationalization.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/09-authenticating.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/10-middleware.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/01-routing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/02-rendering/01-server-side-rendering.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/02-rendering/02-static-site-generation.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/02-rendering/03-incremental-static-regeneration.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/02-rendering/04-automatic-static-optimization.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/02-rendering/05-client-side-rendering.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/02-rendering/06-edge-and-nodejs-runtimes.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/02-rendering/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/03-data-fetching/01-get-static-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/03-data-fetching/02-get-static-paths.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/03-data-fetching/03-get-server-side-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/03-data-fetching/04-incremental-static-regeneration.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/03-data-fetching/05-client-side.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/03-data-fetching/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/04-styling/01-css-modules.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/04-styling/02-tailwind-css.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/04-styling/03-css-in-js.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/04-styling/04-sass.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/04-styling/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/01-images.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/02-fonts.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/03-scripts.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/05-static-assets.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/06-lazy-loading.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/07-analytics.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/08-open-telemetry.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/09-instrumentation.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/10-testing.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/05-optimizing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/01-typescript.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/02-eslint.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/03-environment-variables.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/04-absolute-imports-and-module-aliases.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/05-src-directory.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/06-mdx.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/07-amp.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/08-babel.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/09-post-css.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/10-custom-server.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/11-draft-mode.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/12-error-handling.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/13-debugging.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/14-preview-mode.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/15-content-security-policy.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/06-configuring/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/07-deploying/01-production-checklist.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/07-deploying/02-static-exports.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/07-deploying/03-multi-zones.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/07-deploying/04-ci-build-caching.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/07-deploying/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/08-upgrading/01-codemods.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/08-upgrading/02-app-router-migration.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/08-upgrading/03-version-13.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/08-upgrading/04-version-12.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/08-upgrading/05-version-11.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/08-upgrading/06-version-10.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/08-upgrading/07-version-9.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/01-building-your-application/08-upgrading/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/01-components/font.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/01-components/head.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/01-components/image-legacy.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/01-components/image.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/01-components/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/01-components/link.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/01-components/script.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/get-initial-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/get-server-side-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/get-static-paths.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/get-static-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/next-server.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/use-amp.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/use-report-web-vitals.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/02-functions/use-router.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/assetPrefix.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/basePath.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/compress.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/devIndicators.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/distDir.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/env.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/eslint.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/exportPathMap.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/generateBuildId.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/generateEtags.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/headers.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/httpAgentOptions.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/images.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/onDemandEntries.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/output.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/pageExtensions.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/poweredByHeader.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/productionBrowserSourceMaps.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/reactStrictMode.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/redirects.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/rewrites.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/runtime-configuration.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/trailingSlash.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/transpilePackages.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/turbo.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/typescript.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/urlImports.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/webVitalsAttribution.mdx create mode 100644 apps/docs/content/zh-hans/docs/13/03-pages/02-api-reference/03-next-config-js/webpack.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/01-defining-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/02-pages-and-layouts.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/03-linking-and-navigating.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/04-loading-ui-and-streaming.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/05-error-handling.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/06-redirecting.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/07-route-groups.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/08-colocation.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/09-dynamic-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/10-parallel-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/11-intercepting-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/12-route-handlers.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/13-middleware.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/14-internationalization.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/01-routing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/02-data-fetching/03-patterns.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/02-data-fetching/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/03-rendering/01-server-components.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/03-rendering/02-client-components.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/03-rendering/03-composition-patterns.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/03-rendering/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/04-caching/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/05-styling/01-css-modules.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/05-styling/02-tailwind-css.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/05-styling/03-css-in-js.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/05-styling/04-sass.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/05-styling/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/01-images.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/02-videos.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/03-fonts.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/04-metadata.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/05-scripts.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/06-bundle-analyzer.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/07-lazy-loading.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/08-analytics.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/09-instrumentation.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/10-open-telemetry.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/11-static-assets.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/12-third-party-libraries.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/13-memory-usage.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/06-optimizing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/01-typescript.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/02-eslint.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/03-environment-variables.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/04-absolute-imports-and-module-aliases.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/05-mdx.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/06-src-directory.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/11-draft-mode.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/15-content-security-policy.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/07-configuring/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/08-testing/01-vitest.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/08-testing/02-jest.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/08-testing/03-playwright.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/08-testing/04-cypress.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/08-testing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/09-authentication/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/10-deploying/01-production-checklist.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/10-deploying/02-static-exports.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/10-deploying/03-multi-zones.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/10-deploying/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/11-upgrading/01-codemods.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/11-upgrading/02-app-router-migration.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/11-upgrading/03-version-14.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/11-upgrading/04-from-vite.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/11-upgrading/05-from-create-react-app.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/01-building-your-application/11-upgrading/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/01-components/font.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/01-components/image.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/01-components/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/01-components/link.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/01-components/script.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/01-metadata/app-icons.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/01-metadata/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/01-metadata/manifest.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/01-metadata/opengraph-image.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/01-metadata/robots.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/01-metadata/sitemap.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/default.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/error.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/instrumentation.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/layout.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/loading.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/middleware.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/not-found.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/page.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/route-segment-config.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/route.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/02-file-conventions/template.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/cookies.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/draft-mode.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/fetch.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/generate-image-metadata.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/generate-metadata.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/generate-sitemaps.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/generate-static-params.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/generate-viewport.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/headers.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/image-response.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/next-request.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/next-response.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/not-found.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/permanentRedirect.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/redirect.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/revalidatePath.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/revalidateTag.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/unstable_cache.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/unstable_noStore.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/use-params.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/use-pathname.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/use-report-web-vitals.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/use-router.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/use-search-params.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/use-selected-layout-segment.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/use-selected-layout-segments.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/04-functions/userAgent.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/appDir.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/assetPrefix.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/basePath.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/compress.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/crossOrigin.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/devIndicators.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/distDir.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/env.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/eslint.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/exportPathMap.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/generateBuildId.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/generateEtags.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/headers.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/httpAgentOptions.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/images.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/incrementalCacheHandlerPath.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/instrumentationHook.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/logging.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/mdxRs.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/onDemandEntries.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/optimizePackageImports.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/output.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/pageExtensions.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/partial-prerendering.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/poweredByHeader.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/productionBrowserSourceMaps.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/reactStrictMode.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/redirects.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/rewrites.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/serverActions.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/serverComponentsExternalPackages.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/staleTimes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/trailingSlash.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/transpilePackages.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/turbo.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/typedRoutes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/typescript.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/urlImports.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/webVitalsAttribution.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/05-next-config-js/webpack.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/06-cli/create-next-app.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/06-cli/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/02-app/02-api-reference/06-cli/next.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/01-pages-and-layouts.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/02-dynamic-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/03-linking-and-navigating.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/04-redirecting.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/05-custom-app.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/06-custom-document.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/07-api-routes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/08-custom-error.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/10-internationalization.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/11-middleware.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/01-routing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/02-rendering/01-server-side-rendering.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/02-rendering/02-static-site-generation.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/02-rendering/04-automatic-static-optimization.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/02-rendering/05-client-side-rendering.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/02-rendering/06-edge-and-nodejs-runtimes.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/02-rendering/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/03-data-fetching/01-get-static-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/03-data-fetching/02-get-static-paths.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/03-data-fetching/03-forms-and-mutations.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/03-data-fetching/03-get-server-side-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/03-data-fetching/04-incremental-static-regeneration.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/03-data-fetching/05-client-side.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/03-data-fetching/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/04-styling/01-css-modules.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/04-styling/02-tailwind-css.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/04-styling/03-css-in-js.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/04-styling/04-sass.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/04-styling/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/01-images.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/02-fonts.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/03-scripts.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/05-static-assets.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/06-bundle-analyzer.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/07-analytics.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/08-lazy-loading.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/09-instrumentation.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/10-open-telemetry.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/11-third-party-libraries.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/05-optimizing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/01-typescript.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/02-eslint.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/03-environment-variables.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/04-absolute-imports-and-module-aliases.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/05-src-directory.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/06-mdx.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/07-amp.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/08-babel.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/09-post-css.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/10-custom-server.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/11-draft-mode.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/12-error-handling.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/13-debugging.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/14-preview-mode.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/15-content-security-policy.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/06-configuring/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/07-testing/01-vitest.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/07-testing/02-jest.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/07-testing/03-playwright.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/07-testing/04-cypress.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/07-testing/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/08-authentication/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/09-deploying/01-production-checklist.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/09-deploying/02-static-exports.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/09-deploying/03-multi-zones.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/09-deploying/04-ci-build-caching.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/09-deploying/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/01-codemods.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/02-app-router-migration.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/03-from-vite.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/04-from-create-react-app.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/05-version-14.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/06-version-13.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/07-version-12.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/08-version-11.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/09-version-10.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/10-version-9.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/01-building-your-application/10-upgrading/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/01-components/font.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/01-components/head.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/01-components/image-legacy.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/01-components/image.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/01-components/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/01-components/link.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/01-components/script.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/02-functions/get-initial-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/02-functions/get-server-side-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/02-functions/get-static-paths.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/02-functions/get-static-props.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/02-functions/index.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/02-functions/next-request.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/02-functions/next-response.mdx create mode 100644 apps/docs/content/zh-hans/docs/14/03-pages/02-api-reference/02-functions/use-amp.mdx diff --git a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/assetPrefix.mdx b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/assetPrefix.mdx index 22e60cd8..7551355b 100644 --- a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/assetPrefix.mdx +++ b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/assetPrefix.mdx @@ -1,6 +1,6 @@ --- -source-updated-at: 2025-05-16T04:52:11.000Z -translation-updated-at: 2025-05-20T22:59:36.628Z +source-updated-at: 2025-05-21T18:33:43.000Z +translation-updated-at: 2025-05-21T21:08:11.137Z title: assetPrefix description: 了解如何使用 assetPrefix 配置选项来配置您的 CDN。 --- @@ -21,14 +21,15 @@ description: 了解如何使用 assetPrefix 配置选项来配置您的 CDN。 -> **须知**:Next.js 9.5+ 增加了对可自定义 [基础路径 (Base Path)](/docs/app/api-reference/config/next-config-js/basePath) 的支持,该功能更适合将应用程序托管在子路径(如 `/docs`)下。 -> 我们不建议您为此用例使用自定义资源前缀 (Asset Prefix)。 +> **须知**:Next.js 9.5+ 添加了对可自定义 [基础路径 (Base Path)](/docs/app/api-reference/config/next-config-js/basePath) 的支持,该功能更适合 +> 将应用程序托管在子路径(如 `/docs`)下。 +> 对于此用例,我们不建议使用自定义资源前缀 (Asset Prefix)。 ## 设置 CDN -要设置 [CDN](https://en.wikipedia.org/wiki/Content_delivery_network),您可以配置资源前缀 (assetPrefix) 并将 CDN 的源站解析到托管 Next.js 的域名。 +要设置 [CDN](https://en.wikipedia.org/wiki/Content_delivery_network),您可以配置资源前缀并将 CDN 的源站解析到托管 Next.js 的域名。 -打开 `next.config.mjs` 并根据 [阶段 (phase)](/docs/app/api-reference/config/next-config-js#async-configuration) 添加 `assetPrefix` 配置: +打开 `next.config.mjs` 并根据 [阶段](/docs/app/api-reference/config/next-config-js#async-configuration) 添加 `assetPrefix` 配置: ```js filename="next.config.mjs" // @ts-check @@ -46,7 +47,7 @@ export default (phase) => { } ``` -Next.js 会自动将您配置的资源前缀用于从 `/_next/` 路径(`.next/static/` 文件夹)加载的 JavaScript 和 CSS 文件。例如,使用上述配置后,以下对 JS 分块的请求: +Next.js 会自动对从 `/_next/` 路径(`.next/static/` 文件夹)加载的 JavaScript 和 CSS 文件使用您配置的资源前缀。例如,使用上述配置后,原本的 JS 块请求: ``` /_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js @@ -58,7 +59,7 @@ Next.js 会自动将您配置的资源前缀用于从 `/_next/` 路径(`.next/ https://cdn.mydomain.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js ``` -将文件上传到指定 CDN 的具体配置取决于您选择的 CDN。您只需要在 CDN 上托管 `.next/static/` 文件夹的内容,这些内容应按照上述 URL 请求所示上传为 `_next/static/`。**不要上传 `.next/` 文件夹的其他部分**,因为您不应将服务器代码和其他配置暴露给公众。 +将文件上传到指定 CDN 的具体配置取决于您选择的 CDN 服务。您只需要在 CDN 上托管 `.next/static/` 目录的内容,并按照上述 URL 请求所示将其上传为 `_next/static/`。**请勿上传 `.next/` 文件夹的其他部分**,因为您不应将服务器代码和其他配置公开给公众。 虽然 `assetPrefix` 会覆盖对 `_next/static` 的请求,但它不会影响以下路径: @@ -72,6 +73,6 @@ https://cdn.mydomain.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51 - [public](/docs/pages/api-reference/file-conventions/public-folder) 文件夹中的文件;如果您想通过 CDN 提供这些资源,需要自行添加前缀 - 针对 `getServerSideProps` 页面的 `/_next/data/` 请求。这些请求始终会针对主域名发起,因为它们不是静态的。 -- 针对 `getStaticProps` 页面的 `/_next/data/` 请求。这些请求始终会针对主域名发起,以支持 [增量静态生成 (Incremental Static Generation)](/docs/pages/building-your-application/data-fetching/incremental-static-regeneration),即使您没有使用该功能(为了保持一致性)。 +- 针对 `getStaticProps` 页面的 `/_next/data/` 请求。这些请求始终会针对主域名发起以支持 [增量静态生成 (Incremental Static Generation)](/docs/pages/guides/incremental-static-regeneration),即使您没有使用该功能(为了保持一致性)。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/headers.mdx b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/headers.mdx index 5a5f00d6..3c9a49c4 100644 --- a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/headers.mdx +++ b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/headers.mdx @@ -1,15 +1,15 @@ --- -source-updated-at: 2025-05-16T04:52:11.000Z -translation-updated-at: 2025-05-20T23:00:43.245Z +source-updated-at: 2025-05-21T18:33:43.000Z +translation-updated-at: 2025-05-21T21:10:09.747Z title: headers -description: 为您的 Next.js 应用添加自定义 HTTP 标头。 +description: 为你的 Next.js 应用添加自定义 HTTP 标头。 --- -{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加仅适用于页面路由的内容。任何共享内容不应包裹在组件中。*/} +{/* 本文档内容在应用路由和页面路由间共享。你可以使用 `内容` 组件添加仅适用于页面路由的内容。任何共享内容不应包裹在组件中。 */} -标头允许您为指定路径的请求响应设置自定义 HTTP 标头。 +标头允许你在给定路径的请求响应中设置自定义 HTTP 标头。 -要设置自定义 HTTP 标头,您可以在 `next.config.js` 中使用 `headers` 键: +要设置自定义 HTTP 标头,可以在 `next.config.js` 中使用 `headers` 键: ```js filename="next.config.js" module.exports = { @@ -33,20 +33,20 @@ module.exports = { } ``` -`headers` 是一个异步函数,期望返回一个包含具有 `source` 和 `headers` 属性的对象数组: +`headers` 是一个异步函数,需要返回一个包含具有 `source` 和 `headers` 属性的对象数组: - `source`:传入请求的路径模式 - `headers`:响应标头对象数组,包含 `key` 和 `value` 属性 - `basePath`:`false` 或 `undefined` - 如果为 false,则匹配时不包含 basePath,仅用于外部重写 -- `locale`:`false` 或 `undefined` - 匹配时是否不包含区域设置 -- `has`:包含 `type`、`key` 和 `value` 属性的 [has 对象](#header-cookie-and-query-matching)数组 -- `missing`:包含 `type`、`key` 和 `value` 属性的 [missing 对象](#header-cookie-and-query-matching)数组 +- `locale`:`false` 或 `undefined` - 匹配时是否不包含语言环境 +- `has`:包含 `type`、`key` 和 `value` 属性的 [has 对象](#header-cookie-and-query-matching) 数组 +- `missing`:包含 `type`、`key` 和 `value` 属性的 [missing 对象](#header-cookie-and-query-matching) 数组 标头检查优先于文件系统(包括页面和 `/public` 文件)。 ## 标头覆盖行为 -如果两个标头匹配相同路径并设置相同的标头键,后一个标头键将覆盖前一个。使用以下标头配置,路径 `/hello` 将导致标头 `x-hello` 的值为 `world`,因为最后设置的标头值是 `world`。 +如果两个标头匹配相同路径并设置相同的标头键,后一个标头键会覆盖前一个。使用以下标头配置时,路径 `/hello` 将导致标头 `x-hello` 的值为 `world`,因为最后设置的标头值是 `world`。 ```js filename="next.config.js" module.exports = { @@ -77,7 +77,7 @@ module.exports = { ## 路径匹配 -支持路径匹配,例如 `/blog/:slug` 将匹配 `/blog/hello-world`(不包括嵌套路径): +支持路径匹配,例如 `/blog/:slug` 将匹配 `/blog/hello-world`(不支持嵌套路径): ```js filename="next.config.js" module.exports = { @@ -149,14 +149,14 @@ module.exports = { } ``` -以下字符 `(`、`)`、`{`、`}`、`:`、`*`、`+`、`?` 用于正则表达式路径匹配,因此当它们在 `source` 中作为非特殊值使用时,必须通过在前面添加 `\\` 进行转义: +以下字符 `(`、`)`、`{`、`}`、`:`、`*`、`+`、`?` 用于正则表达式路径匹配,因此当在 `source` 中作为非特殊值使用时,必须通过在前面添加 `\\` 进行转义: ```js filename="next.config.js" module.exports = { async headers() { return [ { - // 这将匹配请求 `/english(default)/something` + // 这将匹配请求的 `/english(default)/something` source: '/english\\(default\\)/:slug', headers: [ { @@ -172,13 +172,13 @@ module.exports = { ## 标头、Cookie 和查询匹配 -要仅在标头、cookie 或查询值也匹配 `has` 字段或不匹配 `missing` 字段时应用标头,可以使用这些字段。必须同时匹配 `source` 和所有 `has` 项,并且所有 `missing` 项都不匹配时才会应用标头。 +要仅在标头、cookie 或查询值也匹配 `has` 字段或不匹配 `missing` 字段时应用标头,可以使用这两个字段。必须同时匹配 `source` 和所有 `has` 项,并且所有 `missing` 项都不匹配,才会应用标头。 `has` 和 `missing` 项可以包含以下字段: - `type`:`String` - 必须是 `header`、`cookie`、`host` 或 `query` 之一 - `key`:`String` - 要匹配的选定类型的键 -- `value`:`String` 或 `undefined` - 要检查的值,如果为 undefined 则匹配任何值。可以使用类似正则表达式的字符串捕获值的特定部分,例如如果对 `first-second` 使用值 `first-(?.*)`,则可以在目标中使用 `:paramName` 来引用 `second` +- `value`:`String` 或 `undefined` - 要检查的值,如果未定义则匹配任何值。可以使用类似正则表达式的字符串捕获值的特定部分,例如如果值 `first-(?.*)` 用于 `first-second`,则 `second` 可以在目标中使用 `:paramName` ```js filename="next.config.js" module.exports = { @@ -226,8 +226,8 @@ module.exports = { { type: 'query', key: 'page', - // 由于提供了值且未使用命名捕获组(例如 `(?home)`), - // page 值将不可用于标头键/值 + // 由于提供了值且未使用命名捕获组(例如 (?home), + // 页面值将不可用于标头键/值 value: 'home', }, { @@ -285,7 +285,7 @@ module.exports = { ## 支持 basePath 的标头 -当与标头一起使用 [`basePath` 支持](/docs/app/api-reference/config/next-config-js/basePath) 时,每个 `source` 会自动添加 `basePath` 前缀,除非您在标头中添加 `basePath: false`: +当与标头一起使用 [`basePath` 支持](/docs/app/api-reference/config/next-config-js/basePath) 时,每个 `source` 会自动添加 `basePath` 前缀,除非你在标头中添加 `basePath: false`: ```js filename="next.config.js" module.exports = { @@ -321,13 +321,13 @@ module.exports = { -当与标头一起使用 [`i18n` 支持](/docs/app/building-your-application/routing/internationalization) 时,每个 `source` 会自动添加前缀以处理配置的 `locales`,除非您在标头中添加 `locale: false`。如果使用 `locale: false`,您必须在 `source` 前添加区域设置才能正确匹配。 +当与标头一起使用 [`i18n` 支持](/docs/app/guides/internationalization) 时,每个 `source` 会自动添加前缀以处理配置的 `locales`,除非你在标头中添加 `locale: false`。如果使用 `locale: false`,则必须在 `source` 前添加语言环境才能正确匹配。 -当与标头一起使用 [`i18n` 支持](/docs/pages/building-your-application/routing/internationalization) 时,每个 `source` 会自动添加前缀以处理配置的 `locales`,除非您在标头中添加 `locale: false`。如果使用 `locale: false`,您必须在 `source` 前添加区域设置才能正确匹配。 +当与标头一起使用 [`i18n` 支持](/docs/pages/guides/internationalization) 时,每个 `source` 会自动添加前缀以处理配置的 `locales`,除非你在标头中添加 `locale: false`。如果使用 `locale: false`,则必须在 `source` 前添加语言环境才能正确匹配。 @@ -341,7 +341,7 @@ module.exports = { async headers() { return [ { - source: '/with-locale', // 自动处理所有区域设置 + source: '/with-locale', // 自动处理所有语言环境 headers: [ { key: 'x-hello', @@ -350,7 +350,7 @@ module.exports = { ], }, { - // 由于设置了 locale: false,不会自动处理区域设置 + // 由于设置了 locale: false,不自动处理语言环境 source: '/nl/with-locale-manual', locale: false, headers: [ @@ -372,8 +372,7 @@ module.exports = { ], }, { - // 这将转换为 /(en|fr|de)/(.*),因此不会像 /:path* 那样 - // 匹配顶级 `/` 或 `/fr` 路由 + // 这将转换为 /(en|fr|de)/(.*),因此不会像 /:path* 那样匹配顶级 `/` 或 `/fr` 路由 source: '/(.*)', headers: [ { @@ -387,15 +386,15 @@ module.exports = { } ``` -## 缓存控制 +## Cache-Control -Next.js 为真正不可变的资源设置 `Cache-Control` 标头为 `public, max-age=31536000, immutable`。此设置无法被覆盖。这些不可变文件在文件名中包含 SHA 哈希,因此可以安全地无限期缓存。例如,[静态图片导入](/docs/app/getting-started/images#local-images)。您无法在 `next.config.js` 中为这些资源设置 `Cache-Control` 标头。 +Next.js 为真正不可变的资源设置 `Cache-Control` 标头为 `public, max-age=31536000, immutable`。此设置无法覆盖。这些不可变文件在文件名中包含 SHA 哈希,因此可以安全地无限期缓存。例如,[静态图片导入](/docs/app/getting-started/images#local-images)。你无法在 `next.config.js` 中为这些资源设置 `Cache-Control` 标头。 -但是,您可以为其他响应或数据设置 `Cache-Control` 标头。 +但是,你可以为其他响应或数据设置 `Cache-Control` 标头。 -详细了解应用路由的 [缓存](/docs/app/deep-dive/caching)。 +了解有关应用路由的 [缓存](/docs/app/deep-dive/caching) 的更多信息。 @@ -428,17 +427,17 @@ export default function handler(req, res) { } ``` -您还可以在 `getServerSideProps` 中使用缓存标头(`Cache-Control`)来缓存动态响应。例如,使用 [`stale-while-revalidate`](https://web.dev/stale-while-revalidate/)。 +你也可以在 `getServerSideProps` 中使用缓存标头(`Cache-Control`)来缓存动态响应。例如,使用 [`stale-while-revalidate`](https://web.dev/stale-while-revalidate/)。 ```ts filename="pages/index.tsx" switcher import { GetStaticProps, GetStaticPaths, GetServerSideProps } from 'next' -// 此值在 10 秒内被视为新鲜(s-maxage=10)。 -// 如果在接下来的 10 秒内重复请求,先前缓存的值仍将是新鲜的。如果在 59 秒内重复请求, -// 缓存的值将过时但仍会渲染(stale-while-revalidate=59)。 +// 此值在十秒内被视为新鲜(s-maxage=10)。 +// 如果在接下来的 10 秒内重复请求,之前缓存的值仍将是新鲜的。如果在 59 秒前重复请求, +// 缓存的值将是过时的但仍会渲染(stale-while-revalidate=59)。 // // 在后台,将发出重新验证请求以用新值填充缓存。 -// 如果刷新页面,您将看到新值。 +// 如果刷新页面,你将看到新值。 export const getServerSideProps = (async (context) => { context.res.setHeader( 'Cache-Control', @@ -452,12 +451,12 @@ export const getServerSideProps = (async (context) => { ``` ```js filename="pages/index.js" switcher -// 此值在 10 秒内被视为新鲜(s-maxage=10)。 -// 如果在接下来的 10 秒内重复请求,先前缓存的值仍将是新鲜的。如果在 59 秒内重复请求, -// 缓存的值将过时但仍会渲染(stale-while-revalidate=59)。 +// 此值在十秒内被视为新鲜(s-maxage=10)。 +// 如果在接下来的 10 秒内重复请求,之前缓存的值仍将是新鲜的。如果在 59 秒前重复请求, +// 缓存的值将是过时的但仍会渲染(stale-while-revalidate=59)。 // // 在后台,将发出重新验证请求以用新值填充缓存。 -// 如果刷新页面,您将看到新值。 +// 如果刷新页面,你将看到新值。 export async function getServerSideProps({ req, res }) { res.setHeader( 'Cache-Control', @@ -476,7 +475,7 @@ export async function getServerSideProps({ req, res }) { ### CORS -[跨源资源共享 (CORS)](https://developer.mozilla.org/docs/Web/HTTP/CORS) 是一项安全功能,允许您控制哪些站点可以访问您的资源。您可以设置 `Access-Control-Allow-Origin` 标头以允许特定源访问您的 API 端点路由处理程序。 +[跨源资源共享 (CORS)](https://developer.mozilla.org/docs/Web/HTTP/CORS) 是一项安全功能,允许你控制哪些站点可以访问你的资源。你可以设置 `Access-Control-Allow-Origin` 标头以允许特定来源访问你的 API 端点路由处理程序。 ```js async headers() { @@ -486,7 +485,7 @@ async headers() { headers: [ { key: "Access-Control-Allow-Origin", - value: "*", // 设置您的源 + value: "*", // 设置你的来源 }, { key: "Access-Control-Allow-Methods", @@ -504,7 +503,7 @@ async headers() { ### X-DNS-Prefetch-Control -[此标头](https://developer.mozilla.org/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) 控制 DNS 预取,允许浏览器主动对外部链接、图片、CSS、JavaScript 等执行域名解析。此预取在后台执行,因此在需要引用项时 [DNS](https://developer.mozilla.org/docs/Glossary/DNS) 更有可能已解析。这减少了用户点击链接时的延迟。 +[此标头](https://developer.mozilla.org/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) 控制 DNS 预取,允许浏览器主动解析外部链接、图片、CSS、JavaScript 等的域名。此预取在后台执行,因此在需要引用项目时 [DNS](https://developer.mozilla.org/docs/Glossary/DNS) 更有可能已解析。这减少了用户点击链接时的延迟。 ```js { @@ -515,7 +514,7 @@ async headers() { ### Strict-Transport-Security -[此标头](https://developer.mozilla.org/docs/Web/HTTP/Headers/Strict-Transport-Security) 通知浏览器应仅使用 HTTPS 访问,而不是 HTTP。使用以下配置,所有当前和未来的子域将使用 HTTPS,`max-age` 为 2 年。这会阻止访问只能通过 HTTP 提供的页面或子域。 +[此标头](https://developer.mozilla.org/docs/Web/HTTP/Headers/Strict-Transport-Security) 通知浏览器应仅使用 HTTPS 访问,而不是使用 HTTP。使用以下配置,所有当前和未来的子域将使用 HTTPS,`max-age` 为 2 年。这会阻止访问只能通过 HTTP 提供的页面或子域。 ```js { @@ -528,7 +527,7 @@ async headers() { [此标头](https://developer.mozilla.org/docs/Web/HTTP/Headers/X-Frame-Options) 指示是否应允许站点在 `iframe` 中显示。这可以防止点击劫持攻击。 -**此标头已被 CSP 的 `frame-ancestors` 选项取代**,后者在现代浏览器中有更好的支持(配置详情请参阅 [内容安全策略](/docs/app/guides/content-security-policy))。 +**此标头已被 CSP 的 `frame-ancestors` 选项取代**,后者在现代浏览器中有更好的支持(有关配置详情,请参阅 [内容安全策略](/docs/app/guides/content-security-policy))。 ```js { @@ -539,7 +538,7 @@ async headers() { ### 权限策略 (Permissions-Policy) -[该头部字段](https://developer.mozilla.org/docs/Web/HTTP/Headers/Permissions-Policy) 允许您控制浏览器中可使用哪些功能和 API。它之前的名称是 `Feature-Policy`。 +[该头部字段](https://developer.mozilla.org/docs/Web/HTTP/Headers/Permissions-Policy) 允许您控制浏览器中可以使用哪些功能和 API。它之前的名称是 `Feature-Policy`。 ```js { @@ -550,9 +549,9 @@ async headers() { ### X-内容类型选项 (X-Content-Type-Options) -[该头部字段](https://developer.mozilla.org/docs/Web/HTTP/Headers/X-Content-Type-Options) 可防止浏览器在未明确设置 `Content-Type` 头部时尝试猜测内容类型。这可以防止允许用户上传和共享文件的网站遭受 XSS 攻击。 +[该头部字段](https://developer.mozilla.org/docs/Web/HTTP/Headers/X-Content-Type-Options) 可以防止浏览器在未明确设置 `Content-Type` 头部时尝试猜测内容类型。这能防止允许用户上传和共享文件的网站遭受 XSS 攻击。 -例如,用户尝试下载图像时,该图像可能被当作可执行文件等其他 `Content-Type` 处理,这可能具有恶意性。此头部也适用于浏览器扩展的下载。该头部唯一有效的值是 `nosniff`。 +例如,用户尝试下载图片时,文件可能被当作其他 `Content-Type` 类型(如可执行文件)处理,这可能存在恶意行为。此头部也适用于浏览器扩展的下载。该头部唯一有效的值是 `nosniff`。 ```js { @@ -563,7 +562,7 @@ async headers() { ### 来源策略 (Referrer-Policy) -[该头部字段](https://developer.mozilla.org/docs/Web/HTTP/Headers/Referrer-Policy) 控制浏览器在从当前网站(源)导航到另一个网站时包含的信息量。 +[该头部字段](https://developer.mozilla.org/docs/Web/HTTP/Headers/Referrer-Policy) 控制浏览器在从当前网站(源)导航到另一个网站时包含多少信息。 ```js { @@ -574,12 +573,12 @@ async headers() { ### 内容安全策略 (Content-Security-Policy) -详细了解如何为您的应用程序添加 [内容安全策略](/docs/app/guides/content-security-policy)。 +详细了解如何为您的应用添加 [内容安全策略 (Content Security Policy)](/docs/app/guides/content-security-policy)。 ## 版本历史 -| 版本 | 变更 | -| --------- | ------------------ | -| `v13.3.0` | 新增 `missing`。 | -| `v10.2.0` | 新增 `has`。 | -| `v9.5.0` | 新增头部字段支持。 | +| 版本 | 变更内容 | +| ---------- | ----------------- | +| `v13.3.0` | 新增 `missing`。 | +| `v10.2.0` | 新增 `has`。 | +| `v9.5.0` | 新增头部字段。 | diff --git a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/htmlLimitedBots.mdx b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/htmlLimitedBots.mdx index 6e52d484..a8175778 100644 --- a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/htmlLimitedBots.mdx +++ b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/htmlLimitedBots.mdx @@ -1,17 +1,17 @@ --- -source-updated-at: 2025-05-16T04:52:11.000Z -translation-updated-at: 2025-05-20T22:58:10.218Z +source-updated-at: 2025-05-21T18:33:43.000Z +translation-updated-at: 2025-05-21T21:07:36.410Z title: htmlLimitedBots description: 指定应接收阻塞元数据的用户代理列表。 --- -`htmlLimitedBots` 配置允许您指定一组应接收阻塞元数据而非 [流式元数据](/docs/app/api-reference/functions/generate-metadata#streaming-metadata) 的用户代理。 +`htmlLimitedBots` 配置允许您指定应接收阻塞元数据而非 [流式元数据](/docs/app/api-reference/functions/generate-metadata#streaming-metadata) 的用户代理列表。 ```ts filename="next.config.ts" switcher import type { NextConfig } from 'next' const config: NextConfig = { - htmlLimitedBots: 'MySpecialBot|MyAnotherSpecialBot|SimpleCrawler', + htmlLimitedBots: /MySpecialBot|MyAnotherSpecialBot|SimpleCrawler/, } export default config @@ -19,18 +19,18 @@ export default config ```js filename="next.config.js" switcher module.exports = { - htmlLimitedBots: 'MySpecialBot|MyAnotherSpecialBot|SimpleCrawler', + htmlLimitedBots: /MySpecialBot|MyAnotherSpecialBot|SimpleCrawler/, } ``` ## 默认列表 -Next.js 内置了 [HTML 受限机器人的默认列表](https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/html-bots.ts)。 +Next.js 内置了 [HTML 受限爬虫的默认列表](https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/html-bots.ts)。 -指定 `htmlLimitedBots` 配置会覆盖 Next.js 的默认列表,使您可以完全控制哪些用户代理应启用此行为。不过这是高级用法,默认列表在大多数情况下已足够使用。 +指定 `htmlLimitedBots` 配置将覆盖 Next.js 的默认列表,使您可以完全控制哪些用户代理应启用此行为。不过这是高级用法,在大多数情况下默认列表已经足够。 ## 版本历史 -| 版本 | 变更说明 | -| ------- | ------------------------------------ | -| 15.2.0 | 新增 `htmlLimitedBots` 配置选项。 | \ No newline at end of file +| 版本 | 变更说明 | +| ------- | --------------------------------- | +| 15.2.0 | 新增 `htmlLimitedBots` 配置选项。 | \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/incrementalCacheHandlerPath.mdx b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/incrementalCacheHandlerPath.mdx index ac17cfd1..64dd055c 100644 --- a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/incrementalCacheHandlerPath.mdx +++ b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/incrementalCacheHandlerPath.mdx @@ -1,6 +1,6 @@ --- -source-updated-at: 2025-05-16T04:52:11.000Z -translation-updated-at: 2025-05-20T22:58:13.280Z +source-updated-at: 2025-05-21T18:33:43.000Z +translation-updated-at: 2025-05-21T21:07:50.596Z title: 自定义 Next.js 缓存处理器 nav_title: cacheHandler description: 配置 Next.js 用于存储和重新验证数据的缓存,以使用 Redis、Memcached 或其他任何外部服务。 @@ -15,7 +15,7 @@ module.exports = { } ``` -查看[自定义缓存处理器](/docs/app/guides/self-hosting#configuring-caching)的示例并了解更多实现细节。 +查看 [自定义缓存处理器](/docs/app/guides/self-hosting#configuring-caching) 的示例并了解更多实现细节。 ## API 参考 @@ -23,19 +23,19 @@ module.exports = { ### `get()` -| 参数 | 类型 | 描述 | -| --------- | ---------- | --------------------- | -| `key` | `string` | 缓存值的键名。 | +| 参数 | 类型 | 描述 | +| --------- | ---------- | ---------------------- | +| `key` | `string` | 缓存值的键名。 | 返回缓存值,如果未找到则返回 `null`。 ### `set()` -| 参数 | 类型 | 描述 | -| --------- | ---------------- | ------------------------- | -| `key` | `string` | 存储数据的键名。 | -| `data` | Data 或 `null` | 要缓存的数据。 | -| `ctx` | `{ tags: [] }` | 提供的缓存标签。 | +| 参数 | 类型 | 描述 | +| --------- | ---------------- | ------------------------ | +| `key` | `string` | 存储数据的键名。 | +| `data` | Data 或 `null` | 要缓存的数据。 | +| `ctx` | `{ tags: [] }` | 提供的缓存标签。 | 返回 `Promise`。 @@ -43,19 +43,19 @@ module.exports = { | 参数 | 类型 | 描述 | | --------- | ------------------------ | ---------------------- | -| `tag` | `string` 或 `string[]` | 需要重新验证的缓存标签。| +| `tag` | `string` 或 `string[]` | 需要重新验证的缓存标签。 | -返回 `Promise`。了解更多关于[重新验证数据](/docs/app/building-your-application/data-fetching/incremental-static-regeneration)或 [`revalidateTag()`](/docs/app/api-reference/functions/revalidateTag) 函数的信息。 +返回 `Promise`。了解更多关于 [重新验证数据](/docs/app/guides/incremental-static-regeneration) 或 [`revalidateTag()`](/docs/app/api-reference/functions/revalidateTag) 函数的信息。 ### `resetRequestCache()` -此方法在下一次请求之前重置单个请求的临时内存缓存。 +此方法在下一个请求之前重置单个请求的临时内存缓存。 返回 `void`。 **须知:** -- `revalidatePath` 是缓存标签之上的便捷层。调用 `revalidatePath` 会调用您的 `revalidateTag` 函数,然后您可以选择是否根据路径标记缓存键。 +- `revalidatePath` 是缓存标签之上的便捷层。调用 `revalidatePath` 会调用您的 `revalidateTag` 函数,然后您可以选择是否基于路径标记缓存键。 ## 平台支持 @@ -64,15 +64,15 @@ module.exports = { | [Node.js 服务器](/docs/app/getting-started/deploying#nodejs-server) | 是 | | [Docker 容器](/docs/app/getting-started/deploying#docker) | 是 | | [静态导出](/docs/app/getting-started/deploying#static-export) | 否 | -| [适配器](/docs/app/getting-started/deploying#adapters) | 视平台而定 | +| [适配器](/docs/app/getting-started/deploying#adapters) | 平台相关 | -了解在自托管 Next.js 时如何[配置 ISR](/docs/app/guides/self-hosting#caching-and-isr)。 +了解在自托管 Next.js 时如何 [配置 ISR](/docs/app/guides/self-hosting#caching-and-isr)。 ## 版本历史 | 版本 | 变更 | | ---------- | -------------------------------------------------------- | -| `v14.1.0` | 重命名为 `cacheHandler` 并成为稳定功能。 | -| `v13.4.0` | `incrementalCacheHandlerPath` 支持 `revalidateTag`。 | -| `v13.4.0` | `incrementalCacheHandlerPath` 支持独立输出。 | -| `v12.2.0` | 实验性添加 `incrementalCacheHandlerPath`。 | \ No newline at end of file +| `v14.1.0` | 重命名为 `cacheHandler` 并成为稳定功能。 | +| `v13.4.0` | `incrementalCacheHandlerPath` 支持 `revalidateTag`。 | +| `v13.4.0` | `incrementalCacheHandlerPath` 支持独立输出。 | +| `v12.2.0` | 实验性添加 `incrementalCacheHandlerPath`。 | \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/redirects.mdx b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/redirects.mdx index 514af834..c37b8156 100644 --- a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/redirects.mdx +++ b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/redirects.mdx @@ -1,15 +1,15 @@ --- -source-updated-at: 2025-05-16T04:52:11.000Z -translation-updated-at: 2025-05-20T22:58:33.974Z +source-updated-at: 2025-05-21T18:33:43.000Z +translation-updated-at: 2025-05-21T21:08:52.435Z title: 重定向 -description: 为你的 Next.js 应用添加重定向功能。 +description: 为您的 Next.js 应用添加重定向功能。 --- -{/* 本文档内容在应用路由和页面路由间共享。如需添加仅适用于页面路由的内容,可使用 `内容` 组件。所有共享内容不应包裹在任何组件中。 */} +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加仅适用于页面路由的内容。任何共享内容不应包裹在组件中。 */} -重定向功能允许你将传入的请求路径转向到不同的目标路径。 +重定向功能允许您将传入的请求路径转向到不同的目标路径。 -要使用重定向,可以在 `next.config.js` 中配置 `redirects` 键: +要使用重定向,您可以在 `next.config.js` 中使用 `redirects` 键: ```js filename="next.config.js" module.exports = { @@ -25,24 +25,24 @@ module.exports = { } ``` -`redirects` 是一个异步函数,需要返回包含 `source`、`destination` 和 `permanent` 属性的对象数组: +`redirects` 是一个异步函数,需要返回一个包含对象的数组,这些对象具有 `source`、`destination` 和 `permanent` 属性: - `source`:传入的请求路径模式 -- `destination`:要路由到的目标路径 -- `permanent`:`true` 或 `false` - 若为 `true` 则使用 308 状态码(指示客户端/搜索引擎永久缓存此重定向),若为 `false` 则使用 307 状态码(临时重定向,不缓存) +- `destination`:您想要路由到的目标路径 +- `permanent`:`true` 或 `false` - 如果为 `true` 将使用 308 状态码,指示客户端/搜索引擎永久缓存此重定向;如果为 `false` 则使用 307 临时状态码且不会被缓存 -> **为什么 Next.js 使用 307 和 308?** 传统上 302 用于临时重定向,301 用于永久重定向,但许多浏览器会将重定向请求方法改为 `GET` 而忽略原始方法。例如,浏览器向 `POST /v1/users` 发起请求并收到 302 状态码和 location `/v2/users` 时,后续请求可能会变成 `GET /v2/users` 而非预期的 `POST /v2/users`。Next.js 使用 307 临时重定向和 308 永久重定向状态码来明确保留原始请求方法。 +> **为什么 Next.js 使用 307 和 308?** 传统上 302 用于临时重定向,301 用于永久重定向,但许多浏览器会将重定向的请求方法改为 `GET`,而不管原始方法是什么。例如,如果浏览器向 `POST /v1/users` 发起请求并收到状态码 `302` 和位置 `/v2/users`,后续请求可能会变成 `GET /v2/users` 而非预期的 `POST /v2/users`。Next.js 使用 307 临时重定向和 308 永久重定向状态码来明确保留所使用的请求方法。 -- `basePath`:`false` 或 `undefined` - 若为 false 则匹配时不包含 `basePath`,仅适用于外部重定向 -- `locale`:`false` 或 `undefined` - 匹配时是否排除语言环境 -- `has`:包含 `type`、`key` 和 `value` 属性的 [has 对象](#header-cookie-and-query-matching)数组 -- `missing`:包含 `type`、`key` 和 `value` 属性的 [missing 对象](#header-cookie-and-query-matching)数组 +- `basePath`: `false` 或 `undefined` - 如果为 false,匹配时将不包含 `basePath`,仅可用于外部重定向 +- `locale`: `false` 或 `undefined` - 匹配时是否不包含语言环境 +- `has` 是由 `type`、`key` 和 `value` 属性组成的 [has 对象](#header-cookie-and-query-matching)数组 +- `missing` 是由 `type`、`key` 和 `value` 属性组成的 [missing 对象](#header-cookie-and-query-matching)数组 -重定向检查优先于文件系统(包括页面和 `/public` 文件)。 +重定向检查会优先于文件系统(包括页面和 `/public` 文件)执行。 -使用页面路由时,除非存在 [中间件](/docs/app/building-your-application/routing/middleware) 且匹配路径,否则重定向不会应用于客户端路由(`Link`、`router.push`)。 +当使用页面路由时,除非存在 [中间件](/docs/app/building-your-application/routing/middleware) 且匹配路径,否则重定向不会应用于客户端路由(`Link`、`router.push`)。 -应用重定向时,请求中提供的所有查询参数都会传递到目标地址。例如以下重定向配置: +应用重定向时,请求中提供的任何查询值都将传递到重定向目标。例如,参见以下重定向配置: ```js { @@ -52,13 +52,13 @@ module.exports = { } ``` -> **须知**:请确保在 `source` 和 `destination` 路径参数中的冒号 `:` 前包含正斜杠 `/`,否则路径将被视为字面字符串,可能导致无限重定向。 +> **须知**:请记住在 `source` 和 `destination` 路径参数中的冒号 `:` 前包含正斜杠 `/`,否则路径将被视为字面字符串,可能导致无限重定向。 当请求 `/old-blog/post-1?hello=world` 时,客户端将被重定向到 `/blog/post-1?hello=world`。 ## 路径匹配 -支持路径匹配,例如 `/old-blog/:slug` 会匹配 `/old-blog/hello-world`(不匹配嵌套路径): +支持路径匹配,例如 `/old-blog/:slug` 将匹配 `/old-blog/hello-world`(不匹配嵌套路径): ```js filename="next.config.js" module.exports = { @@ -76,7 +76,7 @@ module.exports = { ### 通配符路径匹配 -要匹配通配符路径,可在参数后使用 `*`,例如 `/blog/:slug*` 会匹配 `/blog/a/b/c/d/hello-world`: +要匹配通配符路径,可以在参数后使用 `*`,例如 `/blog/:slug*` 将匹配 `/blog/a/b/c/d/hello-world`: ```js filename="next.config.js" module.exports = { @@ -94,7 +94,7 @@ module.exports = { ### 正则表达式路径匹配 -要匹配正则表达式路径,可将正则表达式包裹在参数后的括号中,例如 `/post/:slug(\\d{1,})` 会匹配 `/post/123` 但不匹配 `/post/abc`: +要匹配正则表达式路径,可以将正则表达式包裹在参数后的括号中,例如 `/post/:slug(\\d{1,})` 将匹配 `/post/123` 但不匹配 `/post/abc`: ```js filename="next.config.js" module.exports = { @@ -110,7 +110,7 @@ module.exports = { } ``` -以下字符 `(`、`)`、`{`、`}`、`:`、`*`、`+`、`?` 用于正则路径匹配,因此在 `source` 中作为非特殊值使用时,必须通过添加 `\\` 进行转义: +以下字符 `(`、`)`、`{`、`}`、`:`、`*`、`+`、`?` 用于正则表达式路径匹配,因此当在 `source` 中作为非特殊值使用时,必须通过在前面添加 `\\` 进行转义: ```js filename="next.config.js" module.exports = { @@ -127,21 +127,22 @@ module.exports = { } ``` -## 请求头、Cookie 和查询参数匹配 +## 请求头、Cookie 和查询匹配 -若需仅在请求头、cookie 或查询参数也匹配 `has` 字段或不匹配 `missing` 字段时才应用重定向,可配置这些字段。必须同时满足 `source` 和所有 `has` 项匹配,且所有 `missing` 项不匹配才会应用重定向。 +要仅在请求头、Cookie 或查询值也匹配 `has` 字段或不匹配 `missing` 字段时应用重定向,可以使用这些字段。必须同时匹配 `source` 和所有 `has` 项,且所有 `missing` 项都不匹配才会应用重定向。 `has` 和 `missing` 项可包含以下字段: -- `type`:`String` - 必须是 `header`、`cookie`、`host` 或 `query` 之一 -- `key`:`String` - 要匹配的键名 -- `value`:`String` 或 `undefined` - 要检查的值(若为 undefined 则匹配任意值)。可使用类似正则的字符串捕获特定值部分,例如对值 `first-(?.*)` 匹配 `first-second` 时,目标路径中可使用 `:paramName` 引用 `second` +- `type`: `String` - 必须是 `header`、`cookie`、`host` 或 `query` 之一 +- `key`: `String` - 要匹配的选定类型的键 +- `value`: `String` 或 `undefined` - 要检查的值,如果为 undefined 则匹配任何值。可以使用类似正则表达式的字符串捕获值的特定部分,例如如果对 `first-second` 使用值 `first-(?.*)`,则可以在目标路径中使用 `:paramName` 来引用 `second` ```js filename="next.config.js" module.exports = { async redirects() { return [ - // 当存在 `x-redirect-me` 请求头时应用此重定向 + // 如果存在 `x-redirect-me` 请求头 + // 将应用此重定向 { source: '/:path((?!another-page$).*)', has: [ @@ -153,7 +154,8 @@ module.exports = { permanent: false, destination: '/another-page', }, - // 当存在 `x-dont-redirect` 请求头时不应用此重定向 + // 如果存在 `x-dont-redirect` 请求头 + // 将不应用此重定向 { source: '/:path((?!another-page$).*)', missing: [ @@ -165,14 +167,16 @@ module.exports = { permanent: false, destination: '/another-page', }, - // 当源路径、查询参数和 cookie 都匹配时应用此重定向 + // 如果匹配源路径、查询和 Cookie + // 将应用此重定向 { source: '/specific/:path*', has: [ { type: 'query', key: 'page', - // 由于提供了值且未使用命名捕获组(如 `(?home)`),page 值不会出现在目标路径中 + // 由于提供了值且未使用命名捕获组如 (?home) + // page 值将不会出现在目标路径中 value: 'home', }, { @@ -184,7 +188,8 @@ module.exports = { permanent: false, destination: '/another/:path*', }, - // 当存在 `x-authorized` 请求头且包含匹配值时应用此重定向 + // 如果存在 `x-authorized` 请求头且包含匹配值 + // 将应用此重定向 { source: '/', has: [ @@ -197,7 +202,8 @@ module.exports = { permanent: false, destination: '/home?authorized=:authorized', }, - // 当主机名为 `example.com` 时应用此重定向 + // 如果主机是 `example.com` + // 将应用此重定向 { source: '/:path((?!another-page$).*)', has: [ @@ -216,7 +222,7 @@ module.exports = { ### 支持 basePath 的重定向 -当结合 [`basePath` 支持](/docs/app/api-reference/config/next-config-js/basePath) 使用重定向时,每个 `source` 和 `destination` 会自动添加 `basePath` 前缀,除非在重定向中设置 `basePath: false`: +当结合 [`basePath` 支持](/docs/app/api-reference/config/next-config-js/basePath) 使用重定向时,每个 `source` 和 `destination` 会自动添加 `basePath` 前缀,除非您在重定向中添加 `basePath: false`: ```js filename="next.config.js" module.exports = { @@ -230,7 +236,7 @@ module.exports = { permanent: false, }, { - // 由于设置了 basePath: false,不会添加 /docs + // 由于设置了 basePath: false 不会添加 /docs source: '/without-basePath', destination: 'https://example.com', basePath: false, @@ -245,13 +251,13 @@ module.exports = { -当结合 [`i18n` 支持](/docs/app/building-your-application/routing/internationalization) 使用重定向时,每个 `source` 和 `destination` 会自动添加配置的 `locales` 前缀,除非在重定向中设置 `locale: false`。若使用 `locale: false`,则必须手动为 `source` 和 `destination` 添加语言环境前缀才能正确匹配。 +当结合 [`i18n` 支持](/docs/app/guides/internationalization) 使用重定向时,每个 `source` 和 `destination` 会自动添加前缀以处理配置的 `locales`,除非您在重定向中添加 `locale: false`。如果使用 `locale: false`,则必须在 `source` 和 `destination` 前添加语言环境前缀才能正确匹配。 -当结合 [`i18n` 支持](/docs/pages/building-your-application/routing/internationalization) 使用重定向时,每个 `source` 和 `destination` 会自动添加配置的 `locales` 前缀,除非在重定向中设置 `locale: false`。若使用 `locale: false`,则必须手动为 `source` 和 `destination` 添加语言环境前缀才能正确匹配。 +当结合 [`i18n` 支持](/docs/pages/guides/internationalization) 使用重定向时,每个 `source` 和 `destination` 会自动添加前缀以处理配置的 `locales`,除非您在重定向中添加 `locale: false`。如果使用 `locale: false`,则必须在 `source` 和 `destination` 前添加语言环境前缀才能正确匹配。 @@ -270,14 +276,14 @@ module.exports = { permanent: false, }, { - // 由于设置了 locale: false,不自动处理语言环境 + // 由于设置了 locale: false 不会自动处理语言环境 source: '/nl/with-locale-manual', destination: '/nl/another', locale: false, permanent: false, }, { - // 匹配 '/' 因为 `en` 是默认语言环境 + // 这将匹配 '/' 因为 `en` 是默认语言环境 source: '/en', destination: '/en/another', locale: false, @@ -291,7 +297,8 @@ module.exports = { locale: false, }, { - // 会被转换为 /(en|fr|de)/(.*),因此不会像 /:path* 那样匹配顶级 `/` 或 `/fr` 路由 + // 这将转换为 /(en|fr|de)/(.*) 因此不会匹配顶级 + // `/` 或 `/fr` 路由,像 /:path* 那样 source: '/(.*)', destination: '/another', permanent: false, @@ -301,17 +308,17 @@ module.exports = { } ``` -在极少数情况下,可能需要为旧版 HTTP 客户端分配自定义状态码以实现正确重定向。此时可使用 `statusCode` 属性替代 `permanent` 属性(不可同时使用)。为确保 IE11 兼容性,308 状态码会自动添加 `Refresh` 头。 +在极少数情况下,您可能需要为旧版 HTTP 客户端分配自定义状态码以实现正确重定向。此时可以使用 `statusCode` 属性替代 `permanent` 属性,但不能同时使用两者。为确保 IE11 兼容性,308 状态码会自动添加 `Refresh` 请求头。 ## 其他重定向方式 -- 在 [API 路由](/docs/pages/building-your-application/routing/api-routes) 和 [路由处理器](/docs/app/building-your-application/routing/route-handlers) 中,可根据传入请求进行重定向 -- 在 [`getStaticProps`](/docs/pages/building-your-application/data-fetching/get-static-props) 和 [`getServerSideProps`](/docs/pages/building-your-application/data-fetching/get-server-side-props) 中,可在请求时重定向特定页面 +- 在 [API 路由](/docs/pages/building-your-application/routing/api-routes) 和 [路由处理器](/docs/app/building-your-application/routing/route-handlers) 中,您可以根据传入请求进行重定向 +- 在 [`getStaticProps`](/docs/pages/building-your-application/data-fetching/get-static-props) 和 [`getServerSideProps`](/docs/pages/building-your-application/data-fetching/get-server-side-props) 中,您可以在请求时重定向特定页面 ## 版本历史 -| 版本 | 变更 | -| ---------- | ------------------- | -| `v13.3.0` | 新增 `missing` 支持 | -| `v10.2.0` | 新增 `has` 支持 | +| 版本 | 变更 | +| ---------- | -------------------- | +| `v13.3.0` | 新增 `missing` 功能 | +| `v10.2.0` | 新增 `has` 功能 | | `v9.5.0` | 新增 `redirects` 功能 | \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/rewrites.mdx b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/rewrites.mdx index 9f7a92f8..ea929b75 100644 --- a/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/rewrites.mdx +++ b/apps/docs/content/zh-hans/docs/01-app/05-api-reference/05-config/01-next-config-js/rewrites.mdx @@ -1,27 +1,27 @@ --- -source-updated-at: 2025-05-16T04:52:11.000Z -translation-updated-at: 2025-05-20T22:59:03.964Z +source-updated-at: 2025-05-21T18:33:43.000Z +translation-updated-at: 2025-05-21T21:09:21.469Z title: rewrites -description: 为 Next.js 应用添加重写规则 +description: 为 Next.js 应用添加重写规则。 --- -{/* 本文档内容在应用路由器和页面路由器之间共享。您可以使用 `内容` 组件添加特定于页面路由器的内容。任何共享内容不应包裹在组件中 */} +{/* 本文档内容在应用路由器和页面路由器之间共享。您可以使用 `内容` 组件添加特定于页面路由器的内容。任何共享内容不应包裹在组件中。 */} -重写规则允许您将传入的请求路径映射到不同的目标路径。 +重写 (rewrites) 允许您将传入的请求路径映射到不同的目标路径。 -重写规则充当 URL 代理并隐藏目标路径,使用户看起来像是没有改变他们在网站上的位置。相比之下,[重定向](/docs/app/api-reference/config/next-config-js/redirects) 会重新路由到新页面并显示 URL 变化。 +重写充当 URL 代理并隐藏目标路径,使用户看起来像是没有改变他们在网站上的位置。相比之下,[重定向 (redirects)](/docs/app/api-reference/config/next-config-js/redirects) 会重新路由到新页面并显示 URL 变化。 -重写规则充当 URL 代理并隐藏目标路径,使用户看起来像是没有改变他们在网站上的位置。相比之下,[重定向](/docs/pages/api-reference/config/next-config-js/redirects) 会重新路由到新页面并显示 URL 变化。 +重写充当 URL 代理并隐藏目标路径,使用户看起来像是没有改变他们在网站上的位置。相比之下,[重定向 (redirects)](/docs/pages/api-reference/config/next-config-js/redirects) 会重新路由到新页面并显示 URL 变化。 -要使用重写规则,您可以在 `next.config.js` 中使用 `rewrites` 键: +要使用重写功能,您可以在 `next.config.js` 中使用 `rewrites` 键: ```js filename="next.config.js" module.exports = { @@ -36,18 +36,18 @@ module.exports = { } ``` -重写规则会应用于客户端路由,在上述示例中 `` 将会应用重写。 +重写会应用于客户端路由,在上述示例中,`` 将会应用重写规则。 `rewrites` 是一个异步函数,期望返回一个数组或包含数组的对象(见下文),其中包含具有 `source` 和 `destination` 属性的对象: -- `source`: `String` - 传入的请求路径模式 -- `destination`: `String` - 您想要路由到的路径 -- `basePath`: `false` 或 `undefined` - 如果为 false,则在匹配时不包含 basePath,仅可用于外部重写 -- `locale`: `false` 或 `undefined` - 匹配时是否不包含区域设置 -- `has` 是一个包含 `type`、`key` 和 `value` 属性的 [has 对象](#header-cookie-and-query-matching) 数组 -- `missing` 是一个包含 `type`、`key` 和 `value` 属性的 [missing 对象](#header-cookie-and-query-matching) 数组 +- `source`: `String` - 传入的请求路径模式。 +- `destination`: `String` - 您想要路由到的路径。 +- `basePath`: `false` 或 `undefined` - 如果为 false,则在匹配时不包含 basePath,仅用于外部重写。 +- `locale`: `false` 或 `undefined` - 匹配时是否不包含区域设置。 +- `has` 是一个包含 `type`、`key` 和 `value` 属性的 [has 对象](#header-cookie-and-query-matching) 数组。 +- `missing` 是一个包含 `type`、`key` 和 `value` 属性的 [missing 对象](#header-cookie-and-query-matching) 数组。 -当 `rewrites` 函数返回数组时,重写规则会在检查文件系统(页面和 `/public` 文件)之后、动态路由之前应用。当 `rewrites` 函数返回具有特定结构的对象数组时(自 Next.js `v10.1` 起),可以更改并更精细地控制此行为: +当 `rewrites` 函数返回一个数组时,重写会在检查文件系统(页面和 `/public` 文件)之后、动态路由之前应用。当 `rewrites` 函数返回特定结构的对象数组时(自 Next.js `v10.1` 起),可以更改并更精细地控制此行为: ```js filename="next.config.js" module.exports = { @@ -55,7 +55,7 @@ module.exports = { return { beforeFiles: [ // 这些重写在检查 headers/redirects 之后 - // 且在包括 _next/public 文件在内的所有文件之前检查 + // 并在所有文件(包括 _next/public 文件)之前检查 // 允许覆盖页面文件 { source: '/some-page', @@ -83,18 +83,18 @@ module.exports = { } ``` -> **须知**:`beforeFiles` 中的重写在匹配源后不会立即检查文件系统/动态路由,它们会继续检查直到所有 `beforeFiles` 都被检查完毕。 +> **须知**:`beforeFiles` 中的重写在匹配到源路径后不会立即检查文件系统/动态路由,而是会继续检查直到所有 `beforeFiles` 都被检查完毕。 -Next.js 路由检查的顺序是: +Next.js 路由的检查顺序是: 1. 检查/应用 [headers](/docs/app/api-reference/config/next-config-js/headers) 2. 检查/应用 [redirects](/docs/app/api-reference/config/next-config-js/redirects) 3. 检查/应用 `beforeFiles` 重写 -4. 检查/提供来自 [public 目录](/docs/app/api-reference/file-conventions/public-folder)、`_next/static` 文件和非动态页面的静态文件 -5. 检查/应用 `afterFiles` 重写,如果匹配其中一个重写,则在每次匹配后检查动态路由/静态文件 -6. 检查/应用 `fallback` 重写,这些重写在渲染 404 页面之前且在检查动态路由/所有静态资源之后应用。如果在 `getStaticPaths` 中使用 [fallback: true/'blocking'](/docs/pages/api-reference/functions/get-static-paths#fallback-true),则 `next.config.js` 中定义的 `fallback` 重写将_不会_运行。 +4. 检查/提供 [public 目录](/docs/app/api-reference/file-conventions/public-folder) 中的静态文件、`_next/static` 文件和非动态页面 +5. 检查/应用 `afterFiles` 重写,如果匹配到其中一条重写,我们会在每次匹配后检查动态路由/静态文件 +6. 检查/应用 `fallback` 重写,这些重写在渲染 404 页面之前、检查完动态路由/所有静态资源之后应用。如果您在 `getStaticPaths` 中使用 [fallback: true/'blocking'](/docs/pages/api-reference/functions/get-static-paths#fallback-true),则 `next.config.js` 中定义的 `fallback` 重写将*不会*运行。 @@ -103,15 +103,15 @@ Next.js 路由检查的顺序是: 1. 检查/应用 [headers](/docs/pages/api-reference/config/next-config-js/headers) 2. 检查/应用 [redirects](/docs/pages/api-reference/config/next-config-js/redirects) 3. 检查/应用 `beforeFiles` 重写 -4. 检查/提供来自 [public 目录](/docs/pages/api-reference/file-conventions/public-folder)、`_next/static` 文件和非动态页面的静态文件 -5. 检查/应用 `afterFiles` 重写,如果匹配其中一个重写,则在每次匹配后检查动态路由/静态文件 -6. 检查/应用 `fallback` 重写,这些重写在渲染 404 页面之前且在检查动态路由/所有静态资源之后应用。如果在 `getStaticPaths` 中使用 [fallback: true/'blocking'](/docs/pages/api-reference/functions/get-static-paths#fallback-true),则 `next.config.js` 中定义的 `fallback` 重写将_不会_运行。 +4. 检查/提供 [public 目录](/docs/pages/api-reference/file-conventions/public-folder) 中的静态文件、`_next/static` 文件和非动态页面 +5. 检查/应用 `afterFiles` 重写,如果匹配到其中一条重写,我们会在每次匹配后检查动态路由/静态文件 +6. 检查/应用 `fallback` 重写,这些重写在渲染 404 页面之前、检查完动态路由/所有静态资源之后应用。如果您在 `getStaticPaths` 中使用 [fallback: true/'blocking'](/docs/pages/api-reference/functions/get-static-paths#fallback-true),则 `next.config.js` 中定义的 `fallback` 重写将*不会*运行。 ## 重写参数 -当在重写中使用参数时,如果 `destination` 中没有使用任何参数,则参数将默认传递到查询中。 +在重写中使用参数时,如果 `destination` 中没有使用任何参数,则参数会默认传递到查询中。 ```js filename="next.config.js" module.exports = { @@ -158,7 +158,7 @@ module.exports = { } ``` -> **须知**:来自 [自动静态优化](/docs/pages/building-your-application/rendering/automatic-static-optimization) 或 [预渲染](/docs/pages/building-your-application/data-fetching/get-static-props) 的静态页面的重写参数将在客户端 hydration 后解析并提供在查询中。 +> **须知**:来自 [自动静态优化 (Automatic Static Optimization)](/docs/pages/building-your-application/rendering/automatic-static-optimization) 或 [预渲染 (prerendering)](/docs/pages/building-your-application/data-fetching/get-static-props) 的静态页面的重写参数将在客户端水合后解析并出现在查询中。 ## 路径匹配 @@ -179,7 +179,7 @@ module.exports = { ### 通配符路径匹配 -要匹配通配符路径,您可以在参数后使用 `*`,例如 `/blog/:slug*` 将匹配 `/blog/a/b/c/d/hello-world`: +要匹配通配符路径,可以在参数后使用 `*`,例如 `/blog/:slug*` 将匹配 `/blog/a/b/c/d/hello-world`: ```js filename="next.config.js" module.exports = { @@ -196,7 +196,7 @@ module.exports = { ### 正则表达式路径匹配 -要匹配正则表达式路径,您可以将正则表达式包裹在参数后的括号中,例如 `/blog/:slug(\\d{1,})` 将匹配 `/blog/123` 但不匹配 `/blog/abc`: +要匹配正则表达式路径,可以将正则表达式包裹在参数后的括号中,例如 `/blog/:slug(\\d{1,})` 将匹配 `/blog/123` 但不匹配 `/blog/abc`: ```js filename="next.config.js" module.exports = { @@ -211,14 +211,14 @@ module.exports = { } ``` -以下字符 `(`、`)`、`{`、`}`、`[`、`]`、`|`、`\`、`^`、`.`、`:`、`*`、`+`、`-`、`?`、`$` 用于正则表达式路径匹配,因此当在 `source` 中作为非特殊值使用时,必须通过在它们前面添加 `\\` 来转义: +以下字符 `(`、`)`、`{`、`}`、`[`、`]`、`|`、`\`、`^`、`.`、`:`、`*`、`+`、`-`、`?`、`$` 用于正则表达式路径匹配,因此当它们在 `source` 中作为非特殊值使用时,必须通过在其前面添加 `\\` 进行转义: ```js filename="next.config.js" module.exports = { async rewrites() { return [ { - // 这将匹配请求的 `/english(default)/something` + // 这将匹配请求 `/english(default)/something` source: '/english\\(default\\)/:slug', destination: '/en-us/:slug', }, @@ -229,13 +229,13 @@ module.exports = { ## 头部、Cookie 和查询匹配 -要仅在头部、cookie 或查询值也匹配 `has` 字段或不匹配 `missing` 字段时才匹配重写,可以使用 `has` 和 `missing`。`source` 和所有 `has` 项必须匹配,且所有 `missing` 项必须不匹配才能应用重写。 +要仅在头部、cookie 或查询值也匹配 `has` 字段或不匹配 `missing` 字段时才匹配重写,可以使用 `has` 和 `missing`。`source` 和所有 `has` 项必须匹配,且所有 `missing` 项必须不匹配,才会应用重写。 -`has` 和 `missing` 项可以具有以下字段: +`has` 和 `missing` 项可以包含以下字段: -- `type`: `String` - 必须是 `header`、`cookie`、`host` 或 `query` 之一 -- `key`: `String` - 从所选类型中要匹配的键 -- `value`: `String` 或 `undefined` - 要检查的值,如果未定义则匹配任何值。可以使用类似正则表达式的字符串捕获值的特定部分,例如如果对 `first-second` 使用值 `first-(?.*)`,则 `second` 可以在 destination 中使用 `:paramName` +- `type`: `String` - 必须是 `header`、`cookie`、`host` 或 `query` 之一。 +- `key`: `String` - 要匹配的选定类型的键。 +- `value`: `String` 或 `undefined` - 要检查的值,如果为 undefined 则匹配任何值。可以使用类似正则表达式的字符串捕获值的特定部分,例如如果值 `first-(?.*)` 用于 `first-second`,则 `second` 可以在 destination 中使用 `:paramName`。 ```js filename="next.config.js" module.exports = { @@ -273,7 +273,7 @@ module.exports = { { type: 'query', key: 'page', - // 由于提供了 value 且未使用命名捕获组如 (?home), + // 由于提供了 value 且未使用命名捕获组(如 `(?home)`), // page 值将不会在 destination 中可用 value: 'home', }, @@ -286,7 +286,7 @@ module.exports = { destination: '/:path*/home', }, // 如果存在头部 `x-authorized` 且 - // 包含匹配值,将应用此重写 + // 包含匹配的值,将应用此重写 { source: '/:path*', has: [ @@ -298,7 +298,7 @@ module.exports = { ], destination: '/home?authorized=:authorized', }, - // 如果主机是 `example.com`, + // 如果 host 是 `example.com`, // 将应用此重写 { source: '/:path*', @@ -320,11 +320,11 @@ module.exports = {
示例 -- [使用多区域](https://github.com/vercel/next.js/tree/canary/examples/with-zones) +- [使用多区域 (Using Multiple Zones)](https://github.com/vercel/next.js/tree/canary/examples/with-zones)
-重写规则允许您重写到外部 URL。这对于逐步采用 Next.js 特别有用。以下是重写主应用的 `/blog` 路由到外部站点的示例。 +重写允许您重写到外部 URL。这对于逐步采用 Next.js 特别有用。以下是一个重写示例,将您主应用的 `/blog` 路由重定向到外部站点。 ```js filename="next.config.js" module.exports = { @@ -343,7 +343,7 @@ module.exports = { } ``` -如果使用 `trailingSlash: true`,您还需要在 `source` 参数中插入尾部斜杠。如果目标服务器也期望尾部斜杠,则应将其包含在 `destination` 参数中。 +如果您使用 `trailingSlash: true`,还需要在 `source` 参数中插入尾部斜杠。如果目标服务器也期望尾部斜杠,则应在 `destination` 参数中也包含尾部斜杠。 ```js filename="next.config.js" module.exports = { @@ -367,7 +367,7 @@ module.exports = { 您还可以让 Next.js 在检查所有 Next.js 路由后回退到代理现有网站。 -这样,在将更多页面迁移到 Next.js 时,您无需更改重写配置 +这样,当您将更多页面迁移到 Next.js 时,就不必更改重写配置。 ```js filename="next.config.js" module.exports = { @@ -386,7 +386,7 @@ module.exports = { ### 支持 basePath 的重写 -当在重写中利用 [`basePath` 支持](/docs/app/api-reference/config/next-config-js/basePath) 时,每个 `source` 和 `destination` 都会自动添加 `basePath` 前缀,除非您在重写中添加 `basePath: false`: +当在重写中使用 [`basePath` 支持](/docs/app/api-reference/config/next-config-js/basePath) 时,每个 `source` 和 `destination` 会自动添加 `basePath` 前缀,除非您在重写中添加 `basePath: false`: ```js filename="next.config.js" module.exports = { @@ -414,7 +414,7 @@ module.exports = { ### 支持 i18n 的重写 -当在重写中利用 [`i18n` 支持](/docs/pages/building-your-application/routing/internationalization) 时,每个 `source` 和 `destination` 都会自动添加前缀以处理配置的 `locales`,除非您在重写中添加 `locale: false`。如果使用 `locale: false`,您必须在 `source` 和 `destination` 前添加区域设置才能正确匹配。 +当在重写中使用 [`i18n` 支持](/docs/pages/guides/internationalization) 时,每个 `source` 和 `destination` 会自动添加前缀以处理配置的 `locales`,除非您在重写中添加 `locale: false`。如果使用 `locale: false`,则必须为 `source` 和 `destination` 添加区域设置前缀才能正确匹配。 ```js filename="next.config.js" module.exports = { @@ -448,7 +448,8 @@ module.exports = { locale: false, }, { - // 这将转换为 /(en|fr|de)/(.*),因此不会像 /:path* 那样匹配顶级 `/` 或 `/fr` 路由 + // 这将转换为 /(en|fr|de)/(.*),因此不会匹配顶级 + // `/` 或 `/fr` 路由,像 /:path* 那样 source: '/(.*)', destination: '/another', }, @@ -463,6 +464,6 @@ module.exports = { | 版本 | 变更 | | ---------- | ------------------ | -| `v13.3.0` | 添加 `missing` | -| `v10.2.0` | 添加 `has` | -| `v9.5.0` | 添加头部支持 | \ No newline at end of file +| `v13.3.0` | 添加 `missing`。 | +| `v10.2.0` | 添加 `has`。 | +| `v9.5.0` | 添加头部支持。 | \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/01-defining-routes.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/01-defining-routes.mdx new file mode 100644 index 00000000..f28307d4 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/01-defining-routes.mdx @@ -0,0 +1,60 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:07:32.674Z +title: 定义路由 +description: 学习如何在 Next.js 中创建第一个路由。 +related: + description: 了解更多关于创建页面和布局的内容。 + links: + - app/building-your-application/routing/pages-and-layouts +--- + +> 建议继续阅读前先了解[路由基础](/docs/app/building-your-application/routing)。 + +本页将指导您如何在 Next.js 应用中定义和组织路由。 + +## 创建路由 + +Next.js 使用基于文件系统的路由器,其中**文件夹**用于定义路由。 + +每个文件夹代表一个[路由段 (route segment)](/docs/app/building-your-application/routing#route-segments),映射到 URL 的一个段。要创建[嵌套路由 (nested route)](/docs/app/building-your-application/routing#nested-routes),可以将文件夹相互嵌套。 + +路由段与路径段的对应关系 + +使用特殊的 [`page.js` 文件](/docs/app/building-your-application/routing/pages-and-layouts#pages)可使路由段公开访问。 + +定义路由 + +在此示例中,`/dashboard/analytics` URL 路径_无法_公开访问,因为它没有对应的 `page.js` 文件。此文件夹可用于存储组件、样式表、图像或其他共置文件。 + +> **须知**:特殊文件可使用 `.js`、`.jsx` 或 `.tsx` 扩展名。 + +## 创建用户界面 + +使用[特殊文件约定](/docs/app/building-your-application/routing#file-conventions)为每个路由段创建用户界面。最常见的是[页面 (pages)](/docs/app/building-your-application/routing/pages-and-layouts#pages)用于显示路由专属的 UI,以及[布局 (layouts)](/docs/app/building-your-application/routing/pages-and-layouts#layouts)用于显示跨多个路由共享的 UI。 + +例如,要创建第一个页面,在 `app` 目录中添加 `page.js` 文件并导出一个 React 组件: + +```tsx filename="app/page.tsx" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +```jsx filename="app/page.js" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/02-pages-and-layouts.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/02-pages-and-layouts.mdx new file mode 100644 index 00000000..85158f34 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/02-pages-and-layouts.mdx @@ -0,0 +1,275 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:08:44.272Z +title: 页面与布局 +description: 使用应用路由 (App Router) 创建您的首个页面和共享布局。 +--- + +> 建议先阅读[路由基础](/docs/app/building-your-application/routing)和[定义路由](/docs/app/building-your-application/routing/defining-routes)页面再继续。 + +Next.js 13 中的应用路由 (App Router) 引入了新的文件约定,可轻松创建[页面](#pages)、[共享布局](#layouts)和[模板](#templates)。本指南将介绍如何在 Next.js 应用中使用这些特殊文件。 + +## 页面 + +页面是路由对应的**唯一**用户界面。您可以通过从 `page.js` 文件导出组件来定义页面。使用嵌套文件夹[定义路由](/docs/app/building-your-application/routing/defining-routes),并通过 `page.js` 文件使路由可公开访问。 + +在 `app` 目录中添加 `page.js` 文件来创建您的第一个页面: + +page.js 特殊文件 + +```tsx filename="app/page.tsx" switcher +// `app/page.tsx` 是 `/` 路由对应的界面 +export default function Page() { + return

你好,首页!

+} +``` + +```jsx filename="app/page.js" switcher +// `app/page.js` 是 `/` 路由对应的界面 +export default function Page() { + return

你好,首页!

+} +``` + +```tsx filename="app/dashboard/page.tsx" switcher +// `app/dashboard/page.tsx` 是 `/dashboard` 路由对应的界面 +export default function Page() { + return

你好,仪表盘页面!

+} +``` + +```jsx filename="app/dashboard/page.js" switcher +// `app/dashboard/page.js` 是 `/dashboard` 路由对应的界面 +export default function Page() { + return

你好,仪表盘页面!

+} +``` + +> **须知**: +> +> - 页面始终是[路由子树](/docs/app/building-your-application/routing#terminology)的[叶节点](/docs/app/building-your-application/routing#terminology) +> - 可使用 `.js`、`.jsx` 或 `.tsx` 文件扩展名定义页面 +> - 必须使用 `page.js` 文件才能使路由段可公开访问 +> - 页面默认为[服务端组件 (Server Components)](/docs/app/building-your-application/rendering/server-components),但可设置为[客户端组件 (Client Component)](/docs/app/building-your-application/rendering/client-components) +> - 页面可以获取数据。查看[数据获取](/docs/app/building-your-application/data-fetching)章节了解更多 + +## 布局 + +布局是多个页面间**共享**的用户界面。在导航时,布局会保持状态、维持交互且不会重新渲染。布局还可以[嵌套](#nesting-layouts)。 + +您可以通过从 `layout.js` 文件默认导出一个 React 组件来定义布局。该组件应接收 `children` 属性,该属性在渲染时会被填充为子布局(如果存在)或子页面。 + +layout.js 特殊文件 + +```tsx filename="app/dashboard/layout.tsx" switcher +export default function DashboardLayout({ + children, // 将是一个页面或嵌套布局 +}: { + children: React.ReactNode +}) { + return ( +
+ {/* 在此处添加共享 UI 如页眉或侧边栏 */} + + + {children} +
+ ) +} +``` + +```jsx filename="app/dashboard/layout.js" switcher +export default function DashboardLayout({ + children, // 将是一个页面或嵌套布局 +}) { + return ( +
+ {/* 在此处添加共享 UI 如页眉或侧边栏 */} + + + {children} +
+ ) +} +``` + +> **须知**: +> +> - 最顶层的布局称为[根布局 (Root Layout)](#root-layout-required)。这个**必需**的布局会被应用中的所有页面共享。根布局必须包含 `html` 和 `body` 标签 +> - 任何路由段都可以选择定义自己的[布局](#nesting-layouts)。这些布局将在该段的所有页面间共享 +> - 路由中的布局默认会**嵌套**。每个父布局会通过 React 的 `children` 属性包裹其下方的子布局 +> - 您可以使用[路由组 (Route Groups)](/docs/app/building-your-application/routing/route-groups)来选择性地包含或排除特定路由段的共享布局 +> - 布局默认为[服务端组件 (Server Components)](/docs/app/building-your-application/rendering/server-components),但可设置为[客户端组件 (Client Component)](/docs/app/building-your-application/rendering/client-components) +> - 布局可以获取数据。查看[数据获取](/docs/app/building-your-application/data-fetching)章节了解更多 +> - 无法在父布局与其子级之间传递数据。但您可以在路由中多次获取相同数据,React 会[自动去重请求](/docs/app/building-your-application/caching#request-memoization)且不影响性能 +> - 布局无法访问其下方的路由段。要在客户端组件中访问所有路由段,可使用 [`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment) 或 [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments) +> - 可使用 `.js`、`.jsx` 或 `.tsx` 文件扩展名定义布局 +> - 同一文件夹中可同时定义 `layout.js` 和 `page.js` 文件。布局会包裹页面 + +### 根布局 (必需) + +根布局定义在 `app` 目录的顶层,适用于所有路由。此布局允许您修改从服务器返回的初始 HTML。 + +```tsx filename="app/layout.tsx" switcher +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +> **须知**: +> +> - `app` 目录**必须**包含根布局 +> - 根布局必须定义 `` 和 `` 标签,因为 Next.js 不会自动创建它们 +> - 您可以使用[内置 SEO 支持](/docs/app/building-your-application/optimizing/metadata)来管理 `` HTML 元素,例如 `` 元素 +> - 您可以使用[路由组 (Route Groups)](/docs/app/building-your-application/routing/route-groups)创建多个根布局。查看[示例](/docs/app/building-your-application/routing/route-groups#creating-multiple-root-layouts) +> - 根布局默认为[服务端组件 (Server Components)](/docs/app/building-your-application/rendering/server-components),且**不能**设置为[客户端组件 (Client Component)](/docs/app/building-your-application/rendering/client-components) + +> **从 `pages` 目录迁移:** 根布局替代了 [`_app.js`](/docs/pages/building-your-application/routing/custom-app) 和 [`_document.js`](/docs/pages/building-your-application/routing/custom-document) 文件。[查看迁移指南](/docs/app/building-your-application/upgrading/app-router-migration#migrating-_documentjs-and-_appjs) + +### 嵌套布局 + +定义在文件夹内的布局(如 `app/dashboard/layout.js`)适用于特定路由段(如 `acme.com/dashboard`),并在这些段激活时渲染。默认情况下,文件层次结构中的布局会**嵌套**,即通过 `children` 属性包裹子布局。 + +<Image + alt="嵌套布局" + srcLight="/docs/light/nested-layout.png" + srcDark="/docs/dark/nested-layout.png" + width="1600" + height="606" +/> + +```tsx filename="app/dashboard/layout.tsx" switcher +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode +}) { + return <section>{children}</section> +} +``` + +```jsx filename="app/dashboard/layout.js" switcher +export default function DashboardLayout({ children }) { + return <section>{children}</section> +} +``` + +> **须知**: +> +> - 只有根布局可以包含 `<html>` 和 `<body>` 标签 + +如果结合上述两个布局,根布局 (`app/layout.js`) 会包裹仪表盘布局 (`app/dashboard/layout.js`),而仪表盘布局会包裹 `app/dashboard/*` 内的路由段。 + +这两个布局会按如下方式嵌套: + +<Image + alt="嵌套布局" + srcLight="/docs/light/nested-layouts-ui.png" + srcDark="/docs/dark/nested-layouts-ui.png" + width="1600" + height="1026" +/> + +您可以使用[路由组 (Route Groups)](/docs/app/building-your-application/routing/route-groups)来选择性地包含或排除特定路由段的共享布局。 + +## 模板 + +模板与布局类似,会包裹每个子布局或页面。但与跨路由保持状态的布局不同,模板在导航时会为每个子级创建新实例。这意味着当用户在共享模板的路由间导航时,会挂载组件的新实例、重新创建 DOM 元素、**不保留**状态并重新同步副作用。 + +在某些需要这些特定行为的场景下,模板比布局更合适。例如: + +- 依赖 `useEffect` 的功能(如记录页面访问)和 `useState`(如每页反馈表单) +- 更改框架默认行为。例如,布局内的 Suspense 边界仅在首次加载布局时显示回退内容,而模板会在每次导航时显示回退内容 + +可以通过从 `template.js` 文件默认导出一个 React 组件来定义模板。该组件应接收 `children` 属性。 + +<Image + alt="template.js 特殊文件" + srcLight="/docs/light/template-special-file.png" + srcDark="/docs/dark/template-special-file.png" + width="1600" + height="444" +/> + +```tsx filename="app/template.tsx" switcher +export default function Template({ children }: { children: React.ReactNode }) { + return <div>{children}</div> +} +``` + +```jsx filename="app/template.js" switcher +export default function Template({ children }) { + return <div>{children}</div> +} +``` + +在嵌套方面,`template.js` 会在布局与其子级之间渲染。以下是简化输出: + +```jsx filename="输出" +<Layout> + {/* 注意模板具有唯一键 */} + <Template key={routeParam}>{children}</Template> +</Layout> +``` + +## 修改 `<head>` + +在 `app` 目录中,您可以使用[内置 SEO 支持](/docs/app/building-your-application/optimizing/metadata)修改 `<head>` HTML 元素,如 `title` 和 `meta`。 + +通过在 [`layout.js`](/docs/app/api-reference/file-conventions/layout) 或 [`page.js`](/docs/app/api-reference/file-conventions/page) 文件中导出 [`metadata` 对象](/docs/app/api-reference/functions/generate-metadata#the-metadata-object)或 [`generateMetadata` 函数](/docs/app/api-reference/functions/generate-metadata#generatemetadata-function)来定义元数据。 + +```tsx filename="app/page.tsx" switcher +import { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Next.js', +} + +export default function Page() { + return '...' +} +``` + +```jsx filename="app/page.js" switcher +export const metadata = { + title: 'Next.js', +} + +export default function Page() { + return '...' +} +``` + +> **须知**:您**不应**手动添加 `<head>` 标签(如 `<title>` 和 `<meta>`)到根布局中。而应使用[元数据 API (Metadata API)](/docs/app/api-reference/functions/generate-metadata),它能自动处理高级需求,如流式传输和去重 `<head>` 元素。 + +[在 API 参考中了解可用的元数据选项。](/docs/app/api-reference/functions/generate-metadata) \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/03-linking-and-navigating.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/03-linking-and-navigating.mdx new file mode 100644 index 00000000..7a09c5f1 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/03-linking-and-navigating.mdx @@ -0,0 +1,246 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:08:07.517Z +title: 链接与导航 +description: 了解 Next.js 中的导航工作原理,以及如何使用 Link 组件和 `useRouter` 钩子。 +related: + links: + - app/building-your-application/caching + - app/building-your-application/configuring/typescript +--- + +在 Next.js 中有两种方式实现路由间导航: + +- 使用 [`<Link>` 组件](#link-component) +- 使用 [`useRouter` 钩子](#userouter-hook) + +本文将介绍如何使用 `<Link>` 和 `useRouter()`,并深入探讨导航的工作原理。 + +## `<Link>` 组件 + +`<Link>` 是内置组件,它扩展了 HTML `<a>` 标签的功能,提供[预加载](#1-prefetching)和客户端路由导航。这是 Next.js 中实现路由导航的主要方式。 + +通过从 `next/link` 导入该组件,并向其传递 `href` 属性即可使用: + +```tsx filename="app/page.tsx" switcher +import Link from 'next/link' + +export default function Page() { + return <Link href="/dashboard">Dashboard</Link> +} +``` + +```jsx filename="app/page.js" switcher +import Link from 'next/link' + +export default function Page() { + return <Link href="/dashboard">Dashboard</Link> +} +``` + +你还可以向 `<Link>` 传递其他可选属性。详见 [API 参考文档](/docs/app/api-reference/components/link)。 + +### 示例 + +#### 链接到动态段 + +当链接到[动态段](/docs/app/building-your-application/routing/dynamic-routes)时,可以使用[模板字面量和插值](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals)生成链接列表。例如生成博客文章列表: + +```jsx filename="app/blog/PostList.js" +import Link from 'next/link' + +export default function PostList({ posts }) { + return ( + <ul> + {posts.map((post) => ( + <li key={post.id}> + <Link href={`/blog/${post.slug}`}>{post.title}</Link> + </li> + ))} + </ul> + ) +} +``` + +#### 检查活动链接 + +可以使用 [`usePathname()`](/docs/app/api-reference/functions/use-pathname) 判断链接是否处于活动状态。例如,要为活动链接添加类名,可以检查当前 `pathname` 是否与链接的 `href` 匹配: + +```tsx filename="app/components/links.tsx" switcher +'use client' + +import { usePathname } from 'next/navigation' +import Link from 'next/link' + +export function Links() { + const pathname = usePathname() + + return ( + <nav> + <ul> + <li> + <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/"> + 首页 + </Link> + </li> + <li> + <Link + className={`link ${pathname === '/about' ? 'active' : ''}`} + href="/about" + > + 关于 + </Link> + </li> + </ul> + </nav> + ) +} +``` + +```jsx filename="app/components/links.js" switcher +'use client' + +import { usePathname } from 'next/navigation' +import Link from 'next/link' + +export function Links() { + const pathname = usePathname() + + return ( + <nav> + <ul> + <li> + <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/"> + 首页 + </Link> + </li> + <li> + <Link + className={`link ${pathname === '/about' ? 'active' : ''}`} + href="/about" + > + 关于 + </Link> + </li> + </ul> + </nav> + ) +} +``` + +#### 滚动到特定 `id` + +Next.js App Router 的默认行为是滚动到新路由的顶部,或在前进/后退导航时保持滚动位置。 + +如果需要在导航时滚动到特定 `id`,可以在 URL 后添加 `#` 哈希链接,或将哈希链接传递给 `href` 属性。这是可行的,因为 `<Link>` 会渲染为 `<a>` 元素。 + +```jsx +<Link href="/dashboard#settings">设置</Link> + +// 输出 +<a href="/dashboard#settings">设置</a> +``` + +#### 禁用滚动恢复 + +Next.js App Router 默认会在新路由中滚动到顶部或在前进/后退导航时保持滚动位置。如需禁用此行为,可以向 `<Link>` 组件传递 `scroll={false}`,或向 `router.push()` 和 `router.replace()` 传递 `scroll: false`。 + +```jsx +// next/link +<Link href="/dashboard" scroll={false}> + 仪表盘 +</Link> +``` + +```jsx +// useRouter +import { useRouter } from 'next/navigation' + +const router = useRouter() + +router.push('/dashboard', { scroll: false }) +``` + +## `useRouter()` 钩子 + +`useRouter` 钩子允许你以编程方式更改路由。 + +此钩子只能在客户端组件中使用,需从 `next/navigation` 导入。 + +```jsx filename="app/page.js" +'use client' + +import { useRouter } from 'next/navigation' + +export default function Page() { + const router = useRouter() + + return ( + <button type="button" onClick={() => router.push('/dashboard')}> + 仪表盘 + </button> + ) +} +``` + +有关 `useRouter` 方法的完整列表,请参阅 [API 参考文档](/docs/app/api-reference/functions/use-router)。 + +> **建议:** 除非有特殊需求,否则应优先使用 `<Link>` 组件进行路由导航。 + +## 路由与导航工作原理 + +App Router 采用混合方式实现路由和导航。在服务端,应用代码会按路由段自动进行代码分割;在客户端,Next.js 会[预加载](#1-prefetching)并[缓存](#2-caching)路由段。这意味着当用户导航到新路由时,浏览器不会重新加载页面,只有发生变更的路由段会重新渲染——从而提升导航体验和性能。 + +### 1. 预加载 + +预加载是一种在用户访问前提前在后台加载路由的方式。 + +Next.js 中有两种预加载路由的方式: + +- **`<Link>` 组件**:当路由出现在用户视口中时自动预加载。预加载发生在页面首次加载或通过滚动进入视图时。 +- **`router.prefetch()`**:可使用 `useRouter` 钩子以编程方式预加载路由。 + +`<Link>` 的预加载行为对静态路由和动态路由有所不同: + +- [**静态路由**](/docs/app/building-your-application/rendering/server-components#static-rendering-default):`prefetch` 默认为 `true`。整个路由会被预加载并缓存。 +- [**动态路由**](/docs/app/building-your-application/rendering/server-components#dynamic-rendering):`prefetch` 默认为自动模式。仅预加载并缓存共享布局直到第一个 `loading.js` 文件,缓存时间为 30 秒。这降低了加载整个动态路由的开销,意味着你可以展示[即时加载状态](/docs/app/building-your-application/routing/loading-ui-and-streaming#instant-loading-states)以提供更好的视觉反馈。 + +你可以通过将 `prefetch` 属性设为 `false` 来禁用预加载。 + +更多信息请参阅 [`<Link>` API 参考文档](/docs/app/api-reference/components/link)。 + +> **须知:** +> +> - 预加载功能仅在生产环境中启用,开发环境中不生效。 + +### 2. 缓存 + +Next.js 有一个称为[路由缓存 (Router Cache)](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#caching-data#router-cache) 的**内存客户端缓存**。当用户在应用中导航时,[预加载](#1-prefetching)的路由段和已访问路由的 React 服务端组件负载会被存储在缓存中。 + +这意味着在导航时,系统会尽可能复用缓存,而不是向服务器发起新请求——通过减少请求次数和传输数据量来提升性能。 + +详细了解[路由缓存](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#caching-data)的工作原理及如何配置。 + +### 3. 部分渲染 + +部分渲染意味着在客户端导航时,只有发生变更的路由段会重新渲染,共享的段则会保持不变。 + +例如,在 `/dashboard/settings` 和 `/dashboard/analytics` 这两个同级路由间导航时,`settings` 和 `analytics` 页面会被渲染,而共享的 `dashboard` 布局会保持不变。 + +<Image + alt="部分渲染工作原理" + srcLight="/docs/light/partial-rendering.png" + srcDark="/docs/dark/partial-rendering.png" + width="1600" + height="945" +/> + +如果没有部分渲染,每次导航都会导致服务器重新渲染整个页面。仅渲染变更的段可以减少数据传输量和执行时间,从而提高性能。 + +### 4. 软导航 + +默认情况下,浏览器会在页面间执行硬导航。这意味着浏览器会重新加载页面并重置 React 状态(如应用中的 `useState` 钩子)和浏览器状态(如用户的滚动位置或聚焦元素)。然而在 Next.js 中,App Router 使用软导航。这意味着 React 只会渲染发生变更的段,同时保持 React 和浏览器状态不变,且不会重新加载整个页面。 + +### 5. 前进与后退导航 + +默认情况下,Next.js 会在前进和后退导航时保持滚动位置,并复用[路由缓存](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#caching-data)中的路由段。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/04-route-groups.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/04-route-groups.mdx new file mode 100644 index 00000000..c4fbda69 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/04-route-groups.mdx @@ -0,0 +1,80 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:07:22.082Z +title: 路由组 (Route Groups) +description: 路由组 (Route Groups) 可用于将 Next.js 应用程序划分为不同的部分。 +--- + +在 `app` 目录中,嵌套文件夹通常映射为 URL 路径。但您可以将文件夹标记为**路由组 (Route Group)**,从而避免该文件夹被包含在路由的 URL 路径中。 + +这使您能够将路由段和项目文件按逻辑分组,同时不影响 URL 路径结构。 + +路由组适用于以下场景: + +- [将路由分组组织](#organize-routes-without-affecting-the-url-path),例如按网站板块、功能意图或团队划分 +- 在相同路由段层级中启用[嵌套布局](/docs/app/building-your-application/routing/pages-and-layouts): + - [在同一路由段中创建多个嵌套布局,包括多个根布局](#creating-multiple-root-layouts) + - [为公共路由段中的子集路由添加布局](#opting-specific-segments-into-a-layout) + +## 约定 + +通过在文件夹名称外加圆括号即可创建路由组:`(文件夹名称)` + +## 示例 + +### 组织路由而不影响 URL 路径 + +要组织路由而不影响 URL,可创建分组来归集相关路由。圆括号内的文件夹名称不会出现在 URL 中(例如 `(marketing)` 或 `(shop)`)。 + +<Image + alt="使用路由组组织路由" + srcLight="/docs/light/route-group-organisation.png" + srcDark="/docs/dark/route-group-organisation.png" + width="1600" + height="930" +/> + +即使 `(marketing)` 和 `(shop)` 内的路由共享相同的 URL 层级,您也可以通过在其文件夹中添加 `layout.js` 文件为每个组创建不同的布局。 + +<Image + alt="具有多布局的路由组" + srcLight="/docs/light/route-group-multiple-layouts.png" + srcDark="/docs/dark/route-group-multiple-layouts.png" + width="1600" + height="768" +/> + +### 为特定路由段启用布局 + +要为特定路由启用布局,可新建路由组(例如 `(shop)`),并将需要共享布局的路由移入该组(例如 `account` 和 `cart`)。组外的路由不会共享该布局(例如 `checkout`)。 + +<Image + alt="具有选择性布局的路由组" + srcLight="/docs/light/route-group-opt-in-layouts.png" + srcDark="/docs/dark/route-group-opt-in-layouts.png" + width="1600" + height="930" +/> + +### 创建多个根布局 + +要创建多个[根布局](/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required),需移除顶层的 `layout.js` 文件,并在每个路由组内添加 `layout.js` 文件。这适用于需要将应用划分为具有完全不同 UI 或体验的场景。每个根布局中都需要添加 `<html>` 和 `<body>` 标签。 + +<Image + alt="具有多根布局的路由组" + srcLight="/docs/light/route-group-multiple-root-layouts.png" + srcDark="/docs/dark/route-group-multiple-root-layouts.png" + width="1600" + height="687" +/> + +上例中,`(marketing)` 和 `(shop)` 都拥有自己的根布局。 + +--- + +> **须知**: +> +> - 路由组的命名仅用于组织目的,不会影响 URL 路径 +> - 包含路由组的路由**不应**与其他路由解析为相同的 URL 路径。例如:由于路由组不影响 URL 结构,`(marketing)/about/page.js` 和 `(shop)/about/page.js` 都会解析为 `/about` 从而导致错误 +> - 如果使用多个根布局且没有顶层 `layout.js` 文件,您的首页 `page.js` 文件应定义在某个路由组中,例如:`app/(marketing)/page.js` +> - **跨多个根布局**导航将触发**整页加载**(而非客户端导航)。例如:从使用 `app/(shop)/layout.js` 的 `/cart` 导航到使用 `app/(marketing)/layout.js` 的 `/blog` 会触发整页加载。该特性**仅**适用于多根布局场景 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/05-dynamic-routes.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/05-dynamic-routes.mdx new file mode 100644 index 00000000..322f84f9 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/05-dynamic-routes.mdx @@ -0,0 +1,127 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:07:11.084Z +title: 动态路由 +description: 动态路由可用于根据动态数据以编程方式生成路由片段。 +related: + title: 后续步骤 + description: 如需了解更多后续操作信息,我们推荐以下章节 + links: + - app/building-your-application/routing/linking-and-navigating + - app/api-reference/functions/generate-static-params +--- + +当您无法提前获知确切的路由片段名称,并希望根据动态数据创建路由时,可以使用动态片段,这些片段会在请求时填充或在构建时[预渲染](#generating-static-params)。 + +## 约定 + +通过将文件夹名称包裹在方括号中即可创建动态片段:`[folderName]`。例如 `[id]` 或 `[slug]`。 + +动态片段会作为 `params` 属性传递给 [`layout`](/docs/app/api-reference/file-conventions/layout)、[`page`](/docs/app/api-reference/file-conventions/page)、[`route`](/docs/app/building-your-application/routing/route-handlers) 和 [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata#generatemetadata-function) 函数。 + +## 示例 + +例如,博客可以包含以下路由 `app/blog/[slug]/page.js`,其中 `[slug]` 是博客文章的动态片段。 + +```tsx filename="app/blog/[slug]/page.tsx" switcher +export default function Page({ params }: { params: { slug: string } }) { + return <div>我的文章:{params.slug}</div> +} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +export default function Page({ params }) { + return <div>我的文章:{params.slug}</div> +} +``` + +| 路由 | 示例 URL | `params` | +| ------------------------- | ----------- | --------------- | +| `app/blog/[slug]/page.js` | `/blog/a` | `{ slug: 'a' }` | +| `app/blog/[slug]/page.js` | `/blog/b` | `{ slug: 'b' }` | +| `app/blog/[slug]/page.js` | `/blog/c` | `{ slug: 'c' }` | + +查看 [generateStaticParams()](#generating-static-params) 页面了解如何为片段生成参数。 + +> **须知**:动态片段等同于 `pages` 目录中的[动态路由](/docs/app/building-your-application/routing/dynamic-routes)。 + +## 生成静态参数 + +`generateStaticParams` 函数可与[动态路由片段](/docs/app/building-your-application/routing/dynamic-routes)结合使用,以在构建时[**静态生成**](/docs/app/building-your-application/rendering/server-components#static-rendering-default)路由,而非在请求时按需生成。 + +```tsx filename="app/blog/[slug]/page.tsx" switcher +export async function generateStaticParams() { + const posts = await fetch('https://.../posts').then((res) => res.json()) + + return posts.map((post) => ({ + slug: post.slug, + })) +} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +export async function generateStaticParams() { + const posts = await fetch('https://.../posts').then((res) => res.json()) + + return posts.map((post) => ({ + slug: post.slug, + })) +} +``` + +`generateStaticParams` 函数的主要优势在于其智能数据检索能力。如果在 `generateStaticParams` 函数中使用 `fetch` 请求获取内容,这些请求会被[自动记忆化](/docs/app/building-your-application/caching#request-memoization)。这意味着在多个 `generateStaticParams`、布局和页面中具有相同参数的 `fetch` 请求只会执行一次,从而减少构建时间。 + +如果您正在从 `pages` 目录迁移,请使用[迁移指南](/docs/app/building-your-application/upgrading/app-router-migration#dynamic-paths-getstaticpaths)。 + +查看 [`generateStaticParams` 服务器函数文档](/docs/app/api-reference/functions/generate-static-params) 了解更多信息和高级用例。 + +## 通配片段 + +通过在括号内添加省略号 `[...folderName]`,动态片段可以扩展为**通配**后续片段。 + +例如,`app/shop/[...slug]/page.js` 将匹配 `/shop/clothes`,同时也匹配 `/shop/clothes/tops`、`/shop/clothes/tops/t-shirts` 等。 + +| 路由 | 示例 URL | `params` | +| ---------------------------- | ------------- | --------------------------- | +| `app/shop/[...slug]/page.js` | `/shop/a` | `{ slug: ['a'] }` | +| `app/shop/[...slug]/page.js` | `/shop/a/b` | `{ slug: ['a', 'b'] }` | +| `app/shop/[...slug]/page.js` | `/shop/a/b/c` | `{ slug: ['a', 'b', 'c'] }` | + +## 可选通配片段 + +通过将参数包裹在双方括号中 `[[...folderName]]`,通配片段可以变为**可选**。 + +例如,`app/shop/[[...slug]]/page.js` 除了匹配 `/shop/clothes`、`/shop/clothes/tops`、`/shop/clothes/tops/t-shirts` 外,**还会**匹配 `/shop`。 + +**通配**片段与**可选通配**片段的区别在于,可选情况下不携带参数的路由也会被匹配(如上例中的 `/shop`)。 + +| 路由 | 示例 URL | `params` | +| ------------------------------ | ------------- | --------------------------- | +| `app/shop/[[...slug]]/page.js` | `/shop` | `{}` | +| `app/shop/[[...slug]]/page.js` | `/shop/a` | `{ slug: ['a'] }` | +| `app/shop/[[...slug]]/page.js` | `/shop/a/b` | `{ slug: ['a', 'b'] }` | +| `app/shop/[[...slug]]/page.js` | `/shop/a/b/c` | `{ slug: ['a', 'b', 'c'] }` | + +## TypeScript + +使用 TypeScript 时,您可以根据配置的路由片段为 `params` 添加类型。 + +```tsx filename="app/blog/[slug]/page.tsx" switcher +export default function Page({ params }: { params: { slug: string } }) { + return <h1>我的页面</h1> +} +``` + +```jsx filename="app/blog/[slug]/page.js" switcher +export default function Page({ params }) { + return <h1>我的页面</h1> +} +``` + +| 路由 | `params` 类型定义 | +| ----------------------------------- | ---------------------------------------- | +| `app/blog/[slug]/page.js` | `{ slug: string }` | +| `app/shop/[...slug]/page.js` | `{ slug: string[] }` | +| `app/[categoryId]/[itemId]/page.js` | `{ categoryId: string, itemId: string }` | + +> **须知**:未来这可能会通过 [TypeScript 插件](/docs/app/building-your-application/configuring/typescript#typescript-plugin)自动完成。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/06-loading-ui-and-streaming.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/06-loading-ui-and-streaming.mdx new file mode 100644 index 00000000..a582dca6 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/06-loading-ui-and-streaming.mdx @@ -0,0 +1,182 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:07:19.580Z +title: 加载界面与流式传输 +description: 基于 Suspense 构建的加载界面允许您为特定路由段创建备用界面,并在内容准备就绪时自动进行流式传输。 +--- + +特殊文件 `loading.js` 可帮助您通过 [React Suspense](https://react.dev/reference/react/Suspense) 创建有意义的加载界面。通过这一约定,您可以在路由段内容加载时展示来自服务器的[即时加载状态](#即时加载状态)。一旦渲染完成,新内容会自动替换显示。 + +<Image + alt="加载界面" + srcLight="/docs/light/loading-ui.png" + srcDark="/docs/dark/loading-ui.png" + width="1600" + height="691" +/> + +## 即时加载状态 + +即时加载状态是导航时立即显示的备用界面。您可以预渲染加载指示器(如骨架屏和旋转图标),或未来屏幕中有意义的小部分内容(如封面图片、标题等)。这有助于用户理解应用正在响应,并提供更好的用户体验。 + +通过在文件夹内添加 `loading.js` 文件来创建加载状态。 + +<Image + alt="loading.js 特殊文件" + srcLight="/docs/light/loading-special-file.png" + srcDark="/docs/dark/loading-special-file.png" + width="1600" + height="606" +/> + +```tsx filename="app/dashboard/loading.tsx" switcher +export default function Loading() { + // 您可以在 Loading 中添加任何界面,包括骨架屏。 + return <LoadingSkeleton /> +} +``` + +```jsx filename="app/dashboard/loading.js" switcher +export default function Loading() { + // 您可以在 Loading 中添加任何界面,包括骨架屏。 + return <LoadingSkeleton /> +} +``` + +在同一文件夹中,`loading.js` 会被嵌套在 `layout.js` 内。它会自动将 `page.js` 文件及其下方所有子内容包裹在 `<Suspense>` 边界中。 + +<Image + alt="loading.js 概览" + srcLight="/docs/light/loading-overview.png" + srcDark="/docs/dark/loading-overview.png" + width="1600" + height="768" +/> + +> **须知**: +> +> - 导航是即时的,即使使用[以服务端为中心的路由](/docs/app/building-your-application/routing/linking-and-navigating#how-routing-and-navigation-works)。 +> - 导航是可中断的,这意味着切换路由无需等待当前路由内容完全加载。 +> - 共享布局在新路由段加载时仍保持交互性。 + +> **推荐:** 对路由段(布局和页面)使用 `loading.js` 约定,因为 Next.js 会优化此功能。 + +## 使用 Suspense 进行流式传输 + +除了 `loading.js`,您还可以手动为自己的界面组件创建 Suspense 边界。App 路由器支持在 [Node.js 和 Edge 运行时](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes)中使用 [Suspense](https://react.dev/reference/react/Suspense) 进行流式传输。 + +### 什么是流式传输? + +要了解流式传输在 React 和 Next.js 中的工作原理,理解**服务端渲染 (SSR)** 及其限制会很有帮助。 + +使用 SSR 时,用户看到页面并与之交互前需要完成一系列步骤: + +1. 首先,在服务端获取给定页面的所有数据。 +2. 然后,服务端渲染页面的 HTML。 +3. 页面的 HTML、CSS 和 JavaScript 被发送到客户端。 +4. 使用生成的 HTML 和 CSS 展示非交互式界面。 +5. 最后,React [注水 (hydrate)](https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html) 界面使其可交互。 + +<Image + alt="无流式传输的服务端渲染图表" + srcLight="/docs/light/server-rendering-without-streaming-chart.png" + srcDark="/docs/dark/server-rendering-without-streaming-chart.png" + width="1600" + height="612" +/> + +这些步骤是顺序且阻塞的,意味着服务端只有在所有数据获取完成后才能渲染页面的 HTML。而在客户端,React 只有在页面所有组件的代码下载完成后才能注水界面。 + +通过 React 和 Next.js 的 SSR 可以尽快向用户展示非交互式页面,从而提升感知加载性能。 + +<Image + alt="无流式传输的服务端渲染" + srcLight="/docs/light/server-rendering-without-streaming.png" + srcDark="/docs/dark/server-rendering-without-streaming.png" + width="1600" + height="748" +/> + +然而,由于服务端的所有数据获取完成后才能向用户展示页面,速度可能仍然较慢。 + +**流式传输**允许您将页面的 HTML 拆分为较小的块,并逐步从服务端发送这些块到客户端。 + +<Image + alt="带流式传输的服务端渲染工作原理" + srcLight="/docs/light/server-rendering-with-streaming.png" + srcDark="/docs/dark/server-rendering-with-streaming.png" + width="1600" + height="785" +/> + +这使得页面的部分内容可以更快显示,无需等待所有数据加载完成才渲染任何界面。 + +流式传输与 React 的组件模型配合良好,因为每个组件可以被视为一个块。优先级较高(如产品信息)或不依赖数据的组件(如布局)可以优先发送,React 可以更早开始注水。优先级较低(如评论、相关产品)的组件可以在数据获取后通过同一服务端请求发送。 + +<Image + alt="带流式传输的服务端渲染图表" + srcLight="/docs/light/server-rendering-with-streaming-chart.png" + srcDark="/docs/dark/server-rendering-with-streaming-chart.png" + width="1600" + height="730" +/> + +流式传输在防止长数据请求阻塞页面渲染时特别有益,因为它可以减少[首字节时间 (TTFB)](https://web.dev/ttfb/) 和[首次内容绘制 (FCP)](https://web.dev/first-contentful-paint/)。同时也有助于改善[可交互时间 (TTI)](https://developer.chrome.com/en/docs/lighthouse/performance/interactive/),尤其是在较慢的设备上。 + +### 示例 + +`<Suspense>` 的工作原理是包裹执行异步操作(如获取数据)的组件,在操作进行时显示备用界面(如骨架屏、旋转图标),操作完成后替换为您的组件。 + +```tsx filename="app/dashboard/page.tsx" switcher +import { Suspense } from 'react' +import { PostFeed, Weather } from './Components' + +export default function Posts() { + return ( + <section> + <Suspense fallback={<p>加载动态中...</p>}> + <PostFeed /> + </Suspense> + <Suspense fallback={<p>加载天气中...</p>}> + <Weather /> + </Suspense> + </section> + ) +} +``` + +```jsx filename="app/dashboard/page.js" switcher +import { Suspense } from 'react' +import { PostFeed, Weather } from './Components' + +export default function Posts() { + return ( + <section> + <Suspense fallback={<p>加载动态中...</p>}> + <PostFeed /> + </Suspense> + <Suspense fallback={<p>加载天气中...</p>}> + <Weather /> + </Suspense> + </section> + ) +} +``` + +使用 Suspense 可以获得以下优势: + +1. **流式服务端渲染** - 逐步从服务端向客户端渲染 HTML。 +2. **选择性注水** - React 根据用户交互优先处理需要先交互的组件。 + +更多 Suspense 示例和用例,请参阅 [React 文档](https://react.dev/reference/react/Suspense)。 + +### SEO + +- Next.js 会先等待 [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata) 内的数据获取完成,再向客户端流式传输界面。这确保流式响应的第一部分包含 `<head>` 标签。 +- 由于流式传输是服务端渲染的,不会影响 SEO。您可以使用 Google 的[移动设备友好测试](https://search.google.com/test/mobile-friendly)工具查看页面在 Google 网络爬虫中的呈现情况,并查看序列化的 HTML([来源](https://web.dev/rendering-on-the-web/#seo-considerations))。 + +### 状态码 + +流式传输时,会返回 `200` 状态码表示请求成功。 + +服务端仍可以在流式传输内容中向客户端传达错误或问题,例如使用 [`redirect`](/docs/app/api-reference/functions/redirect) 或 [`notFound`](/docs/app/api-reference/functions/not-found) 时。由于响应头已发送给客户端,响应的状态码无法更新。这不会影响 SEO。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/07-error-handling.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/07-error-handling.mdx new file mode 100644 index 00000000..747dda79 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/07-error-handling.mdx @@ -0,0 +1,228 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:07:09.024Z +title: 错误处理 +description: 通过自动将路由段及其嵌套子项包裹在 React 错误边界中,处理运行时错误。 +related: + links: + - app/api-reference/file-conventions/error +--- + +`error.js` 文件约定允许您在[嵌套路由](/docs/app/building-your-application/routing#nested-routes)中优雅地处理意外的运行时错误。 + +- 自动将路由段及其嵌套子项包裹在 [React 错误边界](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)中 +- 利用文件系统层次结构创建针对特定路由段的错误界面,实现精细控制 +- 将错误隔离到受影响的路由段,同时保持应用程序其余部分正常运行 +- 添加功能以尝试从错误中恢复,无需完全重新加载页面 + +通过在路由段内添加 `error.js` 文件并导出 React 组件来创建错误界面: + +<Image + alt="error.js 特殊文件" + srcLight="/docs/light/error-special-file.png" + srcDark="/docs/dark/error-special-file.png" + width="1600" + height="606" +/> + +```tsx filename="app/dashboard/error.tsx" switcher +'use client' // 错误组件必须是客户端组件 + +import { useEffect } from 'react' + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string } + reset: () => void +}) { + useEffect(() => { + // 将错误记录到错误报告服务 + console.error(error) + }, [error]) + + return ( + <div> + <h2>出错了!</h2> + <button + onClick={ + // 尝试通过重新渲染路由段来恢复 + () => reset() + } + > + 重试 + </button> + </div> + ) +} +``` + +```jsx filename="app/dashboard/error.js" switcher +'use client' // 错误组件必须是客户端组件 + +import { useEffect } from 'react' + +export default function Error({ error, reset }) { + useEffect(() => { + // 将错误记录到错误报告服务 + console.error(error) + }, [error]) + + return ( + <div> + <h2>出错了!</h2> + <button + onClick={ + // 尝试通过重新渲染路由段来恢复 + () => reset() + } + > + 重试 + </button> + </div> + ) +} +``` + +### `error.js` 工作原理 + +<Image + alt="error.js 工作原理" + srcLight="/docs/light/error-overview.png" + srcDark="/docs/dark/error-overview.png" + width="1600" + height="903" +/> + +- `error.js` 会自动创建一个 [React 错误边界](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary),**包裹**嵌套子路由段或 `page.js` 组件 +- 从 `error.js` 文件导出的 React 组件将作为**备用**组件使用 +- 如果在错误边界内抛出错误,错误会被**捕获**,并**渲染**备用组件 +- 当备用错误组件激活时,错误边界**上方**的布局会**保持**其状态并**维持**交互性,错误组件可以显示恢复功能 + +### 从错误中恢复 + +有时错误可能是暂时的。这种情况下,简单地重试可能就能解决问题。 + +错误组件可以使用 `reset()` 函数提示用户尝试从错误中恢复。执行该函数时,会尝试重新渲染错误边界的内容。如果成功,备用错误组件将被重新渲染的结果替换。 + +```tsx filename="app/dashboard/error.tsx" switcher +'use client' + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string } + reset: () => void +}) { + return ( + <div> + <h2>出错了!</h2> + <button onClick={() => reset()}>重试</button> + </div> + ) +} +``` + +```jsx filename="app/dashboard/error.js" switcher +'use client' + +export default function Error({ error, reset }) { + return ( + <div> + <h2>出错了!</h2> + <button onClick={() => reset()}>重试</button> + </div> + ) +} +``` + +### 嵌套路由 + +通过[特殊文件](/docs/app/building-your-application/routing#file-conventions)创建的 React 组件会在[特定的嵌套层次结构](/docs/app/building-your-application/routing#component-hierarchy)中渲染。 + +例如,包含 `layout.js` 和 `error.js` 文件的两个嵌套路由段会按照以下*简化*的组件层次结构渲染: + +<Image + alt="嵌套错误组件层次结构" + srcLight="/docs/light/nested-error-component-hierarchy.png" + srcDark="/docs/dark/nested-error-component-hierarchy.png" + width="1600" + height="687" +/> + +嵌套组件层次结构会影响嵌套路由中 `error.js` 文件的行为: + +- 错误会冒泡到最近的父级错误边界。这意味着 `error.js` 文件会处理其所有嵌套子路由段的错误。通过在路由的不同层级放置 `error.js` 文件,可以实现更精细或更粗略的错误界面。 +- `error.js` 边界**不会**处理**同一**路由段中 `layout.js` 组件抛出的错误,因为错误边界嵌套在该布局组件**内部**。 + +### 处理布局中的错误 + +`error.js` 边界**不会**捕获**同一**路由段中 `layout.js` 或 `template.js` 组件抛出的错误。这种[有意的层次结构](#nested-routes)确保当错误发生时,兄弟路由间共享的重要 UI(如导航)保持可见和可用。 + +要处理特定布局或模板中的错误,请在布局的父级路由段放置 `error.js` 文件。 + +要处理根布局或模板中的错误,请使用 `error.js` 的变体 `global-error.js`。 + +### 处理根布局中的错误 + +根目录的 `app/error.js` 边界**不会**捕获根 `app/layout.js` 或 `app/template.js` 组件抛出的错误。 + +要专门处理这些根组件中的错误,请在根 `app` 目录使用 `error.js` 的变体 `app/global-error.js`。 + +与根 `error.js` 不同,`global-error.js` 错误边界会包裹**整个**应用程序,其备用组件激活时会替换根布局。因此需要注意,`global-error.js` **必须**定义自己的 `<html>` 和 `<body>` 标签。 + +`global-error.js` 是最不精细的错误界面,可视为整个应用程序的"全局捕获"错误处理。由于根组件通常较少动态变化,它很少被触发,大多数错误会被其他 `error.js` 边界捕获。 + +即使定义了 `global-error.js`,仍然建议定义根 `error.js`,其备用组件将在根布局**内部**渲染,包含全局共享的 UI 和品牌标识。 + +```tsx filename="app/global-error.tsx" switcher +'use client' + +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string } + reset: () => void +}) { + return ( + <html> + <body> + <h2>出错了!</h2> + <button onClick={() => reset()}>重试</button> + </body> + </html> + ) +} +``` + +```jsx filename="app/global-error.js" switcher +'use client' + +export default function GlobalError({ error, reset }) { + return ( + <html> + <body> + <h2>出错了!</h2> + <button onClick={() => reset()}>重试</button> + </body> + </html> + ) +} +``` + +### 处理服务端错误 + +如果在服务端组件中抛出错误,Next.js 会将 `Error` 对象(在生产环境中会去除敏感错误信息)作为 `error` 属性传递给最近的 `error.js` 文件。 + +#### 保护敏感错误信息 + +在生产环境中,传递给客户端的 `Error` 对象仅包含通用的 `message` 和 `digest` 属性。 + +这是一种安全预防措施,避免将错误中可能包含的敏感细节泄露给客户端。 + +`message` 属性包含关于错误的通用信息,`digest` 属性包含自动生成的错误哈希值,可用于匹配服务端日志中的对应错误。 + +在开发环境中,传递给客户端的 `Error` 对象会被序列化,并包含原始错误的 `message` 以便于调试。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/08-parallel-routes.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/08-parallel-routes.mdx new file mode 100644 index 00000000..bf6dabf3 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/08-parallel-routes.mdx @@ -0,0 +1,328 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:07:06.900Z +title: 并行路由 (Parallel Routes) +description: 在同一视图中同时或条件性地渲染多个可独立导航的页面。适用于高度动态的应用程序模式。 +--- + +并行路由 (Parallel Routing) 允许您在同一布局中同时或条件性地渲染多个页面。对于应用中高度动态的部分(如社交网站的控制面板和信息流),可以使用并行路由来实现复杂的路由模式。 + +例如,您可以同时渲染团队页面和分析页面。 + +<Image + alt="并行路由示意图" + srcLight="/docs/light/parallel-routes.png" + srcDark="/docs/dark/parallel-routes.png" + width="1600" + height="952" +/> + +并行路由允许您为每个独立加载的路由定义独立的错误和加载状态。 + +<Image + alt="并行路由支持自定义错误和加载状态" + srcLight="/docs/light/parallel-routes-cinematic-universe.png" + srcDark="/docs/dark/parallel-routes-cinematic-universe.png" + width="1600" + height="1218" +/> + +并行路由还允许您基于特定条件(如认证状态)条件性地渲染插槽。这使得同一 URL 下可以呈现完全分离的代码。 + +<Image + alt="条件路由示意图" + srcLight="/docs/light/conditional-routes-ui.png" + srcDark="/docs/dark/conditional-routes-ui.png" + width="1600" + height="898" +/> + +## 约定 + +并行路由通过命名的**插槽 (slots)** 创建。插槽使用 `@folder` 约定定义,并作为 props 传递给同层级的布局组件。 + +> 插槽 _不是_ 路由段 (route segments),也 _不会影响 URL 结构_。文件路径 `/@team/members` 可通过 `/members` 访问。 + +例如,以下文件结构定义了两个显式插槽:`@analytics` 和 `@team`。 + +<Image + alt="并行路由文件系统结构" + srcLight="/docs/light/parallel-routes-file-system.png" + srcDark="/docs/dark/parallel-routes-file-system.png" + width="1600" + height="687" +/> + +上述文件夹结构意味着 `app/layout.js` 中的组件现在接受 `@analytics` 和 `@team` 插槽 props,可以与 `children` prop 并行渲染: + +```tsx filename="app/layout.tsx" switcher +export default function Layout(props: { + children: React.ReactNode + analytics: React.ReactNode + team: React.ReactNode +}) { + return ( + <> + {props.children} + {props.team} + {props.analytics} + </> + ) +} +``` + +```jsx filename="app/layout.js" switcher +export default function Layout(props) { + return ( + <> + {props.children} + {props.team} + {props.analytics} + </> + ) +} +``` + +> **须知**:`children` prop 是一个隐式插槽,不需要映射到文件夹。这意味着 `app/page.js` 等同于 `app/@children/page.js`。 + +## 未匹配路由 + +默认情况下,插槽内渲染的内容会匹配当前 URL。 + +当插槽未匹配时,Next.js 渲染的内容会根据路由技术和文件夹结构有所不同。 + +### `default.js` + +您可以定义 `default.js` 文件,当 Next.js 无法根据当前 URL 恢复插槽的活动状态时,将其作为备用渲染。 + +考虑以下文件夹结构。`@team` 插槽有 `settings` 目录,但 `@analytics` 没有。 + +<Image + alt="并行路由未匹配路由" + srcLight="/docs/light/parallel-routes-unmatched-routes.png" + srcDark="/docs/dark/parallel-routes-unmatched-routes.png" + width="1600" + height="930" +/> + +#### 导航 + +在导航时,Next.js 会渲染插槽之前的活动状态,即使它与当前 URL 不匹配。 + +#### 刷新 + +刷新时,Next.js 会首先尝试渲染未匹配插槽的 `default.js` 文件。如果该文件不存在,则会渲染 404 页面。 + +> 未匹配路由的 404 页面有助于确保您不会意外渲染不应并行渲染的路由。 + +## `useSelectedLayoutSegment(s)` + +[`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment) 和 [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments) 都接受 `parallelRoutesKey` 参数,允许您读取该插槽内的活动路由段。 + +```tsx filename="app/layout.tsx" switcher +'use client' + +import { useSelectedLayoutSegment } from 'next/navigation' + +export default async function Layout(props: { + //... + auth: React.ReactNode +}) { + const loginSegments = useSelectedLayoutSegment('auth') + // ... +} +``` + +```jsx filename="app/layout.js" switcher +'use client' + +import { useSelectedLayoutSegment } from 'next/navigation' + +export default async function Layout(props) { + const loginSegments = useSelectedLayoutSegment('auth') + // ... +} +``` + +当用户导航到 `@auth/login` 或 URL 栏中的 `/login` 时,`loginSegments` 将等于字符串 `"login"`。 + +## 示例 + +### 模态框 (Modals) + +并行路由可用于渲染模态框。 + +<Image + alt="并行路由示意图" + srcLight="/docs/light/parallel-routes-auth-modal.png" + srcDark="/docs/dark/parallel-routes-auth-modal.png" + width="1600" + height="687" +/> + +`@auth` 插槽渲染一个 `<Modal>` 组件,可以通过导航到匹配的路由(如 `/login`)来显示。 + +```tsx filename="app/layout.tsx" switcher +export default async function Layout(props: { + // ... + auth: React.ReactNode +}) { + return ( + <> + {/* ... */} + {props.auth} + </> + ) +} +``` + +```jsx filename="app/layout.js" switcher +export default async function Layout(props) { + return ( + <> + {/* ... */} + {props.auth} + </> + ) +} +``` + +```tsx filename="app/@auth/login/page.tsx" switcher +import { Modal } from 'components/modal' + +export default function Login() { + return ( + <Modal> + <h1>登录</h1> + {/* ... */} + </Modal> + ) +} +``` + +```jsx filename="app/@auth/login/page.js" switcher +import { Modal } from 'components/modal' + +export default function Login() { + return ( + <Modal> + <h1>登录</h1> + {/* ... */} + </Modal> + ) +} +``` + +为确保模态框内容在不活动时不被渲染,您可以创建一个返回 `null` 的 `default.js` 文件。 + +```tsx filename="app/@auth/default.tsx" switcher +export default function Default() { + return null +} +``` + +```jsx filename="app/@auth/default.js" switcher +export default function Default() { + return null +} +``` + +#### 关闭模态框 + +如果模态框是通过客户端导航(例如使用 `<Link href="/login">`)启动的,您可以通过调用 `router.back()` 或使用 `Link` 组件来关闭模态框。 + +```tsx filename="app/@auth/login/page.tsx" highlight="5" switcher +'use client' +import { useRouter } from 'next/navigation' +import { Modal } from 'components/modal' + +export default async function Login() { + const router = useRouter() + return ( + <Modal> + <span onClick={() => router.back()}>关闭模态框</span> + <h1>登录</h1> + ... + </Modal> + ) +} +``` + +```jsx filename="app/@auth/login/page.js" highlight="5" switcher +'use client' +import { useRouter } from 'next/navigation' +import { Modal } from 'components/modal' + +export default async function Login() { + const router = useRouter() + return ( + <Modal> + <span onClick={() => router.back()}>关闭模态框</span> + <h1>登录</h1> + ... + </Modal> + ) +} +``` + +> 更多关于模态框的信息,请参阅 [拦截路由 (Intercepting Routes)](/docs/app/building-your-application/routing/intercepting-routes) 部分。 + +如果您想导航到其他地方并关闭模态框,也可以使用全捕获 (catch-all) 路由。 + +<Image + alt="并行路由示意图" + srcLight="/docs/light/parallel-routes-catchall.png" + srcDark="/docs/dark/parallel-routes-catchall.png" + width="1600" + height="768" +/> + +```tsx filename="app/@auth/[...catchAll]/page.tsx" switcher +export default function CatchAll() { + return null +} +``` + +```jsx filename="app/@auth/[...catchAll]/page.js" switcher +export default function CatchAll() { + return null +} +``` + +> 全捕获路由的优先级高于 `default.js`。 + +### 条件路由 + +并行路由可用于实现条件路由。例如,您可以根据认证状态渲染 `@dashboard` 或 `@login` 路由。 + +```tsx filename="app/layout.tsx" switcher +import { getUser } from '@/lib/auth' + +export default function Layout({ + dashboard, + login, +}: { + dashboard: React.ReactNode + login: React.ReactNode +}) { + const isLoggedIn = getUser() + return isLoggedIn ? dashboard : login +} +``` + +```jsx filename="app/layout.js" switcher +import { getUser } from '@/lib/auth' + +export default function Layout({ dashboard, login }) { + const isLoggedIn = getUser() + return isLoggedIn ? dashboard : login +} +``` + +<Image + alt="并行路由认证示例" + srcLight="/docs/light/conditional-routes-ui.png" + srcDark="/docs/dark/conditional-routes-ui.png" + width="1600" + height="898" +/> \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/09-intercepting-routes.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/09-intercepting-routes.mdx new file mode 100644 index 00000000..a004770b --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/09-intercepting-routes.mdx @@ -0,0 +1,81 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:06:09.078Z +title: 拦截路由 +description: 使用拦截路由可以在当前布局内加载新路由,同时隐藏浏览器 URL,适用于模态框等高级路由模式。 +related: + title: 下一步 + description: 学习如何结合拦截路由与并行路由使用模态框。 + links: + - app/building-your-application/routing/parallel-routes +--- + +拦截路由允许你在当前布局内加载来自应用其他部分的路由。当需要在不切换用户上下文的情况下展示路由内容时,这种路由范式非常有用。 + +例如,当点击信息流中的照片时,可以在模态框中展示该照片并覆盖在信息流上方。此时 Next.js 会拦截 `/photo/123` 路由,隐藏 URL 并将其覆盖在 `/feed` 之上。 + +<Image + alt="拦截路由的软导航" + srcLight="/docs/light/intercepting-routes-soft-navigate.png" + srcDark="/docs/dark/intercepting-routes-soft-navigate.png" + width="1600" + height="617" +/> + +但当通过可分享链接访问照片或刷新页面时,应渲染完整的照片页面而非模态框。此时不应触发路由拦截。 + +<Image + alt="拦截路由的硬导航" + srcLight="/docs/light/intercepting-routes-hard-navigate.png" + srcDark="/docs/dark/intercepting-routes-hard-navigate.png" + width="1600" + height="604" +/> + +## 约定 + +拦截路由可通过 `(..)` 约定来定义,这与相对路径约定 `../` 类似,但用于路由段。 + +你可以使用: +- `(.)` 匹配**同级**路由段 +- `(..)` 匹配**上一级**路由段 +- `(..)(..)` 匹配**上两级**路由段 +- `(...)` 匹配从根 `app` 目录开始的路由段 + +例如,通过在 `feed` 目录下创建 `(..)photo` 目录,可以拦截来自 `feed` 段的 `photo` 段。 + +<Image + alt="拦截路由的文件夹结构" + srcLight="/docs/light/intercepted-routes-files.png" + srcDark="/docs/dark/intercepted-routes-files.png" + width="1600" + height="604" +/> + +> 注意 `(..)` 约定基于_路由段_而非文件系统。 + +## 示例 + +### 模态框 + +拦截路由可与[并行路由](/docs/app/building-your-application/routing/parallel-routes)结合创建模态框。 + +使用此模式创建模态框能解决一些常见问题: +- 通过 URL 实现模态内容**可分享** +- 页面刷新时**保留上下文**而非关闭模态框 +- 后退导航时**关闭模态框**而非返回上一路由 +- 前进导航时**重新打开模态框** + +<Image + alt="拦截路由模态框示例" + srcLight="/docs/light/intercepted-routes-modal-example.png" + srcDark="/docs/dark/intercepted-routes-modal-example.png" + width="1600" + height="976" +/> + +> 上例中,由于 `@modal` 是插槽而非路由段,因此 `photo` 段路径可以使用 `(..)` 匹配器。这意味着尽管在文件系统中高两级,但 `photo` 路由仅高一个路由段层级。 + +其他示例包括:在顶部导航栏打开登录模态框的同时保留独立 `/login` 页面,或在侧边栏模态框中打开购物车。 + +查看结合拦截路由与并行路由的[模态框示例](https://github.com/vercel-labs/nextgram)。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/10-route-handlers.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/10-route-handlers.mdx new file mode 100644 index 00000000..91a61235 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/10-route-handlers.mdx @@ -0,0 +1,692 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:08:48.449Z +title: 路由处理器 (Route Handlers) +description: 使用 Web 的 Request 和 Response API 为指定路由创建自定义请求处理器。 +related: + title: API 参考 + description: 了解更多关于 route.js 文件的信息。 + links: + - app/api-reference/file-conventions/route +--- + +路由处理器 (Route Handlers) 允许您使用 Web [Request](https://developer.mozilla.org/docs/Web/API/Request) 和 [Response](https://developer.mozilla.org/docs/Web/API/Response) API 为指定路由创建自定义请求处理器。 + +<Image + alt="Route.js 特殊文件" + srcLight="/docs/light/route-special-file.png" + srcDark="/docs/dark/route-special-file.png" + width="1600" + height="444" +/> + +> **须知**:路由处理器 (Route Handlers) 仅在 `app` 目录内可用。它们等同于 `pages` 目录中的 [API 路由 (API Routes)](/docs/pages/building-your-application/routing/api-routes),这意味着您**无需**同时使用 API 路由和路由处理器。 + +## 约定 + +路由处理器 (Route Handlers) 在 `app` 目录下的 [`route.js|ts` 文件](/docs/app/api-reference/file-conventions/route)中定义: + +```ts filename="app/api/route.ts" switcher +export async function GET(request: Request) {} +``` + +```js filename="app/api/route.js" switcher +export async function GET(request) {} +``` + +路由处理器可以像 `page.js` 和 `layout.js` 一样嵌套在 `app` 目录中。但在与 `page.js` 相同的路由段层级**不能**存在 `route.js` 文件。 + +### 支持的 HTTP 方法 + +支持以下 [HTTP 方法](https://developer.mozilla.org/docs/Web/HTTP/Methods):`GET`、`POST`、`PUT`、`PATCH`、`DELETE`、`HEAD` 和 `OPTIONS`。如果调用了不支持的方法,Next.js 将返回 `405 Method Not Allowed` 响应。 + +### 扩展的 `NextRequest` 和 `NextResponse` API + +除了支持原生 [Request](https://developer.mozilla.org/docs/Web/API/Request) 和 [Response](https://developer.mozilla.org/docs/Web/API/Response),Next.js 还通过 [`NextRequest`](/docs/app/api-reference/functions/next-request) 和 [`NextResponse`](/docs/app/api-reference/functions/next-response) 对其进行了扩展,为高级用例提供了便捷的辅助方法。 + +## 行为 + +### 缓存 + +当使用 `GET` 方法和 `Response` 对象时,路由处理器默认会被缓存。 + +```ts filename="app/items/route.ts" switcher +export async function GET() { + const res = await fetch('https://data.mongodb-api.com/...', { + headers: { + 'Content-Type': 'application/json', + 'API-Key': process.env.DATA_API_KEY, + }, + }) + const data = await res.json() + + return Response.json({ data }) +} +``` + +```js filename="app/items/route.js" switcher +export async function GET() { + const res = await fetch('https://data.mongodb-api.com/...', { + headers: { + 'Content-Type': 'application/json', + 'API-Key': process.env.DATA_API_KEY, + }, + }) + const data = await res.json() + + return Response.json({ data }) +} +``` + +> **TypeScript 警告**:`Response.json()` 仅在 TypeScript 5.2 及以上版本中有效。如果使用较低版本的 TypeScript,可以使用 [`NextResponse.json()`](/docs/app/api-reference/functions/next-response#json) 来替代以获得类型化响应。 + +### 退出缓存 + +您可以通过以下方式退出缓存: + +- 在 `GET` 方法中使用 `Request` 对象 +- 使用其他 HTTP 方法 +- 使用 [动态函数 (Dynamic Functions)](#dynamic-functions) 如 `cookies` 和 `headers` +- [路由段配置选项 (Segment Config Options)](#segment-config-options) 手动指定动态模式 + +例如: + +```ts filename="app/products/api/route.ts" switcher +export async function GET(request: Request) { + const { searchParams } = new URL(request.url) + const id = searchParams.get('id') + const res = await fetch(`https://data.mongodb-api.com/product/${id}`, { + headers: { + 'Content-Type': 'application/json', + 'API-Key': process.env.DATA_API_KEY, + }, + }) + const product = await res.json() + + return Response.json({ product }) +} +``` + +```js filename="app/products/api/route.js" switcher +export async function GET(request) { + const { searchParams } = new URL(request.url) + const id = searchParams.get('id') + const res = await fetch(`https://data.mongodb-api.com/product/${id}`, { + headers: { + 'Content-Type': 'application/json', + 'API-Key': process.env.DATA_API_KEY, + }, + }) + const product = await res.json() + + return Response.json({ product }) +} +``` + +类似地,`POST` 方法会导致路由处理器被动态执行。 + +```ts filename="app/items/route.ts" switcher +export async function POST() { + const res = await fetch('https://data.mongodb-api.com/...', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'API-Key': process.env.DATA_API_KEY, + }, + body: JSON.stringify({ time: new Date().toISOString() }), + }) + + const data = await res.json() + + return Response.json(data) +} +``` + +```js filename="app/items/route.js" switcher +export async function POST() { + const res = await fetch('https://data.mongodb-api.com/...', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'API-Key': process.env.DATA_API_KEY, + }, + body: JSON.stringify({ time: new Date().toISOString() }), + }) + + const data = await res.json() + + return Response.json(data) +} +``` + +> **须知**:与 API 路由类似,路由处理器可用于处理表单提交等场景。目前正在开发一个与 React 深度集成的[处理表单和变更 (handling forms and mutations)](/docs/app/building-your-application/data-fetching/forms-and-mutations) 的新抽象层。 + +### 路由解析 + +您可以将 `route` 视为最低级别的路由原语。 + +- 它们**不参与**布局或像 `page` 那样的客户端导航 +- 在与 `page.js` 相同的路由中**不能**存在 `route.js` 文件 + +| 页面 | 路由 | 结果 | +| -------------------- | ------------------ | ---------------------------- | +| `app/page.js` | `app/route.js` | <Cross size={18} /> 冲突 | +| `app/page.js` | `app/api/route.js` | <Check size={18} /> 有效 | +| `app/[user]/page.js` | `app/api/route.js` | <Check size={18} /> 有效 | + +每个 `route.js` 或 `page.js` 文件会接管该路由的所有 HTTP 方法。 + +```jsx filename="app/page.js" +export default function Page() { + return <h1>Hello, Next.js!</h1> +} + +// ❌ 冲突 +// `app/route.js` +export async function POST(request) {} +``` + +## 示例 + +以下示例展示了如何将路由处理器与其他 Next.js API 和功能结合使用。 + +### 重新验证缓存数据 + +您可以使用 [`next.revalidate`](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data) 选项[重新验证缓存数据 (revalidate cached data)](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data): + +```ts filename="app/items/route.ts" switcher +export async function GET() { + const res = await fetch('https://data.mongodb-api.com/...', { + next: { revalidate: 60 }, // 每 60 秒重新验证 + }) + const data = await res.json() + + return Response.json(data) +} +``` + +```js filename="app/items/route.js" switcher +export async function GET() { + const res = await fetch('https://data.mongodb-api.com/...', { + next: { revalidate: 60 }, // 每 60 秒重新验证 + }) + const data = await res.json() + + return Response.json(data) +} +``` + +或者,您可以使用 [`revalidate` 路由段配置选项](/docs/app/api-reference/file-conventions/route-segment-config#revalidate): + +```ts +export const revalidate = 60 +``` + +### 动态函数 + +路由处理器可以与 Next.js 的动态函数一起使用,例如 [`cookies`](/docs/app/api-reference/functions/cookies) 和 [`headers`](/docs/app/api-reference/functions/headers)。 + +#### Cookies + +您可以使用 `next/headers` 中的 [`cookies`](/docs/app/api-reference/functions/cookies) 读取 cookie。这个服务器函数可以直接在路由处理器中调用,或嵌套在其他函数中。 + +这个 `cookies` 实例是只读的。要设置 cookie,您需要使用 [`Set-Cookie`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie) 头返回一个新的 `Response`。 + +```ts filename="app/api/route.ts" switcher +import { cookies } from 'next/headers' + +export async function GET(request: Request) { + const cookieStore = cookies() + const token = cookieStore.get('token') + + return new Response('Hello, Next.js!', { + status: 200, + headers: { 'Set-Cookie': `token=${token.value}` }, + }) +} +``` + +```js filename="app/api/route.js" switcher +import { cookies } from 'next/headers' + +export async function GET(request) { + const cookieStore = cookies() + const token = cookieStore.get('token') + + return new Response('Hello, Next.js!', { + status: 200, + headers: { 'Set-Cookie': `token=${token}` }, + }) +} +``` + +或者,您可以使用基于底层 Web API 的抽象来读取 cookie ([`NextRequest`](/docs/app/api-reference/functions/next-request)): + +```ts filename="app/api/route.ts" switcher +import { type NextRequest } from 'next/server' + +export async function GET(request: NextRequest) { + const token = request.cookies.get('token') +} +``` + +```js filename="app/api/route.js" switcher +export async function GET(request) { + const token = request.cookies.get('token') +} +``` + +#### Headers + +您可以使用 `next/headers` 中的 [`headers`](/docs/app/api-reference/functions/headers) 读取请求头。这个服务器函数可以直接在路由处理器中调用,或嵌套在其他函数中。 + +这个 `headers` 实例是只读的。要设置请求头,您需要返回一个带有新 `headers` 的新 `Response`。 + +```ts filename="app/api/route.ts" switcher +import { headers } from 'next/headers' + +export async function GET(request: Request) { + const headersList = headers() + const referer = headersList.get('referer') + + return new Response('Hello, Next.js!', { + status: 200, + headers: { referer: referer }, + }) +} +``` + +```js filename="app/api/route.js" switcher +import { headers } from 'next/headers' + +export async function GET(request) { + const headersList = headers() + const referer = headersList.get('referer') + + return new Response('Hello, Next.js!', { + status: 200, + headers: { referer: referer }, + }) +} +``` + +或者,您可以使用基于底层 Web API 的抽象来读取请求头 ([`NextRequest`](/docs/app/api-reference/functions/next-request)): + +```ts filename="app/api/route.ts" switcher +import { type NextRequest } from 'next/server' + +export async function GET(request: NextRequest) { + const requestHeaders = new Headers(request.headers) +} +``` + +```js filename="app/api/route.js" switcher +export async function GET(request) { + const requestHeaders = new Headers(request.headers) +} +``` + +### 重定向 + +```ts filename="app/api/route.ts" switcher +import { redirect } from 'next/navigation' + +export async function GET(request: Request) { + redirect('https://nextjs.org/') +} +``` + +```js filename="app/api/route.js" switcher +import { redirect } from 'next/navigation' + +export async function GET(request) { + redirect('https://nextjs.org/') +} +``` + +### 动态路由段 + +> 我们建议在继续之前阅读[定义路由 (Defining Routes)](/docs/app/building-your-application/routing/defining-routes) 页面。 + +路由处理器可以使用[动态段 (Dynamic Segments)](/docs/app/building-your-application/routing/dynamic-routes) 从动态数据创建请求处理器。 + +```ts filename="app/items/[slug]/route.ts" switcher +export async function GET( + request: Request, + { params }: { params: { slug: string } } +) { + const slug = params.slug // 'a'、'b' 或 'c' +} +``` + +```js filename="app/items/[slug]/route.js" switcher +export async function GET(request, { params }) { + const slug = params.slug // 'a'、'b' 或 'c' +} +``` + +| 路由 | 示例 URL | `params` | +| --------------------------- | ----------- | --------------- | +| `app/items/[slug]/route.js` | `/items/a` | `{ slug: 'a' }` | +| `app/items/[slug]/route.js` | `/items/b` | `{ slug: 'b' }` | +| `app/items/[slug]/route.js` | `/items/c` | `{ slug: 'c' }` | + +### URL 查询参数 + +传递给路由处理器的请求对象是一个 `NextRequest` 实例,它具有[一些额外的便捷方法](/docs/app/api-reference/functions/next-request#nexturl),可以更轻松地处理查询参数。 + +```ts filename="app/api/search/route.ts" switcher +import { type NextRequest } from 'next/server' + +export function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams + const query = searchParams.get('query') + // 对于 /api/search?query=hello,query 是 "hello" +} +``` + +```js filename="app/api/search/route.js" switcher +export function GET(request) { + const searchParams = request.nextUrl.searchParams + const query = searchParams.get('query') + // 对于 /api/search?query=hello,query 是 "hello" +} +``` + +### 流式传输 + +流式传输通常与大型语言模型 (LLM) 如 OpenAI 结合使用,用于 AI 生成内容。了解更多关于 [AI SDK](https://sdk.vercel.ai/docs) 的信息。 + +```ts filename="app/api/chat/route.ts" switcher +import { Configuration, OpenAIApi } from 'openai-edge' +import { OpenAIStream, StreamingTextResponse } from 'ai' + +export const runtime = 'edge' + +const apiConfig = new Configuration({ + apiKey: process.env.OPENAI_API_KEY!, +}) + +const openai = new OpenAIApi(apiConfig) + +export async function POST(req: Request) { + // 从请求体中提取 `messages` + const { messages } = await req.json() + + // 根据提示向 OpenAI API 请求响应 + const response = await openai.createChatCompletion({ + model: 'gpt-3.5-turbo', + stream: true, + messages: messages, + max_tokens: 500, + temperature: 0.7, + top_p: 1, + frequency_penalty: 1, + presence_penalty: 1, + }) + + // 将响应转换为友好的文本流 + const stream = OpenAIStream(response) + + // 返回流式响应 + return new StreamingTextResponse(stream) +} +``` + +```js filename="app/api/chat/route.js" switcher +import { Configuration, OpenAIApi } from 'openai-edge' +import { OpenAIStream, StreamingTextResponse } from 'ai' + +export const runtime = 'edge' + +const apiConfig = new Configuration({ + apiKey: process.env.OPENAI_API_KEY, +}) + +const openai = new OpenAIApi(apiConfig) + +export async function POST(req) { + // 从请求体中提取 `messages` + const { messages } = await req.json() + + // 根据提示向 OpenAI API 请求响应 + const response = await openai.createChatCompletion({ + model: 'gpt-3.5-turbo', + stream: true, + messages: messages, + max_tokens: 500, + temperature: 0.7, + top_p: 1, + frequency_penalty: 1, + presence_penalty: 1, + }) + + // 将响应转换为友好的文本流 + const stream = OpenAIStream(response) + + // 返回流式响应 + return new StreamingTextResponse(stream) +} +``` + +这些抽象使用了 Web API 来创建流。你也可以直接使用底层的 Web API。 + +```ts filename="app/api/route.ts" switcher +// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream +function iteratorToStream(iterator: any) { + return new ReadableStream({ + async pull(controller) { + const { value, done } = await iterator.next() + + if (done) { + controller.close() + } else { + controller.enqueue(value) + } + }, + }) +} + +function sleep(time: number) { + return new Promise((resolve) => { + setTimeout(resolve, time) + }) +} + +const encoder = new TextEncoder() + +async function* makeIterator() { + yield encoder.encode('<p>One</p>') + await sleep(200) + yield encoder.encode('<p>Two</p>') + await sleep(200) + yield encoder.encode('<p>Three</p>') +} + +export async function GET() { + const iterator = makeIterator() + const stream = iteratorToStream(iterator) + + return new Response(stream) +} +``` + +```js filename="app/api/route.js" switcher +// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream +function iteratorToStream(iterator) { + return new ReadableStream({ + async pull(controller) { + const { value, done } = await iterator.next() + + if (done) { + controller.close() + } else { + controller.enqueue(value) + } + }, + }) +} + +function sleep(time) { + return new Promise((resolve) => { + setTimeout(resolve, time) + }) +} + +const encoder = new TextEncoder() + +async function* makeIterator() { + yield encoder.encode('<p>One</p>') + await sleep(200) + yield encoder.encode('<p>Two</p>') + await sleep(200) + yield encoder.encode('<p>Three</p>') +} + +export async function GET() { + const iterator = makeIterator() + const stream = iteratorToStream(iterator) + + return new Response(stream) +} +``` + +### 请求体 + +你可以使用标准的 Web API 方法读取 `Request` 的请求体: + +```ts filename="app/items/route.ts" switcher +export async function POST(request: Request) { + const res = await request.json() + return Response.json({ res }) +} +``` + +```js filename="app/items/route.js" switcher +export async function POST(request) { + const res = await request.json() + return Response.json({ res }) +} +``` + +### 请求体 FormData + +你可以使用 `request.formData()` 函数读取 `FormData`: + +```ts filename="app/items/route.ts" switcher +export async function POST(request: Request) { + const formData = await request.formData() + const name = formData.get('name') + const email = formData.get('email') + return Response.json({ name, email }) +} +``` + +```js filename="app/items/route.js" switcher +export async function POST(request) { + const formData = await request.formData() + const name = formData.get('name') + const email = formData.get('email') + return Response.json({ name, email }) +} +``` + +由于 `formData` 的数据都是字符串,你可能需要使用 [`zod-form-data`](https://www.npmjs.com/zod-form-data) 来验证请求并以你喜欢的格式(如 `number`)获取数据。 + +### CORS + +你可以使用标准的 Web API 方法在 `Response` 上设置 CORS 头: + +```ts filename="app/api/route.ts" switcher +export async function GET(request: Request) { + return new Response('Hello, Next.js!', { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + }) +} +``` + +```js filename="app/api/route.js" switcher +export async function GET(request) { + return new Response('Hello, Next.js!', { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + }) +} +``` + +### Edge 和 Node.js 运行时 + +路由处理器具有同构的 Web API,可以无缝支持 Edge 和 Node.js 运行时,包括流式传输的支持。由于路由处理器使用与页面和布局相同的 [路由段配置](/docs/app/api-reference/file-conventions/route-segment-config),因此它们支持期待已久的功能,如通用的 [静态重新生成](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data) 路由处理器。 + +你可以使用 `runtime` 段配置选项来指定运行时: + +```ts +export const runtime = 'edge' // 默认为 'nodejs' +``` + +### 非 UI 响应 + +你可以使用路由处理器返回非 UI 内容。注意 [`sitemap.xml`](/docs/app/api-reference/file-conventions/metadata/sitemap#generate-a-sitemap)、[`robots.txt`](/docs/app/api-reference/file-conventions/metadata/robots#generate-a-robots-file)、[`应用图标`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) 和 [开放图谱图片](/docs/app/api-reference/file-conventions/metadata/opengraph-image) 都有内置支持。 + +```ts filename="app/rss.xml/route.ts" switcher +export async function GET() { + return new Response(`<?xml version="1.0" encoding="UTF-8" ?> +<rss version="2.0"> + +<channel> + <title>Next.js Documentation + https://nextjs.org/docs + The React Framework for the Web + + +`) +} +``` + +```js filename="app/rss.xml/route.js" switcher +export async function GET() { + return new Response(` + + + + Next.js Documentation + https://nextjs.org/docs + The React Framework for the Web + + +`) +} +``` + +### 段配置选项 + +路由处理器使用与页面和布局相同的 [路由段配置](/docs/app/api-reference/file-conventions/route-segment-config)。 + +```ts filename="app/items/route.ts" switcher +export const dynamic = 'auto' +export const dynamicParams = true +export const revalidate = false +export const fetchCache = 'auto' +export const runtime = 'nodejs' +export const preferredRegion = 'auto' +``` + +```js filename="app/items/route.js" switcher +export const dynamic = 'auto' +export const dynamicParams = true +export const revalidate = false +export const fetchCache = 'auto' +export const runtime = 'nodejs' +export const preferredRegion = 'auto' +``` + +更多详情请参阅 [API 参考](/docs/app/api-reference/file-conventions/route-segment-config)。 diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/11-middleware.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/11-middleware.mdx new file mode 100644 index 00000000..4c4b9b59 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/11-middleware.mdx @@ -0,0 +1,401 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:07:14.954Z +title: 中间件 +description: 学习如何使用中间件在请求完成前运行代码。 +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加特定于页面路由的内容。任何共享内容不应包裹在组件中。 */} + +中间件允许您在请求完成前运行代码。然后,根据传入的请求,您可以通过重写、重定向、修改请求或响应头,或直接响应来修改响应。 + +中间件在缓存内容和路由匹配之前运行。详见[路径匹配](#matching-paths)。 + +## 约定 + +在项目根目录下使用 `middleware.ts`(或 `.js`)文件定义中间件。例如,与 `pages` 或 `app` 同级,或位于 `src` 目录内(如适用)。 + +## 示例 + +```ts filename="middleware.ts" switcher +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +// 如果内部使用 `await`,此函数可标记为 `async` +export function middleware(request: NextRequest) { + return NextResponse.redirect(new URL('/home', request.url)) +} + +// 查看下方的「路径匹配」了解更多 +export const config = { + matcher: '/about/:path*', +} +``` + +```js filename="middleware.js" switcher +import { NextResponse } from 'next/server' + +// 如果内部使用 `await`,此函数可标记为 `async` +export function middleware(request) { + return NextResponse.redirect(new URL('/home', request.url)) +} + +// 查看下方的「路径匹配」了解更多 +export const config = { + matcher: '/about/:path*', +} +``` + +## 路径匹配 + +中间件会为**项目中的每个路由**调用。以下是执行顺序: + +1. `next.config.js` 中的 `headers` +2. `next.config.js` 中的 `redirects` +3. 中间件(`rewrites`、`redirects` 等) +4. `next.config.js` 中的 `beforeFiles`(`rewrites`) +5. 文件系统路由(`public/`、`_next/static/`、`pages/`、`app/` 等) +6. `next.config.js` 中的 `afterFiles`(`rewrites`) +7. 动态路由(`/blog/[slug]`) +8. `next.config.js` 中的 `fallback`(`rewrites`) + +有两种方式可以定义中间件运行的路径: + +1. [自定义匹配器配置](#matcher) +2. [条件语句](#conditional-statements) + +### 匹配器 + +`matcher` 允许您筛选中间件在特定路径上运行。 + +```js filename="middleware.js" +export const config = { + matcher: '/about/:path*', +} +``` + +您可以使用数组语法匹配单个路径或多个路径: + +```js filename="middleware.js" +export const config = { + matcher: ['/about/:path*', '/dashboard/:path*'], +} +``` + +`matcher` 配置支持完整的正则表达式,因此支持如负向先行断言或字符匹配等操作。以下是一个匹配除特定路径外的所有路径的负向先行断言示例: + +```js filename="middleware.js" +export const config = { + matcher: [ + /* + * 匹配所有不以以下内容开头的请求路径: + * - api(API 路由) + * - _next/static(静态文件) + * - _next/image(图片优化文件) + * - favicon.ico(网站图标文件) + */ + '/((?!api|_next/static|_next/image|favicon.ico).*)', + ], +} +``` + +> **须知**:`matcher` 值必须是常量,以便在构建时进行静态分析。动态值(如变量)将被忽略。 + +配置的匹配器: + +1. 必须以 `/` 开头 +2. 可以包含命名参数:`/about/:path` 匹配 `/about/a` 和 `/about/b`,但不匹配 `/about/a/c` +3. 可以在命名参数上使用修饰符(以 `:` 开头):`/about/:path*` 匹配 `/about/a/b/c`,因为 `*` 表示 _零个或多个_。`?` 表示 _零个或一个_,`+` 表示 _一个或多个_ +4. 可以使用括号括起来的正则表达式:`/about/(.*)` 与 `/about/:path*` 相同 + +更多详情请参阅 [path-to-regexp](https://github.com/pillarjs/path-to-regexp#path-to-regexp-1) 文档。 + +> **须知**:为了向后兼容,Next.js 始终将 `/public` 视为 `/public/index`。因此,`/public/:path` 的匹配器会匹配。 + +### 条件语句 + +```ts filename="middleware.ts" switcher +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +export function middleware(request: NextRequest) { + if (request.nextUrl.pathname.startsWith('/about')) { + return NextResponse.rewrite(new URL('/about-2', request.url)) + } + + if (request.nextUrl.pathname.startsWith('/dashboard')) { + return NextResponse.rewrite(new URL('/dashboard/user', request.url)) + } +} +``` + +```js filename="middleware.js" switcher +import { NextResponse } from 'next/server' + +export function middleware(request) { + if (request.nextUrl.pathname.startsWith('/about')) { + return NextResponse.rewrite(new URL('/about-2', request.url)) + } + + if (request.nextUrl.pathname.startsWith('/dashboard')) { + return NextResponse.rewrite(new URL('/dashboard/user', request.url)) + } +} +``` + +## NextResponse + +`NextResponse` API 允许您: + +- 将传入请求 `redirect` 到不同的 URL +- 通过显示给定 URL 来 `rewrite` 响应 +- 为 API 路由、`getServerSideProps` 和 `rewrite` 目标设置请求头 +- 设置响应 cookie +- 设置响应头 + + + +要从中间件生成响应,您可以: + +1. `rewrite` 到生成响应的路由([页面](/docs/app/building-your-application/routing/pages-and-layouts) 或 [路由处理器](/docs/app/building-your-application/routing/route-handlers)) +2. 直接返回 `NextResponse`。参见[生成响应](#producing-a-response) + + + + + +要从中间件生成响应,您可以: + +1. `rewrite` 到生成响应的路由([页面](/docs/pages/building-your-application/routing/pages-and-layouts) 或 [Edge API 路由](/docs/pages/building-your-application/routing/api-routes)) +2. 直接返回 `NextResponse`。参见[生成响应](#producing-a-response) + + + +## 使用 Cookie + +Cookie 是常规的头部信息。在 `Request` 中,它们存储在 `Cookie` 头中。在 `Response` 中,它们位于 `Set-Cookie` 头中。Next.js 通过 `NextRequest` 和 `NextResponse` 上的 `cookies` 扩展提供了一种便捷的方式来访问和操作这些 Cookie。 + +1. 对于传入请求,`cookies` 提供以下方法:`get`、`getAll`、`set` 和 `delete` Cookie。您可以使用 `has` 检查 Cookie 是否存在,或使用 `clear` 删除所有 Cookie。 +2. 对于传出响应,`cookies` 提供以下方法:`get`、`getAll`、`set` 和 `delete`。 + +```ts filename="middleware.ts" switcher +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +export function middleware(request: NextRequest) { + // 假设传入请求中存在 "Cookie:nextjs=fast" 头 + // 使用 `RequestCookies` API 从请求中获取 Cookie + let cookie = request.cookies.get('nextjs') + console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' } + const allCookies = request.cookies.getAll() + console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }] + + request.cookies.has('nextjs') // => true + request.cookies.delete('nextjs') + request.cookies.has('nextjs') // => false + + // 使用 `ResponseCookies` API 在响应上设置 Cookie + const response = NextResponse.next() + response.cookies.set('vercel', 'fast') + response.cookies.set({ + name: 'vercel', + value: 'fast', + path: '/', + }) + cookie = response.cookies.get('vercel') + console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' } + // 传出的响应将包含 `Set-Cookie:vercel=fast;path=/test` 头。 + + return response +} +``` + +```js filename="middleware.js" switcher +import { NextResponse } from 'next/server' + +export function middleware(request) { + // 假设传入请求中存在 "Cookie:nextjs=fast" 头 + // 使用 `RequestCookies` API 从请求中获取 Cookie + let cookie = request.cookies.get('nextjs') + console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' } + const allCookies = request.cookies.getAll() + console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }] + + request.cookies.has('nextjs') // => true + request.cookies.delete('nextjs') + request.cookies.has('nextjs') // => false + + // 使用 `ResponseCookies` API 在响应上设置 Cookie + const response = NextResponse.next() + response.cookies.set('vercel', 'fast') + response.cookies.set({ + name: 'vercel', + value: 'fast', + path: '/', + }) + cookie = response.cookies.get('vercel') + console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' } + // 传出的响应将包含 `Set-Cookie:vercel=fast;path=/test` 头。 + + return response +} +``` + +## 设置头部 + +您可以使用 `NextResponse` API 设置请求和响应头(自 Next.js v13.0.0 起支持设置 _请求_ 头)。 + +```ts filename="middleware.ts" switcher +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +export function middleware(request: NextRequest) { + // 克隆请求头并设置新头 `x-hello-from-middleware1` + const requestHeaders = new Headers(request.headers) + requestHeaders.set('x-hello-from-middleware1', 'hello') + + // 您也可以在 NextResponse.rewrite 中设置请求头 + const response = NextResponse.next({ + request: { + // 新请求头 + headers: requestHeaders, + }, + }) + + // 设置新响应头 `x-hello-from-middleware2` + response.headers.set('x-hello-from-middleware2', 'hello') + return response +} +``` + +```js filename="middleware.js" switcher +import { NextResponse } from 'next/server' + +export function middleware(request) { + // 克隆请求头并设置新头 `x-hello-from-middleware1` + const requestHeaders = new Headers(request.headers) + requestHeaders.set('x-hello-from-middleware1', 'hello') + + // 您也可以在 NextResponse.rewrite 中设置请求头 + const response = NextResponse.next({ + request: { + // 新请求头 + headers: requestHeaders, + }, + }) + + // 设置新响应头 `x-hello-from-middleware2` + response.headers.set('x-hello-from-middleware2', 'hello') + return response +} +``` + +> **须知**:避免设置过大的头部,因为这可能会根据后端 Web 服务器配置导致 [431 请求头字段过大](https://developer.mozilla.org/docs/Web/HTTP/Status/431) 错误。 + +## 生成响应 + +您可以通过返回 `Response` 或 `NextResponse` 实例直接从中间件响应。(自 [Next.js v13.1.0](https://nextjs.org/blog/next-13-1#nextjs-advanced-middleware) 起可用) + +```ts filename="middleware.ts" switcher +import { NextRequest } from 'next/server' +import { isAuthenticated } from '@lib/auth' + +// 将中间件限制为以 `/api/` 开头的路径 +export const config = { + matcher: '/api/:function*', +} + +export function middleware(request: NextRequest) { + // 调用我们的身份验证函数检查请求 + if (!isAuthenticated(request)) { + // 返回 JSON 响应,指示错误消息 + return Response.json( + { success: false, message: 'authentication failed' }, + { status: 401 } + ) + } +} +``` + +```js filename="middleware.js" switcher +import { isAuthenticated } from '@lib/auth' + +// 将中间件限制为以 `/api/` 开头的路径 +export const config = { + matcher: '/api/:function*', +} + +export function middleware(request) { + // 调用我们的身份验证函数检查请求 + if (!isAuthenticated(request)) { + // 返回 JSON 响应,指示错误消息 + return Response.json( + { success: false, message: 'authentication failed' }, + { status: 401 } + ) + } +} +``` + +## 高级中间件标志 + +在 Next.js 的 `v13.1` 版本中,引入了两个额外的中间件标志 `skipMiddlewareUrlNormalize` 和 `skipTrailingSlashRedirect` 来处理高级用例。 + +`skipTrailingSlashRedirect` 允许禁用 Next.js 默认的添加或删除尾部斜杠的重定向,允许在中间件内进行自定义处理,这样可以维护某些路径的尾部斜杠而不影响其他路径,便于逐步迁移。 + +```js filename="next.config.js" +module.exports = { + skipTrailingSlashRedirect: true, +} +``` + +```js filename="middleware.js" +const legacyPrefixes = ['/docs', '/blog'] + +export default async function middleware(req) { + const { pathname } = req.nextUrl + + if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) { + return NextResponse.next() + } + + // 应用尾部斜杠处理 + if ( + !pathname.endsWith('/') && + !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/) + ) { + req.nextUrl.pathname += '/' + return NextResponse.redirect(req.nextUrl) + } +} +``` + +`skipMiddlewareUrlNormalize` 允许禁用 Next.js 进行的 URL 规范化,使直接访问和客户端转换的处理相同。在某些需要使用原始 URL 进行完全控制的高级情况下,此功能非常有用。 + +```js filename="next.config.js" +module.exports = { + skipMiddlewareUrlNormalize: true, +} +``` + +```js filename="middleware.js" +export default async function middleware(req) { + const { pathname } = req.nextUrl + + // GET /_next/data/build-id/hello.json + + console.log(pathname) + // 启用标志后,现在为 /_next/data/build-id/hello.json + // 未启用标志时,将被规范化为 /hello +} +``` + +## 版本历史 + +| 版本 | 变更 | +| --------- | ----------------------------------------------------------------------------------------- | +| `v13.1.0` | 添加了高级中间件标志 | +| `v13.0.0` | 中间件可以修改请求头、响应头并发送响应 | +| `v12.2.0` | 中间件稳定,请参阅[升级指南](/docs/messages/middleware-upgrade-guide) | +| `v12.0.9` | 在 Edge Runtime 中强制使用绝对 URL ([PR](https://github.com/vercel/next.js/pull/33410)) | +| `v12.0.0` | 添加中间件(Beta 版) | \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/12-colocation.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/12-colocation.mdx new file mode 100644 index 00000000..997b40f3 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/12-colocation.mdx @@ -0,0 +1,182 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:06:03.070Z +title: 项目组织与文件共置 +nav_title: 项目组织 +description: 了解如何组织 Next.js 项目及实现文件共置。 +related: + links: + - app/building-your-application/routing/defining-routes + - app/building-your-application/routing/route-groups + - app/building-your-application/configuring/src-directory + - app/building-your-application/configuring/absolute-imports-and-module-aliases +--- + +除了[路由文件夹与文件约定](/docs/getting-started/project-structure#app-routing-conventions),Next.js 对项目文件组织和共置方式**不做强制约束**。 + +本文介绍默认行为及可用于项目组织的功能特性。 + +- [默认安全共置](#safe-colocation-by-default) +- [项目组织功能](#project-organization-features) +- [项目组织策略](#project-organization-strategies) + +## 默认安全共置 + +在 `app` 目录中,[嵌套文件夹层级](/docs/app/building-your-application/routing#route-segments)定义了路由结构。 + +每个文件夹对应 URL 路径中的一个路由段 (route segment)。 + +但需注意,即使路由结构由文件夹定义,只有当一个路由段中添加了 `page.js` 或 `route.js` 文件后,该路由才会**对外可访问**。 + +示意图:说明在路由段添加 page.js 或 route.js 文件前,路由不可公开访问 + +且即使路由变为可公开访问,只有 `page.js` 或 `route.js` 返回的**内容**会被发送至客户端。 + +示意图:展示 page.js 和 route.js 文件如何使路由可公开访问 + +这意味着**项目文件**可以**安全地共置**于 `app` 目录的路由段中,而不会意外变为可路由。 + +示意图:共置的项目文件即使所在段包含 page.js 或 route.js 也不会变为可路由 + +> **须知**: +> +> - 这与 `pages` 目录不同,`pages` 中的任何文件都被视为路由。 +> - 虽然你**可以**在 `app` 中共置项目文件,但并非**必须**。若倾向其他方式,可[将项目文件存储在 `app` 目录外](#store-project-files-outside-of-app)。 + +## 项目组织功能 + +Next.js 提供多种功能助您组织项目。 + +### 私有文件夹 + +通过给文件夹添加下划线前缀可创建私有文件夹:`_folderName` + +这表示该文件夹是私有实现细节,路由系统不应处理,从而**将该文件夹及其所有子文件夹**排除在路由之外。 + +使用私有文件夹的示例目录结构 + +由于 `app` 目录中的文件默认可[安全共置](#safe-colocation-by-default),共置并非必须使用私有文件夹。但它们适用于以下场景: + +- 将 UI 逻辑与路由逻辑分离。 +- 在项目和 Next.js 生态中统一组织内部文件。 +- 在代码编辑器中排序和分组文件。 +- 避免与未来 Next.js 文件约定产生命名冲突。 + +> **须知** +> +> - 虽然这不是框架约定,您也可以考虑对私有文件夹外的文件使用相同下划线模式标记为"私有"。 +> - 可通过给文件夹名添加 `%5F`(下划线的 URL 编码形式)创建以下划线开头的 URL 段:`%5FfolderName`。 +> - 若不使用私有文件夹,建议了解 Next.js [特殊文件约定](/docs/getting-started/project-structure#routing-files)以避免意外命名冲突。 + +### 路由组 + +通过将文件夹包裹在圆括号中可创建路由组:`(folderName)` + +这表示该文件夹仅用于组织目的,**不应包含**在路由的 URL 路径中。 + +使用路由组的示例目录结构 + +路由组适用于: + +- [将路由分组组织](/docs/app/building-your-application/routing/route-groups#organize-routes-without-affecting-the-url-path),例如按站点板块、功能意图或团队。 +- 在同一路由段层级启用嵌套布局: + - [在同一段创建多个嵌套布局,包括多个根布局](/docs/app/building-your-application/routing/route-groups#creating-multiple-root-layouts) + - [为公共段中的路由子集添加布局](/docs/app/building-your-application/routing/route-groups#opting-specific-segments-into-a-layout) + +### `src` 目录 + +Next.js 支持将应用代码(包括 `app`)存储在可选的 [`src` 目录](/docs/app/building-your-application/configuring/src-directory)中。这可将应用代码与主要位于项目根目录的配置文件分离。 + +使用 `src` 目录的示例结构 + +### 模块路径别名 + +Next.js 支持[模块路径别名](/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases),简化深层嵌套项目文件中的导入维护。 + +```jsx filename="app/dashboard/settings/analytics/page.js" +// 之前 +import { Button } from '../../../components/button' + +// 之后 +import { Button } from '@/components/button' +``` + +## 项目组织策略 + +Next.js 项目中没有绝对"正确"或"错误"的文件组织方式。 + +以下部分列举了常见策略的高阶概述。核心原则是选择适合您和团队的策略,并保持项目内的一致性。 + +> **须知**:下文示例中使用的 `components` 和 `lib` 文件夹是通用占位名称,它们没有特殊框架含义,您的项目可使用其他文件夹如 `ui`、`utils`、`hooks`、`styles` 等。 + +### 将项目文件存储在 `app` 外 + +此策略将所有应用代码存放在**项目根目录**的共享文件夹中,保持 `app` 目录纯粹用于路由。 + +项目文件存储在 app 外的示例结构 + +### 将项目文件存储在 `app` 内的顶层文件夹中 + +此策略将所有应用代码存放在 `app` 目录根级的共享文件夹中。 + +项目文件存储在 app 内的示例结构 + +### 按功能或路由拆分项目文件 + +此策略将全局共享的应用代码存放在 `app` 根目录,并将特定功能的应用代码**拆分**到使用它们的路由段中。 + +按功能或路由拆分项目文件的示例结构 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/13-internationalization.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/13-internationalization.mdx new file mode 100644 index 00000000..c9b509aa --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/13-internationalization.mdx @@ -0,0 +1,154 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:05:38.592Z +title: 国际化 +description: 通过国际化路由和本地化内容添加多语言支持。 +--- + +Next.js 允许您配置路由和内容渲染以支持多语言。使您的站点适应不同区域设置包括翻译内容(本地化)和国际化路由。 + +## 术语 + +- **区域设置 (Locale):** 用于标识一组语言和格式偏好的标识符。通常包括用户首选语言及其可能的地理区域。 + - `en-US`: 美国使用的英语 + - `nl-NL`: 荷兰使用的荷兰语 + - `nl`: 荷兰语,无特定区域 + +## 路由概述 + +建议使用浏览器中的用户语言偏好来选择区域设置。更改首选语言会修改发送到应用程序的 `Accept-Language` 请求头。 + +例如,使用以下库可以根据传入的 `Request`、计划支持的区域设置和默认区域设置,通过检查 `Headers` 来决定选择哪个区域设置。 + +```js filename="middleware.js" +import { match } from '@formatjs/intl-localematcher' +import Negotiator from 'negotiator' + +let headers = { 'accept-language': 'en-US,en;q=0.5' } +let languages = new Negotiator({ headers }).languages() +let locales = ['en-US', 'nl-NL', 'nl'] +let defaultLocale = 'en-US' + +match(languages, locales, defaultLocale) // -> 'en-US' +``` + +可以通过子路径 (`/fr/products`) 或域名 (`my-site.fr/products`) 实现路由国际化。利用这些信息,您现在可以基于[中间件](/docs/app/building-your-application/routing/middleware)中的区域设置重定向用户。 + +```js filename="middleware.js" + +let locales = ['en-US', 'nl-NL', 'nl'] + +// 获取首选区域设置,类似于上述方式或使用库 +function getLocale(request) { ... } + +export function middleware(request) { + // 检查路径中是否存在支持的区域设置 + const { pathname } = request.nextUrl + const pathnameHasLocale = locales.some( + (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` + ) + + if (pathnameHasLocale) return + + // 无区域设置时重定向 + const locale = getLocale(request) + request.nextUrl.pathname = `/${locale}${pathname}` + // 例如传入请求为 /products + // 新 URL 变为 /en-US/products + return Response.redirect(request.nextUrl) +} + +export const config = { + matcher: [ + // 跳过所有内部路径 (_next) + '/((?!_next).*)', + // 可选:仅在根 URL (/) 上运行 + // '/' + ], +} +``` + +最后,确保 `app/` 目录下的所有特殊文件都嵌套在 `app/[lang]` 下。这使得 Next.js 路由器能动态处理路由中的不同区域设置,并将 `lang` 参数传递给每个布局和页面。例如: + +```jsx filename="app/[lang]/page.js" +// 您现在可以访问当前区域设置 +// 例如 /en-US/products -> `lang` 为 "en-US" +export default async function Page({ params: { lang } }) { + return ... +} +``` + +根布局也可以嵌套在新文件夹中(例如 `app/[lang]/layout.js`)。 + +## 本地化 + +根据用户首选区域设置更改显示内容(即本地化)并非 Next.js 特有功能。以下描述的模式在任何 Web 应用程序中同样适用。 + +假设我们希望在应用程序中同时支持英语和荷兰语内容。我们可以维护两个不同的“字典”,这些字典对象提供了从某个键到本地化字符串的映射。例如: + +```json filename="dictionaries/en.json" +{ + "products": { + "cart": "Add to Cart" + } +} +``` + +```json filename="dictionaries/nl.json" +{ + "products": { + "cart": "Toevoegen aan Winkelwagen" + } +} +``` + +然后我们可以创建一个 `getDictionary` 函数来加载请求的区域设置的翻译: + +```jsx filename="app/[lang]/dictionaries.js" +import 'server-only' + +const dictionaries = { + en: () => import('./dictionaries/en.json').then((module) => module.default), + nl: () => import('./dictionaries/nl.json').then((module) => module.default), +} + +export const getDictionary = async (locale) => dictionaries[locale]() +``` + +根据当前选择的语言,我们可以在布局或页面中获取字典。 + +```jsx filename="app/[lang]/page.js" +import { getDictionary } from './dictionaries' + +export default async function Page({ params: { lang } }) { + const dict = await getDictionary(lang) // en + return // Add to Cart +} +``` + +由于 `app/` 目录中的所有布局和页面默认都是[服务器组件](/docs/app/building-your-application/rendering/server-components),我们无需担心翻译文件的大小会影响客户端 JavaScript 包大小。此代码**仅在服务器上运行**,只有生成的 HTML 会发送到浏览器。 + +## 静态生成 + +要为给定的一组区域设置生成静态路由,我们可以在任何页面或布局中使用 `generateStaticParams`。这可以是全局的,例如在根布局中: + +```jsx filename="app/[lang]/layout.js" +export async function generateStaticParams() { + return [{ lang: 'en-US' }, { lang: 'de' }] +} + +export default function Root({ children, params }) { + return ( + + {children} + + ) +} +``` + +## 资源 + +- [最小化 i18n 路由和翻译](https://github.com/vercel/next.js/tree/canary/examples/app-dir-i18n-routing) +- [`next-intl`](https://next-intl-docs.vercel.app/docs/next-13) +- [`next-international`](https://github.com/QuiiBz/next-international) +- [`next-i18n-router`](https://github.com/i18nexus/next-i18n-router) \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/index.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/index.mdx new file mode 100644 index 00000000..7c430066 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/01-routing/index.mdx @@ -0,0 +1,162 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:05:52.309Z +title: 路由基础 +nav_title: 路由 +description: 学习前端应用路由的基础知识。 +--- + +路由是每个应用的骨架。本页将介绍 Web 路由的**基础概念**以及如何在 Next.js 中处理路由。 + +## 术语 + +首先,您会在文档中频繁遇到这些术语。以下是快速参考: + +组件树术语 + +- **树 (Tree):** 用于可视化层次结构的约定。例如包含父子组件的组件树、文件夹结构等。 +- **子树 (Subtree):** 树的一部分,从新根节点(首个)开始到叶节点(末个)结束。 +- **根节点 (Root):** 树或子树中的第一个节点,例如根布局。 +- **叶节点 (Leaf):** 子树中没有子节点的节点,例如 URL 路径的最后一段。 + +URL 结构术语 + +- **URL 片段 (URL Segment):** 由斜杠分隔的 URL 路径部分。 +- **URL 路径 (URL Path):** 域名之后的部分(由多个片段组成)。 + +## `app` 路由 + +在版本 13 中,Next.js 推出了基于 [React 服务端组件](/docs/app/building-your-application/rendering/server-components) 构建的新 **App 路由**,支持共享布局、嵌套路由、加载状态、错误处理等功能。 + +App 路由在名为 `app` 的新目录中工作。`app` 目录与 `pages` 目录共存以实现渐进式迁移。您可以将部分路由迁移到新行为,同时保留其他路由在 `pages` 目录中使用旧行为。如果应用使用 `pages` 目录,请同时参阅 [Pages 路由](/docs/pages/building-your-application/routing) 文档。 + +> **须知**: App 路由优先级高于 Pages 路由。跨目录的路由不应解析到相同 URL 路径,否则会引发构建时错误以防止冲突。 + +Next.js App 目录 + +默认情况下,`app` 内的组件是 [React 服务端组件](/docs/app/building-your-application/rendering/server-components)。这是性能优化项且便于使用,您也可以选择 [客户端组件](/docs/app/building-your-application/rendering/client-components)。 + +> **推荐:** 如果您不熟悉服务端组件,请查阅 [服务端组件](/docs/app/building-your-application/rendering/server-components) 页面。 + +## 文件夹与文件的作用 + +Next.js 使用基于文件系统的路由规则: + +- **文件夹**用于定义路由。路由是从**根文件夹**到最终包含 `page.js` 文件的**叶文件夹**的嵌套文件夹路径。参见 [定义路由](/docs/app/building-your-application/routing/defining-routes)。 +- **文件**用于创建路由段对应的 UI。参见 [特殊文件](#file-conventions)。 + +## 路由段 + +路由中的每个文件夹代表一个**路由段**。每个路由段映射到 **URL 路径** 中的对应**片段**。 + +路由段如何映射到 URL 片段 + +## 嵌套路由 + +要创建嵌套路由,可以将文件夹互相嵌套。例如,通过在 `app` 目录中嵌套两个新文件夹,可添加 `/dashboard/settings` 路由。 + +`/dashboard/settings` 路由由三个段组成: + +- `/` (根段) +- `dashboard` (段) +- `settings` (叶段) + +## 文件约定 + +Next.js 提供了一组特殊文件来为嵌套路由创建具有特定行为的 UI: + +| | | +| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| [`layout`](/docs/app/building-your-application/routing/pages-and-layouts#layouts) | 为路由段及其子项提供共享 UI | +| [`page`](/docs/app/building-your-application/routing/pages-and-layouts#pages) | 路由的唯一 UI 并使路由可公开访问 | +| [`loading`](/docs/app/building-your-application/routing/loading-ui-and-streaming) | 为路由段及其子项提供加载 UI | +| [`not-found`](/docs/app/api-reference/file-conventions/not-found) | 为路由段及其子项提供 404 UI | +| [`error`](/docs/app/building-your-application/routing/error-handling) | 为路由段及其子项提供错误 UI | +| [`global-error`](/docs/app/building-your-application/routing/error-handling) | 全局错误 UI | +| [`route`](/docs/app/building-your-application/routing/route-handlers) | 服务端 API 端点 | +| [`template`](/docs/app/building-your-application/routing/pages-and-layouts#templates) | 专用于重新渲染的布局 UI | +| [`default`](/docs/app/api-reference/file-conventions/default) | 为 [并行路由](/docs/app/building-your-application/routing/parallel-routes) 提供回退 UI | + +> **须知**: 特殊文件可使用 `.js`、`.jsx` 或 `.tsx` 扩展名。 + +## 组件层级 + +路由段特殊文件中定义的 React 组件按特定层级渲染: + +1. `layout.js` +2. `template.js` +3. `error.js` (React 错误边界) +4. `loading.js` (React Suspense 边界) +5. `not-found.js` (React 错误边界) +6. `page.js` 或嵌套的 `layout.js` + +文件约定的组件层级 + +在嵌套路由中,子段的组件会嵌套在其父段组件**内部**。 + +嵌套文件约定的组件层级 + +## 同置 + +除了特殊文件外,您还可以在 `app` 目录的文件夹中同置自己的文件(例如组件、样式、测试等)。 + +这是因为虽然文件夹定义路由,但只有 `page.js` 或 `route.js` 返回的内容可公开访问。 + +包含同置文件的文件夹结构示例 + +了解更多关于 [项目组织与同置](/docs/app/building-your-application/routing/colocation)。 + +## 高级路由模式 + +App 路由还提供了一系列约定来实现更高级的路由模式,包括: + +- [并行路由](/docs/app/building-your-application/routing/parallel-routes): 允许在同一视图中同时展示两个或多个可独立导航的页面。适用于具有子导航的拆分视图(例如仪表盘)。 +- [拦截路由](/docs/app/building-your-application/routing/intercepting-routes): 允许拦截路由并在另一路由的上下文中展示。适用于需要保持当前页面上下文的情况(例如编辑任务时查看所有任务,或在信息流中展开照片)。 + +这些模式让您能构建更丰富复杂的 UI,使历史上对小团队和个人开发者难以实现的功能变得民主化。 + +## 下一步 + +现在您已了解 Next.js 路由基础,点击以下链接创建您的第一个路由: \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx new file mode 100644 index 00000000..e0785354 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/01-fetching-caching-and-revalidating.mdx @@ -0,0 +1,375 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:06:48.651Z +title: 数据获取、缓存与重新验证 +nav_title: 获取、缓存与重新验证 +description: 学习如何在您的 Next.js 应用中获取、缓存和重新验证数据。 +--- + +数据获取是任何应用的核心部分。本文将介绍如何在 React 和 Next.js 中获取、缓存及重新验证数据。 + +数据获取有以下四种方式: + +1. [在服务端使用 `fetch`](#在服务端使用-fetch-获取数据) +2. [在服务端使用第三方库](#在服务端使用第三方库获取数据) +3. [在客户端通过路由处理器获取](#在客户端通过路由处理器获取数据) +4. [在客户端使用第三方库](#在客户端使用第三方库获取数据) + +## 在服务端使用 `fetch` 获取数据 + +Next.js 扩展了原生的 [`fetch` Web API](https://developer.mozilla.org/docs/Web/API/Fetch_API),允许您为服务端的每个 fetch 请求配置[缓存](#数据缓存)和[重新验证](#重新验证数据)行为。React 则扩展了 `fetch` 功能,在渲染 React 组件树时自动[记忆化](/docs/app/building-your-application/data-fetching/patterns#fetching-data-where-its-needed) fetch 请求。 + +您可以在[服务端组件](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md)中使用 `async`/`await` 配合 `fetch`,也可用于[路由处理器](/docs/app/building-your-application/routing/route-handlers)和[服务端动作](/docs/app/building-your-application/data-fetching/forms-and-mutations)。 + +例如: + +```tsx filename="app/page.tsx" switcher +async function getData() { + const res = await fetch('https://api.example.com/...') + // 返回值不会被序列化 + // 您可以返回 Date、Map、Set 等类型 + + if (!res.ok) { + // 这将触发最近的 `error.js` 错误边界 + throw new Error('Failed to fetch data') + } + + return res.json() +} + +export default async function Page() { + const data = await getData() + + return
+} +``` + +```jsx filename="app/page.js" switcher +async function getData() { + const res = await fetch('https://api.example.com/...') + // 返回值不会被序列化 + // 您可以返回 Date、Map、Set 等类型 + + if (!res.ok) { + // 这将触发最近的 `error.js` 错误边界 + throw new Error('Failed to fetch data') + } + + return res.json() +} + +export default async function Page() { + const data = await getData() + + return
+} +``` + +> **须知**: +> +> - Next.js 提供了一些在服务端组件中获取数据时可能有用的函数,如 [`cookies`](/docs/app/api-reference/functions/cookies) 和 [`headers`](/docs/app/api-reference/functions/headers)。这些函数会导致路由动态渲染,因为它们依赖于请求时的信息。 +> - 在路由处理器中,`fetch` 请求不会被记忆化,因为路由处理器不属于 React 组件树。 +> - 在 TypeScript 中使用 `async`/`await` 的服务端组件需要 TypeScript `5.1.3` 或更高版本以及 `@types/react` `18.2.8` 或更高版本。 + +### 数据缓存 + +缓存存储数据,避免每次请求都从数据源重新获取。 + +默认情况下,Next.js 会自动将 `fetch` 的返回值缓存在服务端的[数据缓存](/docs/app/building-your-application/caching#data-cache)中。这意味着数据可以在构建时或请求时获取、缓存,并在每次数据请求时重复使用。 + +```js +// 'force-cache' 是默认值,可以省略 +fetch('https://...', { cache: 'force-cache' }) +``` + +使用 `POST` 方法的 `fetch` 请求也会被自动缓存。除非它位于使用 `POST` 方法的[路由处理器](/docs/app/building-your-application/routing/route-handlers)中,此时不会被缓存。 + +> **什么是数据缓存?** +> +> 数据缓存是一个持久的 [HTTP 缓存](https://developer.mozilla.org/docs/Web/HTTP/Caching)。根据您的平台,缓存可以自动扩展并[跨多个区域共享](https://vercel.com/docs/infrastructure/data-cache)。 +> +> 了解更多关于[数据缓存](/docs/app/building-your-application/caching#data-cache)的信息。 + +### 重新验证数据 + +重新验证是清除数据缓存并重新获取最新数据的过程。这在数据发生变化且您希望确保显示最新信息时非常有用。 + +缓存数据可以通过两种方式重新验证: + +- **基于时间的重新验证**:在一定时间间隔后自动重新验证数据。适用于变化不频繁且新鲜度要求不高的数据。 +- **按需重新验证**:基于事件(如表单提交)手动重新验证数据。按需重新验证可以使用基于标签或路径的方法一次性重新验证一组数据。适用于需要尽快显示最新数据的场景(例如无头 CMS 内容更新时)。 + +#### 基于时间的重新验证 + +要按时间间隔重新验证数据,可以使用 `fetch` 的 `next.revalidate` 选项设置资源的缓存生命周期(以秒为单位)。 + +```js +fetch('https://...', { next: { revalidate: 3600 } }) +``` + +或者,要重新验证路由段中的所有 `fetch` 请求,可以使用[段配置选项](/docs/app/api-reference/file-conventions/route-segment-config)。 + +```jsx filename="layout.js | page.js" +export const revalidate = 3600 // 最多每小时重新验证一次 +``` + +如果在静态渲染的路由中有多个 fetch 请求,且每个请求有不同的重新验证频率,则所有请求将使用最短的时间。对于动态渲染的路由,每个 `fetch` 请求将独立重新验证。 + +了解更多关于[基于时间的重新验证](/docs/app/building-your-application/caching#time-based-revalidation)。 + +#### 按需重新验证 + +可以在路由处理器或服务端动作中通过路径 ([`revalidatePath`](/docs/app/api-reference/functions/revalidatePath)) 或缓存标签 ([`revalidateTag`](/docs/app/api-reference/functions/revalidateTag)) 按需重新验证数据。 + +Next.js 有一个缓存标签系统,用于跨路由使 `fetch` 请求失效。 + +1. 使用 `fetch` 时,可以选择用一个或多个标签标记缓存条目。 +2. 然后,可以调用 `revalidateTag` 重新验证与该标签关联的所有条目。 + +例如,以下 `fetch` 请求添加了缓存标签 `collection`: + +```tsx filename="app/page.tsx" switcher +export default async function Page() { + const res = await fetch('https://...', { next: { tags: ['collection'] } }) + const data = await res.json() + // ... +} +``` + +```jsx filename="app/page.js" switcher +export default async function Page() { + const res = await fetch('https://...', { next: { tags: ['collection'] } }) + const data = await res.json() + // ... +} +``` + +如果使用路由处理器,应创建一个只有 Next.js 应用知道的密钥。此密钥用于防止未经授权的重新验证尝试。例如,可以通过以下 URL 结构访问路由(手动或通过 webhook): + +```bash filename="URL" +https:///api/revalidate?tag=collection&secret= +``` + +```ts filename="app/api/revalidate/route.ts" switcher +import { NextRequest } from 'next/server' +import { revalidateTag } from 'next/cache' + +// 例如 webhook 调用 `your-website.com/api/revalidate?tag=collection&secret=` +export async function POST(request: NextRequest) { + const secret = request.nextUrl.searchParams.get('secret') + const tag = request.nextUrl.searchParams.get('tag') + + if (secret !== process.env.MY_SECRET_TOKEN) { + return Response.json({ message: 'Invalid secret' }, { status: 401 }) + } + + if (!tag) { + return Response.json({ message: 'Missing tag param' }, { status: 400 }) + } + + revalidateTag(tag) + + return Response.json({ revalidated: true, now: Date.now() }) +} +``` + +```js filename="app/api/revalidate/route.js" switcher +import { revalidateTag } from 'next/cache' + +// 例如 webhook 调用 `your-website.com/api/revalidate?tag=collection&secret=` +export async function POST(request) { + const secret = request.nextUrl.searchParams.get('secret') + const tag = request.nextUrl.searchParams.get('tag') + + if (secret !== process.env.MY_SECRET_TOKEN) { + return Response.json({ message: 'Invalid secret' }, { status: 401 }) + } + + if (!tag) { + return Response.json({ message: 'Missing tag param' }, { status: 400 }) + } + + revalidateTag(tag) + + return Response.json({ revalidated: true, now: Date.now() }) +} +``` + +或者,可以使用 [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) 重新验证与路径关联的所有数据。 + +```ts filename="app/api/revalidate/route.ts" switcher +import { NextRequest } from 'next/server' +import { revalidatePath } from 'next/cache' + +export async function POST(request: NextRequest) { + const path = request.nextUrl.searchParams.get('path') + + if (!path) { + return Response.json({ message: 'Missing path param' }, { status: 400 }) + } + + revalidatePath(path) + + return Response.json({ revalidated: true, now: Date.now() }) +} +``` + +```js filename="app/api/revalidate/route.js" switcher +import { revalidatePath } from 'next/cache' + +export async function POST(request) { + const path = request.nextUrl.searchParams.get('path') + + if (!path) { + return Response.json({ message: 'Missing path param' }, { status: 400 }) + } + + revalidatePath(path) + + return Response.json({ revalidated: true, now: Date.now() }) +} +``` + +了解更多关于[按需重新验证](/docs/app/building-your-application/caching#on-demand-revalidation)。 + +#### 错误处理与重新验证 + +如果在尝试重新验证数据时抛出错误,将继续从缓存中提供上次成功生成的数据。在后续请求中,Next.js 将重试重新验证数据。 + +### 退出数据缓存 + +以下情况 `fetch` 请求**不会**被缓存: + +- `fetch` 请求中添加了 `cache: 'no-store'`。 +- 单个 `fetch` 请求中添加了 `revalidate: 0` 选项。 +- `fetch` 请求位于使用 `POST` 方法的路由处理器中。 +- `fetch` 请求在使用了 `headers` 或 `cookies` 之后。 +- 使用了 `const dynamic = 'force-dynamic'` 路由段选项。 +- `fetchCache` 路由段选项配置为默认跳过缓存。 +- `fetch` 请求使用了 `Authorization` 或 `Cookie` 标头,并且组件树中有未缓存的请求在其上方。 + +#### 单个 `fetch` 请求 + +要为单个 `fetch` 请求退出缓存,可以将 `fetch` 的 `cache` 选项设置为 `'no-store'`。这将在每次请求时动态获取数据。 + +```js filename="layout.js | page.js" +fetch('https://...', { cache: 'no-store' }) +``` + +查看所有可用的 `cache` 选项,请参阅 [`fetch` API 参考](/docs/app/api-reference/functions/fetch)。 + +#### 多个 `fetch` 请求 + +如果路由段(如布局或页面)中有多个 `fetch` 请求,可以使用[段配置选项](/docs/app/api-reference/file-conventions/route-segment-config)配置该段中所有数据请求的缓存行为。 + +例如,使用 `const dynamic = 'force-dynamic'` 将导致所有数据在请求时获取,并且该段将动态渲染。 + +```js filename="layout.js | page.js" +// 添加 +export const dynamic = 'force-dynamic' +``` + +段配置选项提供了广泛的列表,让您可以精细控制路由段的静态和动态行为。更多信息请参阅[API 参考](/docs/app/api-reference/file-conventions/route-segment-config)。 + +## 在服务端使用第三方库获取数据 + +如果您使用的第三方库不支持或不暴露 `fetch`(例如数据库、CMS 或 ORM 客户端),可以使用[路由段配置选项](/docs/app/api-reference/file-conventions/route-segment-config)和 React 的 `cache` 函数配置这些请求的缓存和重新验证行为。 + +数据是否缓存取决于路由段是[静态还是动态渲染](/docs/app/building-your-application/rendering/server-components#server-rendering-strategies)。如果段是静态的(默认),请求的输出将作为路由段的一部分被缓存和重新验证。如果段是动态的,请求的输出将**不会**被缓存,并在段渲染时每次请求都会重新获取。 + +> **须知**: +> +> Next.js 正在开发一个 API `unstable_cache`,用于配置单个第三方请求的缓存和重新验证行为。 + +### 示例 + +在以下示例中: + +- `revalidate` 选项设置为 `3600`,意味着数据最多每小时缓存并重新验证一次。 +- React 的 `cache` 函数用于[记忆化](/docs/app/building-your-application/caching#request-memoization)数据请求。 + +```ts filename="utils/get-item.ts" switcher +import { cache } from 'react' + +export const revalidate = 3600 // 最多每小时重新验证数据一次 + +export const getItem = cache(async (id: string) => { + const item = await db.item.findUnique({ id }) + return item +}) +``` + +```js filename="utils/get-item.js" switcher +import { cache } from 'react' + +export const revalidate = 3600 // 最多每小时重新验证数据一次 + +export const getItem = cache(async (id) => { + const item = await db.item.findUnique({ id }) + return item +}) +``` + +尽管 `getItem` 函数被调用了两次,但只会向数据库发送一次查询。 + +```tsx filename="app/item/[id]/layout.tsx" switcher +import { getItem } from '@/utils/get-item' + +export default async function Layout({ + params: { id }, +}: { + params: { id: string } +}) { + const item = await getItem(id) + // ... +} +``` + +```jsx filename="app/item/[id]/layout.js" switcher +import { getItem } from '@/utils/get-item' + +export default async function Layout({ params: { id } }) { + const item = await getItem(id) + // ... +} +``` + +```tsx filename="app/item/[id]/page.tsx" switcher +import { getItem } from '@/utils/get-item' + +export default async function Page({ + params: { id }, +}: { + params: { id: string } +}) { + const item = await getItem(id) + // ... +} +``` + +```jsx filename="app/item/[id]/page.js" switcher +import { getItem } from '@/utils/get-item' + +export default async function Page({ params: { id } }) { + const item = await getItem(id) + // ... +} +``` + +## 在客户端通过路由处理器获取数据 + +如果需要在客户端组件中获取数据,可以从客户端调用[路由处理器](/docs/app/building-your-application/routing/route-handlers)。路由处理器在服务端执行并将数据返回给客户端。这在您不想向客户端暴露敏感信息(如 API 令牌)时非常有用。 + +请参阅[路由处理器](/docs/app/building-your-application/routing/route-handlers)文档中的示例。 + +> **服务端组件与路由处理器** +> +> 由于服务端组件在服务端渲染,您不需要从服务端组件调用路由处理器来获取数据。相反,可以直接在服务端组件中获取数据。 + +## 在客户端使用第三方库获取数据 + +您也可以使用第三方库(如 [SWR](https://swr.vercel.app/) 或 [React Query](https://tanstack.com/query/latest))在客户端获取数据。这些库提供了自己的 API 用于记忆化请求、缓存、重新验证和变更数据。 + +> **未来 API**: +> +> `use` 是一个 React 函数,**接受并处理**由函数返回的 promise。目前**不建议**在客户端组件中包装 `fetch` 使用 `use`,因为这可能会触发多次重新渲染。了解更多关于 `use` 的信息,请参阅 [React RFC](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#usepromise)。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/02-patterns.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/02-patterns.mdx new file mode 100644 index 00000000..7295ac78 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/02-patterns.mdx @@ -0,0 +1,314 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:06:14.103Z +title: 数据获取模式 +description: 了解 React 和 Next.js 中常见的数据获取模式。 +--- + +在 React 和 Next.js 中有一些推荐的数据获取模式和最佳实践。本页将介绍一些最常见的模式及其使用方法。 + +### 服务端数据获取 + +我们建议尽可能在服务端获取数据。这样做可以: + +- 直接访问后端数据资源(如数据库) +- 通过防止敏感信息(如访问令牌和 API 密钥)暴露给客户端,提高应用安全性 +- 在同一环境中获取数据并渲染,减少客户端与服务器之间的往返通信以及客户端的[主线程工作负载](https://vercel.com/blog/how-react-18-improves-application-performance) +- 通过单次往返执行多次数据获取,而非在客户端发起多个独立请求 +- 减少客户端-服务器之间的[瀑布流问题](#并行与顺序数据获取) +- 根据所在地区,数据获取可以更靠近数据源,从而降低延迟并提升性能 + +您可以使用服务端组件、[路由处理器](/docs/app/building-your-application/routing/route-handlers)和[服务端操作](/docs/app/building-your-application/data-fetching/forms-and-mutations)在服务端获取数据。 + +### 在需要的地方获取数据 + +如果需要在组件树中的多个组件中使用相同数据(如当前用户),您不必全局获取数据或在组件间传递 props。相反,您可以在需要数据的组件中使用 `fetch` 或 React `cache`,而无需担心多次请求相同数据对性能的影响。 + +这是因为 `fetch` 请求会自动被记忆化。了解更多关于[请求记忆化](/docs/app/building-your-application/caching#request-memoization)的内容。 + +> **须知**:这也适用于布局,因为无法在父布局与其子组件之间传递数据。 + +### 流式渲染 + +流式渲染和 [Suspense](https://react.dev/reference/react/Suspense) 是 React 的功能,允许您逐步渲染并将 UI 单元增量式地流式传输到客户端。 + +通过服务端组件和[嵌套布局](/docs/app/building-your-application/routing/pages-and-layouts),您可以立即渲染不需要特定数据的页面部分,并为正在获取数据的页面部分显示[加载状态](/docs/app/building-your-application/routing/loading-ui-and-streaming)。这意味着用户无需等待整个页面加载完成即可开始与之交互。 + +服务端流式渲染 + +要了解更多关于流式渲染和 Suspense 的内容,请参阅[加载 UI](/docs/app/building-your-application/routing/loading-ui-and-streaming) 和[流式渲染与 Suspense](/docs/app/building-your-application/routing/loading-ui-and-streaming#streaming-with-suspense) 页面。 + +### 并行与顺序数据获取 + +在 React 组件内部获取数据时,需要注意两种数据获取模式:并行和顺序。 + +顺序与并行数据获取 + +- **顺序数据获取**:路由中的请求相互依赖,因此会产生瀑布流。某些情况下您可能需要这种模式,因为一个请求依赖于另一个请求的结果,或者您希望在满足某个条件后再进行下一个请求以节省资源。然而,这种行为也可能是无意的,并导致更长的加载时间。 +- **并行数据获取**:路由中的请求会立即并行发起,同时加载数据。这减少了客户端-服务器之间的瀑布流问题以及数据加载的总时间。 + +#### 顺序数据获取 + +如果有嵌套组件,且每个组件都获取自己的数据,那么当这些数据请求不同时(这不适用于相同数据的请求,因为它们会自动[记忆化](/docs/app/building-your-application/caching#request-memoization)),数据获取将按顺序进行。 + +例如,`Playlists` 组件只有在 `Artist` 组件完成数据获取后才会开始获取数据,因为 `Playlists` 依赖于 `artistID` prop: + +```tsx filename="app/artist/page.tsx" switcher +// ... + +async function Playlists({ artistID }: { artistID: string }) { + // 等待播放列表 + const playlists = await getArtistPlaylists(artistID) + + return ( +
    + {playlists.map((playlist) => ( +
  • {playlist.name}
  • + ))} +
+ ) +} + +export default async function Page({ + params: { username }, +}: { + params: { username: string } +}) { + // 等待艺术家数据 + const artist = await getArtist(username) + + return ( + <> +

{artist.name}

+ 加载中...}> + + + + ) +} +``` + +```jsx filename="app/artist/page.js" switcher +// ... + +async function Playlists({ artistID }) { + // 等待播放列表 + const playlists = await getArtistPlaylists(artistID) + + return ( +
    + {playlists.map((playlist) => ( +
  • {playlist.name}
  • + ))} +
+ ) +} + +export default async function Page({ params: { username } }) { + // 等待艺术家数据 + const artist = await getArtist(username) + + return ( + <> +

{artist.name}

+ 加载中...}> + + + + ) +} +``` + +在这种情况下,您可以使用 [`loading.js`](/docs/app/building-your-application/routing/loading-ui-and-streaming)(用于路由段)或 [React ``](/docs/app/building-your-application/routing/loading-ui-and-streaming#streaming-with-suspense)(用于嵌套组件)来显示即时加载状态,同时 React 流式传输结果。 + +这将防止整个路由被数据获取阻塞,用户可以与页面中未被阻塞的部分进行交互。 + +> **阻塞式数据请求:** +> +> 防止瀑布流问题的另一种方法是在应用的根节点全局获取数据,但这会阻塞其下所有路由段的渲染,直到数据加载完成。这可以描述为“全有或全无”的数据获取方式。要么获取整个页面或应用的所有数据,要么什么也没有。 +> +> 任何带有 `await` 的 `fetch` 请求都会阻塞其下整个树的渲染和数据获取,除非它们被包裹在 `` 边界中或使用了 `loading.js`。另一种方法是使用[并行数据获取](#并行数据获取)或[预加载模式](#预加载数据)。 + +#### 并行数据获取 + +要并行获取数据,您可以在使用数据的组件外部定义请求,然后在组件内部调用它们。这样可以同时发起两个请求以节省时间,但用户需要等待两个 Promise 都解析完成后才能看到渲染结果。 + +在下面的示例中,`getArtist` 和 `getArtistAlbums` 函数在 `Page` 组件外部定义,然后在组件内部调用,我们等待两个 Promise 解析: + +```tsx filename="app/artist/[username]/page.tsx" switcher +import Albums from './albums' + +async function getArtist(username: string) { + const res = await fetch(`https://api.example.com/artist/${username}`) + return res.json() +} + +async function getArtistAlbums(username: string) { + const res = await fetch(`https://api.example.com/artist/${username}/albums`) + return res.json() +} + +export default async function Page({ + params: { username }, +}: { + params: { username: string } +}) { + // 并行发起两个请求 + const artistData = getArtist(username) + const albumsData = getArtistAlbums(username) + + // 等待 Promise 解析 + const [artist, albums] = await Promise.all([artistData, albumsData]) + + return ( + <> +

{artist.name}

+ + + ) +} +``` + +```jsx filename="app/artist/[username]/page.js" switcher +import Albums from './albums' + +async function getArtist(username) { + const res = await fetch(`https://api.example.com/artist/${username}`) + return res.json() +} + +async function getArtistAlbums(username) { + const res = await fetch(`https://api.example.com/artist/${username}/albums`) + return res.json() +} + +export default async function Page({ params: { username } }) { + // 并行发起两个请求 + const artistData = getArtist(username) + const albumsData = getArtistAlbums(username) + + // 等待 Promise 解析 + const [artist, albums] = await Promise.all([artistData, albumsData]) + + return ( + <> +

{artist.name}

+ + + ) +} +``` + +为了改善用户体验,您可以添加 [Suspense 边界](/docs/app/building-your-application/routing/loading-ui-and-streaming) 来分割渲染工作,并尽快显示部分结果。 + +### 预加载数据 + +防止瀑布流问题的另一种方法是使用预加载模式。您可以选择创建一个 `preload` 函数来进一步优化并行数据获取。通过这种方法,您无需将 Promise 作为 props 传递下去。`preload` 函数也可以使用任何名称,因为它是一种模式,而非 API。 + +```tsx filename="components/Item.tsx" switcher +import { getItem } from '@/utils/get-item' + +export const preload = (id: string) => { + // void 运算符会执行给定表达式并返回 undefined + // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void + void getItem(id) +} +export default async function Item({ id }: { id: string }) { + const result = await getItem(id) + // ... +} +``` + +```jsx filename="components/Item.js" switcher +import { getItem } from '@/utils/get-item' + +export const preload = (id) => { + // void 运算符会执行给定表达式并返回 undefined + // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void + void getItem(id) +} +export default async function Item({ id }) { + const result = await getItem(id) + // ... +} +``` + +```tsx filename="app/item/[id]/page.tsx" switcher +import Item, { preload, checkIsAvailable } from '@/components/Item' + +export default async function Page({ + params: { id }, +}: { + params: { id: string } +}) { + // 开始加载项目数据 + preload(id) + // 执行另一个异步任务 + const isAvailable = await checkIsAvailable() + + return isAvailable ? : null +} +``` + +```jsx filename="app/item/[id]/page.js" switcher +import Item, { preload, checkIsAvailable } from '@/components/Item' + +export default async function Page({ params: { id } }) { + // 开始加载项目数据 + preload(id) + // 执行另一个异步任务 + const isAvailable = await checkIsAvailable() + + return isAvailable ? : null +} +``` + +### 使用 React `cache`、`server-only` 和预加载模式 + +您可以结合 `cache` 函数、预加载模式和 `server-only` 包来创建一个可在整个应用中使用的数据获取工具。 + +```ts filename="utils/get-item.ts" switcher +import { cache } from 'react' +import 'server-only' + +export const preload = (id: string) => { + void getItem(id) +} + +export const getItem = cache(async (id: string) => { + // ... +}) +``` + +```js filename="utils/get-item.js" switcher +import { cache } from 'react' +import 'server-only' + +export const preload = (id) => { + void getItem(id) +} + +export const getItem = cache(async (id) => { + // ... +}) +``` + +通过这种方法,您可以急切地获取数据、缓存响应,并确保数据获取[仅在服务端进行](/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment)。 + +`utils/get-item` 的导出可以被布局、页面或其他组件使用,以控制何时获取项目数据。 + +> **须知:** +> +> - 我们建议使用 [`server-only` 包](/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment) 来确保服务端数据获取函数永远不会在客户端使用。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/03-forms-and-mutations.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/03-forms-and-mutations.mdx new file mode 100644 index 00000000..ab12c47c --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/03-forms-and-mutations.mdx @@ -0,0 +1,983 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:08:02.600Z +title: 表单与数据变更 +nav_title: 表单与数据变更 +description: 学习如何使用 Next.js 处理表单提交与数据变更。 +--- + +{/* 本文档内容在应用路由和页面路由间共享。可使用 `内容` 组件添加页面路由专属内容,共享内容不应包裹在任何组件中 */} + + + +表单让您能够在网页应用中创建和更新数据。Next.js 通过 **API 路由** 提供了强大的表单提交与数据变更处理方案。 + +> **须知:** +> +> - 我们即将推荐 [渐进式迁移](/docs/app/building-your-application/upgrading/app-router-migration) 到应用路由,并使用 [服务端操作 (Server Actions)](/docs/app/building-your-application/data-fetching/forms-and-mutations#how-server-actions-work) 处理表单提交与数据变更。服务端操作允许您定义异步服务端函数,无需手动创建 API 路由即可直接从组件调用。 +> - API 路由 [默认不指定 CORS 头信息](https://developer.mozilla.org/docs/Web/HTTP/CORS),意味着默认仅支持同源请求。 +> - 由于 API 路由在服务端运行,可通过 [环境变量](/docs/pages/building-your-application/configuring/environment-variables) 使用敏感值(如 API 密钥)而不会暴露给客户端,这对应用安全性至关重要。 + + + + + +表单让您能够在网页应用中创建和更新数据。Next.js 通过 **服务端操作 (Server Actions)** 提供了强大的表单提交与数据变更处理方案。 + +
+ 示例 + +- [带加载与错误状态的表单](https://github.com/vercel/next.js/tree/canary/examples/next-forms) + +
+ +## 服务端操作原理 + +使用服务端操作时,您无需手动创建 API 端点,而是直接定义可在组件中调用的异步服务端函数。 + +> **🎥 观看:** 通过应用路由学习表单与数据变更 → [YouTube (10分钟)](https://youtu.be/dDpZfOQBMaU?si=cJZHlUu_jFhCzHUg)。 + +服务端操作可在服务端组件中定义,或从客户端组件调用。在服务端组件中定义操作可使表单在无 JavaScript 环境下正常工作,实现渐进增强。 + +在 `next.config.js` 中启用服务端操作: + +```js filename="next.config.js" +module.exports = { + experimental: { + serverActions: true, + }, +} +``` + +> **须知:** +> +> - 从服务端组件调用服务端操作的表单可在无 JavaScript 环境下运行。 +> - 从客户端组件调用服务端操作的表单会在 JavaScript 未加载时排队提交,优先保证客户端水合。 +> - 服务端操作继承所在页面或布局的 [运行时环境](/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes)。 +> - 服务端操作兼容完全静态的路由(包括使用 ISR 重新验证数据)。 + +## 重新验证缓存数据 + +服务端操作与 Next.js [缓存与重新验证](/docs/app/building-your-application/caching) 架构深度集成。表单提交时,服务端操作可更新缓存数据并重新验证应变更的缓存键。 + +不同于传统应用每个路由只能有一个表单,服务端操作支持每个路由多个操作。此外,表单提交时浏览器无需刷新。在单次网络往返中,Next.js 可同时返回更新后的 UI 和刷新数据。 + +查看下方 [通过服务端操作重新验证数据](#revalidating-data) 的示例。 + +
+ +## 示例 + +### 纯服务端表单 + + + +使用页面路由时,您需要手动创建 API 端点来安全地在服务端变更数据。 + +```ts filename="pages/api/submit.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const data = req.body + const id = await createItem(data) + res.status(200).json({ id }) +} +``` + +```js filename="pages/api/submit.js" switcher +export default function handler(req, res) { + const data = req.body + const id = await createItem(data) + res.status(200).json({ id }) +} +``` + +然后通过事件处理器在客户端调用 API 路由: + +```tsx filename="pages/index.tsx" switcher +import { FormEvent } from 'react' + +export default function Page() { + async function onSubmit(event: FormEvent) { + event.preventDefault() + + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // 如需处理响应 + const data = await response.json() + // ... + } + + return ( +
+ + +
+ ) +} +``` + +```jsx filename="pages/index.jsx" switcher +export default function Page() { + async function onSubmit(event) { + event.preventDefault() + + const formData = new FormData(event.target) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // 如需处理响应 + const data = await response.json() + // ... + } + + return ( +
+ + +
+ ) +} +``` + +
+ + + +要创建纯服务端表单,需在服务端组件中定义服务端操作。操作可内联定义(在函数顶部添加 `"use server"` 指令),或在单独文件中定义(文件顶部添加指令)。 + +```tsx filename="app/page.tsx" switcher +export default function Page() { + async function create(formData: FormData) { + 'use server' + + // 变更数据 + // 重新验证缓存 + } + + return
...
+} +``` + +```jsx filename="app/page.jsx" switcher +export default function Page() { + async function create(formData) { + 'use server' + + // 变更数据 + // 重新验证缓存 + } + + return
...
+} +``` + +> **须知**:`
` 接收 [FormData](https://developer.mozilla.org/docs/Web/API/FormData/FormData) 数据类型。上例中通过 HTML [`form`](https://developer.mozilla.org/docs/Web/HTML/Element/form) 提交的 FormData 可在服务端操作 `create` 中访问。 + +### 重新验证数据 + +服务端操作允许您按需使 [Next.js 缓存](/docs/app/building-your-application/caching) 失效。您可以使用 [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) 使整个路由段失效: + +```ts filename="app/actions.ts" switcher +'use server' + +import { revalidatePath } from 'next/cache' + +export default async function submit() { + await submitForm() + revalidatePath('/') +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { revalidatePath } from 'next/cache' + +export default async function submit() { + await submitForm() + revalidatePath('/') +} +``` + +或使用 [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) 通过缓存标签使特定数据获取失效: + +```ts filename="app/actions.ts" switcher +'use server' + +import { revalidateTag } from 'next/cache' + +export default async function submit() { + await addPost() + revalidateTag('posts') +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { revalidateTag } from 'next/cache' + +export default async function submit() { + await addPost() + revalidateTag('posts') +} +``` + + + +### 重定向 + + + +若要在数据变更后重定向用户,可使用 [`redirect`](/docs/pages/building-your-application/routing/api-routes#response-helpers) 跳转到任意绝对或相对 URL: + +```ts filename="pages/api/submit.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const id = await addPost() + res.redirect(307, `/post/${id}`) +} +``` + +```js filename="pages/api/submit.js" switcher +export default async function handler(req, res) { + const id = await addPost() + res.redirect(307, `/post/${id}`) +} +``` + + + + + +若要在服务端操作完成后重定向用户,可使用 [`redirect`](/docs/app/api-reference/functions/redirect) 跳转到任意绝对或相对 URL: + +```ts filename="app/actions.ts" switcher +'use server' + +import { redirect } from 'next/navigation' +import { revalidateTag } from 'next/cache' + +export default async function submit() { + const id = await addPost() + revalidateTag('posts') // 更新缓存文章 + redirect(`/post/${id}`) // 导航到新路由 +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { redirect } from 'next/navigation' +import { revalidateTag } from 'next/cache' + +export default async function submit() { + const id = await addPost() + revalidateTag('posts') // 更新缓存文章 + redirect(`/post/${id}`) // 导航到新路由 +} +``` + + + +### 表单验证 + +推荐使用 `required` 和 `type="email"` 等 HTML 验证进行基础表单验证。 + +如需高级服务端验证,可使用 [zod](https://zod.dev/) 等模式验证库验证解析后的表单数据结构: + + + +```ts filename="pages/api/submit.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' +import { z } from 'zod' + +const schema = z.object({ + // ... +}) + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const parsed = schema.parse(req.body) + // ... +} +``` + +```js filename="pages/api/submit.js" switcher +import { z } from 'zod' + +const schema = z.object({ + // ... +}) + +export default async function handler(req, res) { + const parsed = schema.parse(req.body) + // ... +} +``` + + + + + +```tsx filename="app/actions.ts" switcher +import { z } from 'zod' + +const schema = z.object({ + // ... +}) + +export default async function submit(formData: FormData) { + const parsed = schema.parse({ + id: formData.get('id'), + }) + // ... +} +``` + +```jsx filename="app/actions.js" switcher +import { z } from 'zod' + +const schema = z.object({ + // ... +}) + +export default async function submit(formData) { + const parsed = schema.parse({ + id: formData.get('id'), + }) + // ... +} +``` + + + +### 显示加载状态 + + + +使用 `useFormStatus` 钩子显示表单在服务端提交时的加载状态。该钩子只能作为使用服务端操作的 `form` 元素的子组件使用。 + +例如以下提交按钮: + +```tsx filename="app/submit-button.tsx" switcher +'use client' + +import { experimental_useFormStatus as useFormStatus } from 'react-dom' + +export function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) +} +``` + +```jsx filename="app/submit-button.jsx" switcher +'use client' + +import { experimental_useFormStatus as useFormStatus } from 'react-dom' + +export function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) +} +``` + +`` 可在包含服务端操作的表单中使用: + +```tsx filename="app/page.tsx" switcher +import { SubmitButton } from '@/app/submit-button' + +export default async function Home() { + return ( + + + + + ) +} +``` + +```jsx filename="app/page.jsx" switcher +import { SubmitButton } from '@/app/submit-button' + +export default async function Home() { + return ( +
+ + + + ) +} +``` + +
+ + + +可使用 React 状态显示表单在服务端提交时的加载状态: + +```tsx filename="pages/index.tsx" switcher +import React, { useState, FormEvent } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + + async function onSubmit(event: FormEvent) { + event.preventDefault() + setIsLoading(true) // 请求开始时设置加载状态 + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // 如需处理响应 + const data = await response.json() + // ... + } catch (error) { + // 如需处理错误 + console.error(error) + } finally { + setIsLoading(false) // 请求完成后取消加载状态 + } + } + + return ( +
+ + +
+ ) +} +``` + +```jsx filename="pages/index.jsx" switcher +import React, { useState } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + + async function onSubmit(event) { + event.preventDefault() + setIsLoading(true) // 请求开始时设置加载状态 + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + // 如需处理响应 + const data = await response.json() + // ... + } catch (error) { + // 如需处理错误 + console.error(error) + } finally { + setIsLoading(false) // 请求完成后取消加载状态 + } + } + + return ( +
+ + +
+ ) +} +``` + +
+ +### 错误处理 + + + +服务端操作 (Server Actions) 也可以返回[可序列化对象](https://developer.mozilla.org/docs/Glossary/Serialization)。例如,您的服务端操作可以处理创建新项目时的错误: + +```ts filename="app/actions.ts" switcher +'use server' + +export async function createTodo(prevState: any, formData: FormData) { + try { + await createItem(formData.get('todo')) + return revalidatePath('/') + } catch (e) { + return { message: 'Failed to create' } + } +} +``` + +```js filename="app/actions.js" switcher +'use server' + +export async function createTodo(prevState, formData) { + try { + await createItem(formData.get('todo')) + return revalidatePath('/') + } catch (e) { + return { message: 'Failed to create' } + } +} +``` + +然后,在客户端组件 (Client Component) 中,您可以读取这个值并显示错误信息。 + +```tsx filename="app/add-form.tsx" switcher +'use client' + +import { experimental_useFormState as useFormState } from 'react-dom' +import { experimental_useFormStatus as useFormStatus } from 'react-dom' +import { createTodo } from '@/app/actions' + +const initialState = { + message: null, +} + +function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) +} + +export function AddForm() { + const [state, formAction] = useFormState(createTodo, initialState) + + return ( +
+ + + +

+ {state?.message} +

+ + ) +} +``` + +```jsx filename="app/add-form.jsx" switcher +'use client' + +import { experimental_useFormState as useFormState } from 'react-dom' +import { experimental_useFormStatus as useFormStatus } from 'react-dom' +import { createTodo } from '@/app/actions' + +const initialState = { + message: null, +} + +function SubmitButton() { + const { pending } = useFormStatus() + + return ( + + ) +} + +export function AddForm() { + const [state, formAction] = useFormState(createTodo, initialState) + + return ( +
+ + + +

+ {state?.message} +

+ + ) +} +``` + +
+ + + +您可以使用 React 状态在表单提交失败时显示错误信息: + +```tsx filename="pages/index.tsx" switcher +import React, { useState, FormEvent } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + async function onSubmit(event: FormEvent) { + event.preventDefault() + setIsLoading(true) + setError(null) // 新请求开始时清除之前的错误 + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + throw new Error('Failed to submit the data. Please try again.') + } + + // 如有需要处理响应 + const data = await response.json() + // ... + } catch (error) { + // 捕获错误信息显示给用户 + setError(error.message) + console.error(error) + } finally { + setIsLoading(false) + } + } + + return ( +
+ {error &&
{error}
} +
+ + +
+
+ ) +} +``` + +```jsx filename="pages/index.jsx" switcher +import React, { useState } from 'react' + +export default function Page() { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + async function onSubmit(event) { + event.preventDefault() + setIsLoading(true) + setError(null) // 新请求开始时清除之前的错误 + + try { + const formData = new FormData(event.currentTarget) + const response = await fetch('/api/submit', { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + throw new Error('Failed to submit the data. Please try again.') + } + + // 如有需要处理响应 + const data = await response.json() + // ... + } catch (error) { + // 捕获错误信息显示给用户 + setError(error.message) + console.error(error) + } finally { + setIsLoading(false) + } + } + + return ( +
+ {error &&
{error}
} +
+ + +
+
+ ) +} +``` + +
+ + + +### 乐观更新 (Optimistic Updates) + +使用 `useOptimistic` 在服务端操作完成前乐观地更新 UI,而无需等待响应: + +```tsx filename="app/page.tsx" switcher +'use client' + +import { experimental_useOptimistic as useOptimistic } from 'react' +import { send } from './actions' + +type Message = { + message: string +} + +export function Thread({ messages }: { messages: Message[] }) { + const [optimisticMessages, addOptimisticMessage] = useOptimistic( + messages, + (state: Message[], newMessage: string) => [ + ...state, + { message: newMessage }, + ] + ) + + return ( +
+ {optimisticMessages.map((m, k) => ( +
{m.message}
+ ))} +
{ + const message = formData.get('message') + addOptimisticMessage(message) + await send(message) + }} + > + + +
+
+ ) +} +``` + +```jsx filename="app/page.jsx" switcher +'use client' + +import { experimental_useOptimistic as useOptimistic } from 'react' +import { send } from './actions' + +export function Thread({ messages }) { + const [optimisticMessages, addOptimisticMessage] = useOptimistic( + messages, + (state, newMessage) => [...state, { message: newMessage }] + ) + + return ( +
+ {optimisticMessages.map((m) => ( +
{m.message}
+ ))} +
{ + const message = formData.get('message') + addOptimisticMessage(message) + await send(message) + }} + > + + +
+
+ ) +} +``` + +
+ +### 设置 Cookies + + + +您可以在 API 路由中使用响应对象的 `setHeader` 方法设置 cookie: + +```ts filename="pages/api/cookie.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') + res.status(200).send('Cookie 已设置。') +} +``` + +```js filename="pages/api/cookie.js" switcher +export default async function handler(req, res) { + res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly') + res.status(200).send('Cookie 已设置。') +} +``` + + + + + +您可以在服务端操作中使用 [`cookies`](/docs/app/api-reference/functions/cookies) 函数设置 cookie: + +```ts filename="app/actions.ts" switcher +'use server' + +import { cookies } from 'next/headers' + +export async function create() { + const cart = await createCart() + cookies().set('cartId', cart.id) +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { cookies } from 'next/headers' + +export async function create() { + const cart = await createCart() + cookies().set('cartId', cart.id) +} +``` + + + +### 读取 Cookies + + + +您可以在 API 路由中使用 [`cookies`](/docs/pages/building-your-application/routing/api-routes#request-helpers) 请求辅助函数读取 cookie: + +```ts filename="pages/api/cookie.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const auth = req.cookies.authorization + // ... +} +``` + +```js filename="pages/api/cookie.js" switcher +export default async function handler(req, res) { + const auth = req.cookies.authorization + // ... +} +``` + + + + + +您可以在服务端操作中使用 [`cookies`](/docs/app/api-reference/functions/cookies) 函数读取 cookie: + +```ts filename="app/actions.ts" switcher +'use server' + +import { cookies } from 'next/headers' + +export async function read() { + const auth = cookies().get('authorization')?.value + // ... +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { cookies } from 'next/headers' + +export async function read() { + const auth = cookies().get('authorization')?.value + // ... +} +``` + + + +### 删除 Cookies + + + +您可以在 API 路由中使用响应对象的 `setHeader` 方法删除 cookie: + +```ts filename="pages/api/cookie.ts" switcher +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') + res.status(200).send('Cookie 已删除。') +} +``` + +```js filename="pages/api/cookie.js" switcher +export default async function handler(req, res) { + res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0') + res.status(200).send('Cookie 已删除。') +} +``` + + + + + +您可以在服务端操作中使用 [`cookies`](/docs/app/api-reference/functions/cookies) 函数删除 cookie: + +```ts filename="app/actions.ts" switcher +'use server' + +import { cookies } from 'next/headers' + +export async function delete() { + cookies().delete('name') + // ... +} +``` + +```js filename="app/actions.js" switcher +'use server' + +import { cookies } from 'next/headers' + +export async function delete() { + cookies().delete('name') + // ... +} +``` + +查看[更多示例](/docs/app/api-reference/functions/cookies#deleting-cookies)了解如何从服务端操作中删除 cookie。 + + diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/index.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/index.mdx new file mode 100644 index 00000000..92e99176 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/02-data-fetching/index.mdx @@ -0,0 +1,6 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:37.414Z +title: 数据获取 +description: 了解如何使用 Next.js 获取、缓存、重新验证和变更数据。 +--- \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/01-server-components.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/01-server-components.mdx new file mode 100644 index 00000000..db6b373f --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/01-server-components.mdx @@ -0,0 +1,138 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:05:38.095Z +title: 服务端组件 (Server Components) +description: 了解如何使用 React 服务端组件 (Server Components) 在服务端渲染部分应用界面。 +related: + description: 了解 Next.js 如何缓存数据及静态渲染结果。 + links: + - app/building-your-application/caching +--- + +React 服务端组件 (Server Components) 允许您编写可在服务端渲染并选择性缓存的用户界面。在 Next.js 中,渲染工作会按路由段进一步拆分以实现流式传输和部分渲染,并提供三种不同的服务端渲染策略: + +- [静态渲染 (Static Rendering)](#static-rendering-default) +- [动态渲染 (Dynamic Rendering)](#dynamic-rendering) +- [流式渲染 (Streaming)](#streaming) + +本文将介绍服务端组件的工作原理、适用场景以及不同的服务端渲染策略。 + +## 服务端渲染的优势 + +在服务端执行渲染工作具有以下优势: + +- **数据获取**:服务端组件允许将数据获取移至更靠近数据源的服务端,通过减少渲染所需数据的获取时间及客户端请求次数来提升性能。 +- **安全性**:服务端组件可确保敏感数据和逻辑(如令牌和 API 密钥)保留在服务端,避免泄露给客户端。 +- **缓存**:服务端渲染结果可被缓存并在后续请求及不同用户间复用,通过减少每次请求的渲染和数据获取量来提升性能并降低成本。 +- **包体积**:服务端组件允许将原本会影响客户端 JavaScript 包体积的大型依赖保留在服务端,这对网络较慢或设备性能较低的用户尤为有益,因为客户端无需为服务端组件下载、解析和执行任何 JavaScript。 +- **初始页面加载与 [首次内容绘制 (FCP)](https://web.dev/fcp/)**:服务端可直接生成 HTML,让用户无需等待客户端下载、解析和执行渲染页面所需的 JavaScript 即可立即查看页面。 +- **搜索引擎优化与社交网络分享**:生成的 HTML 可被搜索引擎爬虫用于索引页面,或被社交网络爬虫用于生成页面预览卡片。 +- **流式传输**:服务端组件允许将渲染工作拆分为多个块,并在就绪后流式传输至客户端,使用户无需等待整个页面在服务端完成渲染即可提前看到部分内容。 + +## 在 Next.js 中使用服务端组件 + +Next.js 默认使用服务端组件,无需额外配置即可自动实现服务端渲染。您也可以根据需要选择使用客户端组件 (Client Components),详见[客户端组件](/docs/app/building-your-application/rendering/client-components)。 + +## 服务端组件如何渲染? + +在服务端,Next.js 使用 React 的 API 来协调渲染工作。渲染任务会被拆分为多个块:按独立路由段和 [Suspense 边界](https://react.dev/reference/react/Suspense)划分。 + +每个块的渲染分为两个步骤: + +1. React 将服务端组件渲染为一种特殊数据格式 —— **React 服务端组件负载 (RSC Payload)**。 +2. Next.js 使用 RSC 负载和客户端组件的 JavaScript 指令在服务端生成 **HTML**。 + +{/* 渲染示意图 */} + +随后在客户端: + +1. HTML 被用于立即展示路由的快速非交互式预览 —— 这仅适用于初始页面加载。 +2. React 服务端组件负载用于协调客户端与服务端组件树,并更新 DOM。 +3. JavaScript 指令用于[水合 (hydrate)](https://react.dev/reference/react-dom/client/hydrateRoot) 客户端组件,使应用具备交互性。 + +> **什么是 React 服务端组件负载 (RSC)?** +> +> RSC 负载是渲染后的 React 服务端组件树的紧凑二进制表示形式,供 React 在客户端更新浏览器 DOM 时使用。RSC 负载包含: +> +> - 服务端组件的渲染结果 +> - 客户端组件应渲染位置的占位符及其 JavaScript 文件引用 +> - 从服务端组件传递给客户端组件的任何 props + +## 服务端渲染策略 + +服务端渲染分为三种子类型:静态渲染、动态渲染和流式渲染。 + +### 静态渲染(默认) + +{/* 静态渲染示意图 */} + +通过静态渲染,路由会在**构建时**或在[数据重新验证](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data)后的后台进行渲染。结果会被缓存并可推送至[内容分发网络 (CDN)](https://developer.mozilla.org/docs/Glossary/CDN),使得渲染结果能在用户和服务器请求间共享。 + +静态渲染适用于路由数据无需用户个性化且可在构建时确定的场景,如静态博客文章或产品页面。 + +### 动态渲染 + +{/* 动态渲染示意图 */} + +通过动态渲染,路由会在**请求时**为每个用户进行渲染。 + +动态渲染适用于路由数据需用户个性化或只能在请求时获取的场景,如 cookies 或 URL 的搜索参数。 + +> **使用缓存数据的动态路由** +> +> 大多数网站的路由并非完全静态或完全动态 —— 而是一个连续区间。例如,电商页面可能使用定期重新验证的缓存产品数据,同时也包含未缓存的个性化用户数据。 +> +> 在 Next.js 中,您可以拥有同时包含缓存和未缓存数据的动态渲染路由。这是因为 RSC 负载与数据是分开缓存的,使您可以选择动态渲染而无需担心在请求时获取所有数据对性能的影响。 +> +> 了解更多关于[全路由缓存](/docs/app/building-your-application/caching#full-route-cache)和[数据缓存](/docs/app/building-your-application/caching#data-cache)的信息。 + +#### 切换至动态渲染 + +在渲染过程中,如果检测到[动态函数](#dynamic-functions)或[未缓存数据请求](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#opting-out-of-data-caching),Next.js 会将整个路由切换为动态渲染。下表总结了动态函数和数据缓存如何影响路由的渲染方式: + +| 动态函数 | 数据 | 路由 | +| -------- | ---------- | ------------------ | +| 无 | 已缓存 | 静态渲染 | +| 有 | 已缓存 | 动态渲染 | +| 无 | 未缓存 | 动态渲染 | +| 有 | 未缓存 | 动态渲染 | + +上表中,要使路由完全静态,所有数据必须已缓存。但您也可以创建同时使用缓存和未缓存数据获取的动态渲染路由。 + +作为开发者,您无需手动选择静态或动态渲染,Next.js 会根据使用的功能和 API 自动为每个路由选择最佳渲染策略。您只需决定何时[缓存或重新验证特定数据](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating),并可以选择[流式传输](#streaming)部分 UI。 + +#### 动态函数 + +动态函数依赖于只能在请求时获取的信息,如用户的 cookies、当前请求头或 URL 的搜索参数。在 Next.js 中,这些动态函数包括: + +- **[`cookies()`](/docs/app/api-reference/functions/cookies) 和 [`headers()`](/docs/app/api-reference/functions/headers)**:在服务端组件中使用这些函数会使整个路由在请求时转为动态渲染。 +- **[`useSearchParams()`](/docs/app/api-reference/functions/use-search-params)**: + - 在客户端组件中使用会跳过静态渲染,改为在客户端渲染直至最近的父级 Suspense 边界。 + - 建议将使用 `useSearchParams()` 的客户端组件包裹在 `` 边界中,这样其上层的客户端组件仍可静态渲染。[示例](/docs/app/api-reference/functions/use-search-params#static-rendering)。 +- **[`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional)**:在[页面](/docs/app/api-reference/file-conventions/page)属性中使用会使页面在请求时转为动态渲染。 + +使用以上任何函数都会使整个路由在请求时转为动态渲染。 + +### 流式渲染 + +流式渲染期间路由段并行化示意图,展示单个块的数据获取、渲染和水合过程。 + +流式渲染允许您逐步从服务端渲染 UI。工作被拆分为多个块,并在就绪后流式传输至客户端。这使得用户无需等待全部内容完成渲染即可立即看到页面的部分内容。 + +客户端部分渲染页面示意图,显示正在流式传输的块的加载状态。 + +流式渲染已内置在 Next.js 应用路由中,这既提升了初始页面加载性能,也优化了依赖较慢数据获取的 UI(否则会阻塞整个路由渲染)。例如产品页面的评论部分。 + +您可以通过 `loading.js` 和 [React Suspense](/docs/app/building-your-application/routing/loading-ui-and-streaming) 组件来启动路由段的流式传输。更多信息请参阅[加载状态与流式传输](/docs/app/building-your-application/routing/loading-ui-and-streaming)章节。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/02-client-components.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/02-client-components.mdx new file mode 100644 index 00000000..4eb6e07b --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/02-client-components.mdx @@ -0,0 +1,108 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:56.783Z +title: 客户端组件 (Client Components) +description: 了解如何使用客户端组件 (Client Components) 在客户端渲染部分应用内容。 +--- + +客户端组件 (Client Components) 允许您编写可在请求时在客户端渲染的交互式 UI。在 Next.js 中,客户端渲染是**可选的**,这意味着您需要明确决定哪些组件应由 React 在客户端渲染。 + +本页将介绍客户端组件 (Client Components) 的工作原理、渲染方式以及适用场景。 + +## 客户端渲染的优势 + +在客户端执行渲染工作具有以下优势: + +- **交互性**:客户端组件 (Client Components) 可以使用状态 (state)、副作用 (effects) 和事件监听器 (event listeners),这意味着它们能即时响应用户操作并更新 UI。 +- **浏览器 API**:客户端组件 (Client Components) 能够访问浏览器 API,例如 [地理位置 API](https://developer.mozilla.org/docs/Web/API/Geolocation_API) 或 [localStorage](https://developer.mozilla.org/docs/Web/API/Window/localStorage),从而支持构建特定场景的 UI。 + +## 在 Next.js 中使用客户端组件 (Client Components) + +要使用客户端组件 (Client Components),您可以在文件顶部(导入语句之前)添加 React 的 [`"use client"` 指令](https://react.dev/reference/react/use-client)。 + +`"use client"` 用于声明服务端组件 (Server Components) 和客户端组件 (Client Components) 模块之间的[边界](/docs/app/building-your-application/rendering#network-boundary)。这意味着在文件中定义 `"use client"` 后,所有导入该文件的模块(包括子组件)都将被视为客户端包的一部分。 + +```tsx filename="app/counter.tsx" highlight={1} switcher +'use client' + +import { useState } from 'react' + +export default function Counter() { + const [count, setCount] = useState(0) + + return ( +
+

You clicked {count} times

+ +
+ ) +} +``` + +```jsx filename="app/counter.js" highlight={1} switcher +'use client' + +import { useState } from 'react' + +export default function Counter() { + const [count, setCount] = useState(0) + + return ( +
+

You clicked {count} times

+ +
+ ) +} +``` + +下图展示了嵌套组件的情况——如果在 `toggle.js` 中使用 `onClick` 和 `useState` 但未定义 `"use client"` 指令会导致错误。这是因为默认情况下组件会在服务端渲染,而这些 API 在服务端不可用。通过在 `toggle.js` 中定义 `"use client"` 指令,您可以告知 React 在客户端渲染该组件及其子组件,因为相关 API 在客户端可用。 + +Use Client Directive and Network Boundary + +> **定义多个 `use client` 入口点**: +> +> 您可以在 React 组件树中定义多个 "use client" 入口点。这允许将应用拆分为多个客户端包(或分支)。 +> +> 但并非每个需要在客户端渲染的组件都必须定义 `"use client"`。一旦定义边界后,所有子组件和导入的模块都将被视为客户端包的一部分。 + +## 客户端组件 (Client Components) 如何渲染? + +在 Next.js 中,客户端组件 (Client Components) 的渲染方式取决于请求类型:完整页面加载(首次访问应用或浏览器刷新触发的页面重载)还是后续导航。 + +### 完整页面加载 + +为优化初始页面加载,Next.js 会使用 React 的 API 在服务端为客户端组件 (Client Components) 和服务端组件 (Server Components) 渲染静态 HTML 预览。这意味着用户首次访问应用时,可以立即看到页面内容,而无需等待客户端下载、解析和执行客户端组件 (Client Components) 的 JavaScript 包。 + +在服务端: + +1. React 将服务端组件 (Server Components) 渲染为特殊数据格式——**React 服务端组件负载 (RSC Payload)**,其中包含对客户端组件 (Client Components) 的引用。 +2. Next.js 使用 RSC Payload 和客户端组件 (Client Components) 的 JavaScript 指令在服务端渲染路由的 **HTML**。 + +然后在客户端: + +1. 使用 HTML 立即显示路由的非交互式快速初始预览。 +2. 使用 React 服务端组件负载 (RSC Payload) 协调客户端组件 (Client Components) 和服务端组件 (Server Components) 的组件树,并更新 DOM。 +3. 使用 JavaScript 指令对客户端组件 (Client Components) 进行[水合 (hydrate)](https://react.dev/reference/react-dom/client/hydrateRoot),使其 UI 具备交互性。 + +> **什么是水合 (hydration)?** +> +> 水合 (hydration) 是为 DOM 附加事件监听器使其从静态 HTML 变为交互式的过程。底层通过 React 的 [`hydrateRoot`](https://react.dev/reference/react-dom/client/hydrateRoot) API 实现。 + +### 后续导航 + +在后续导航中,客户端组件 (Client Components) 完全在客户端渲染,不依赖服务端渲染的 HTML。 + +这意味着客户端组件 (Client Components) 的 JavaScript 包会被下载并解析。当包就绪后,React 将使用 RSC Payload 协调客户端组件 (Client Components) 和服务端组件 (Server Components) 的树结构,并更新 DOM。 + +## 返回服务端环境 + +有时,在声明 `"use client"` 边界后,您可能需要返回服务端环境。例如为了减少客户端包体积、在服务端获取数据,或使用仅限服务端的 API。 + +即使代码理论上嵌套在客户端组件 (Client Components) 中,您仍可以通过交叉使用客户端组件 (Client Components)、服务端组件 (Server Components) 和服务端操作 (Server Actions) 将代码保留在服务端。更多信息请参阅[组合模式](/docs/app/building-your-application/rendering/composition-patterns)页面。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/03-composition-patterns.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/03-composition-patterns.mdx new file mode 100644 index 00000000..346014df --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/03-composition-patterns.mdx @@ -0,0 +1,556 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:06:45.530Z +title: 服务端与客户端组合模式 +nav_title: 组合模式 +description: 关于使用服务端组件 (Server Components) 和客户端组件 (Client Components) 的推荐模式。 +--- + +构建 React 应用时,您需要考虑应用的哪些部分应在服务端或客户端渲染。本文介绍使用服务端组件和客户端组件时的一些推荐组合模式。 + +## 何时使用服务端与客户端组件? + +以下是服务端组件和客户端组件不同使用场景的快速参考: + +| 您需要实现的功能 | 服务端组件 | 客户端组件 | +| -------------------------------------------------------------------------------- | ------------------- | ------------------- | +| 获取数据 | | | +| 直接访问后端资源 | | | +| 在服务端保存敏感信息(访问令牌、API 密钥等) | | | +| 将大型依赖保留在服务端 / 减少客户端 JavaScript | | | +| 添加交互性和事件监听器(`onClick()`、`onChange()` 等) | | | +| 使用状态和生命周期 Effects(`useState()`、`useReducer()`、`useEffect()` 等) | | | +| 使用仅限浏览器的 API | | | +| 使用依赖于状态、Effects 或浏览器 API 的自定义钩子 | | | +| 使用 [React 类组件](https://react.dev/reference/react/Component) | | | + +## 服务端组件模式 + +在选择客户端渲染之前,您可能希望在服务端执行某些操作,例如获取数据或访问数据库和后端服务。 + +以下是使用服务端组件时的常见模式: + +### 组件间共享数据 + +在服务端获取数据时,有时需要在不同组件间共享数据。例如,某个布局和页面可能依赖相同的数据。 + +您无需使用 [React Context](https://react.dev/learn/passing-data-deeply-with-context)(服务端不可用)或通过 props 传递数据,可以使用 [`fetch`](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-server-with-fetch) 或 React 的 `cache` 函数在需要数据的组件中获取相同数据,而无需担心重复请求。这是因为 React 扩展了 `fetch` 来自动记忆数据请求,当 `fetch` 不可用时可以使用 `cache` 函数。 + +了解更多关于 React 中的 [记忆化 (memoization)](/docs/app/building-your-application/caching#request-memoization)。 + +### 防止服务端代码泄露到客户端环境 + +由于 JavaScript 模块可以在服务端和客户端组件间共享,原本仅应在服务端运行的代码可能会意外进入客户端。 + +例如以下数据获取函数: + +```ts filename="lib/data.ts" switcher +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +```js filename="lib/data.js" switcher +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +乍看之下,`getData` 似乎在服务端和客户端都能工作。但此函数包含 `API_KEY`,其设计初衷是仅在服务端执行。 + +由于环境变量 `API_KEY` 没有 `NEXT_PUBLIC_` 前缀,它是只能在服务端访问的私有变量。为防止环境变量泄露到客户端,Next.js 会用空字符串替换私有环境变量。 + +因此,尽管 `getData()` 可以在客户端导入和执行,但不会按预期工作。虽然将变量公开可使函数在客户端工作,但您可能不希望将敏感信息暴露给客户端。 + +为防止服务端代码意外在客户端使用,我们可以使用 `server-only` 包,在开发者意外将这些模块导入客户端组件时提供构建时错误。 + +要使用 `server-only`,首先安装包: + +```bash filename="Terminal" +npm install server-only +``` + +然后将包导入包含服务端代码的模块: + +```js filename="lib/data.js" +import 'server-only' + +export async function getData() { + const res = await fetch('https://external-service.com/data', { + headers: { + authorization: process.env.API_KEY, + }, + }) + + return res.json() +} +``` + +现在,任何导入 `getData()` 的客户端组件都会收到构建时错误,说明此模块只能用于服务端。 + +对应的 `client-only` 包可用于标记包含仅限客户端代码的模块——例如访问 `window` 对象的代码。 + +### 使用第三方包和 Provider + +由于服务端组件是 React 的新特性,生态系统中的第三方包和 provider 刚开始为使用客户端特性的组件(如 `useState`、`useEffect` 和 `createContext`)添加 `"use client"` 指令。 + +目前,许多来自 `npm` 的使用客户端特性的组件尚未添加该指令。这些第三方组件在客户端组件中可以正常工作(因为它们有 `"use client"` 指令),但在服务端组件中无法工作。 + +例如,假设您安装了假设的 `acme-carousel` 包,其中包含 `` 组件。该组件使用了 `useState`,但尚未添加 `"use client"` 指令。 + +如果在客户端组件中使用 ``,它会按预期工作: + +```tsx filename="app/gallery.tsx" switcher +'use client' + +import { useState } from 'react' +import { Carousel } from 'acme-carousel' + +export default function Gallery() { + let [isOpen, setIsOpen] = useState(false) + + return ( +
+ + + {/* 可以工作,因为 Carousel 在客户端组件中使用 */} + {isOpen && } +
+ ) +} +``` + +```jsx filename="app/gallery.js" switcher +'use client' + +import { useState } from 'react' +import { Carousel } from 'acme-carousel' + +export default function Gallery() { + let [isOpen, setIsOpen] = useState(false) + + return ( +
+ + + {/* 可以工作,因为 Carousel 在客户端组件中使用 */} + {isOpen && } +
+ ) +} +``` + +但如果直接在服务端组件中使用,会出现错误: + +```tsx filename="app/page.tsx" switcher +import { Carousel } from 'acme-carousel' + +export default function Page() { + return ( +
+

View pictures

+ + {/* 错误:`useState` 不能在服务端组件中使用 */} + +
+ ) +} +``` + +```jsx filename="app/page.js" switcher +import { Carousel } from 'acme-carousel' + +export default function Page() { + return ( +
+

View pictures

+ + {/* 错误:`useState` 不能在服务端组件中使用 */} + +
+ ) +} +``` + +这是因为 Next.js 不知道 `` 使用了客户端特性。 + +要解决此问题,您可以将依赖客户端特性的第三方组件包装在您自己的客户端组件中: + +```tsx filename="app/carousel.tsx" switcher +'use client' + +import { Carousel } from 'acme-carousel' + +export default Carousel +``` + +```jsx filename="app/carousel.js" switcher +'use client' + +import { Carousel } from 'acme-carousel' + +export default Carousel +``` + +现在,您可以直接在服务端组件中使用 ``: + +```tsx filename="app/page.tsx" switcher +import Carousel from './carousel' + +export default function Page() { + return ( +
+

View pictures

+ + {/* 可以工作,因为 Carousel 是客户端组件 */} + +
+ ) +} +``` + +```jsx filename="app/page.js" switcher +import Carousel from './carousel' + +export default function Page() { + return ( +
+

View pictures

+ + {/* 可以工作,因为 Carousel 是客户端组件 */} + +
+ ) +} +``` + +我们预计您不需要包装大多数第三方组件,因为您可能会在客户端组件中使用它们。但有一个例外是 provider,因为它们依赖 React 状态和上下文,并且通常需要在应用的根目录使用。[在下方了解更多关于第三方上下文 provider 的内容](#using-context-providers)。 + +#### 使用上下文 Provider + +上下文 Provider 通常渲染在应用根目录附近以共享全局关注点,例如当前主题。由于 [React 上下文](https://react.dev/learn/passing-data-deeply-with-context) 在服务端组件中不受支持,尝试在应用根目录创建上下文会导致错误: + +```tsx filename="app/layout.tsx" switcher +import { createContext } from 'react' + +// createContext 在服务端组件中不受支持 +export const ThemeContext = createContext({}) + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import { createContext } from 'react' + +// createContext 在服务端组件中不受支持 +export const ThemeContext = createContext({}) + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ) +} +``` + +要解决此问题,在客户端组件中创建上下文并渲染其 provider: + +```tsx filename="app/theme-provider.tsx" switcher +'use client' + +import { createContext } from 'react' + +export const ThemeContext = createContext({}) + +export default function ThemeProvider({ children }) { + return {children} +} +``` + +```jsx filename="app/theme-provider.js" switcher +'use client' + +import { createContext } from 'react' + +export const ThemeContext = createContext({}) + +export default function ThemeProvider({ children }) { + return {children} +} +``` + +现在您的服务端组件可以直接渲染 provider,因为它已被标记为客户端组件: + +```tsx filename="app/layout.tsx" switcher +import ThemeProvider from './theme-provider' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {children} + + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import ThemeProvider from './theme-provider' + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ) +} +``` + +在根目录渲染 provider 后,应用中所有其他客户端组件都可以消费此上下文。 + +> **须知**:您应尽可能在组件树深层渲染 provider——注意 `ThemeProvider` 只包裹 `{children}` 而不是整个 `` 文档。这使得 Next.js 更容易优化服务端组件的静态部分。 + +#### 给库作者的建议 + +同样,为其他开发者创建可消费包的库作者可以使用 `"use client"` 指令标记包的客户端入口点。这使得包用户可以直接将包组件导入其服务端组件,而无需创建包装边界。 + +您可以通过 [在组件树深层使用 'use client'](#moving-client-components-down-the-tree) 来优化包,使导入的模块成为服务端组件模块图的一部分。 + +值得注意的是,某些打包工具可能会移除 `"use client"` 指令。您可以在 [React Wrap Balancer](https://github.com/shuding/react-wrap-balancer/blob/main/tsup.config.ts#L10-L13) 和 [Vercel Analytics](https://github.com/vercel/analytics/blob/main/packages/web/tsup.config.js#L26-L30) 仓库中找到如何配置 esbuild 以包含 `"use client"` 指令的示例。 + +## 客户端组件 + +### 将客户端组件移至组件树深层 + +为减少客户端 JavaScript 包大小,我们建议将客户端组件移至组件树深层。 + +例如,您可能有一个包含静态元素(如徽标、链接等)和使用了状态的交互式搜索栏的布局。 + +无需将整个布局设为客户端组件,将交互逻辑移至客户端组件(如 ``),保持布局为服务端组件。这意味着您无需将布局的所有组件 JavaScript 发送到客户端。 + +```tsx filename="app/layout.tsx" switcher +// SearchBar 是客户端组件 +import SearchBar from './searchbar' +// Logo 是服务端组件 +import Logo from './logo' + +// 默认情况下 Layout 是服务端组件 +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + +
{children}
+ + ) +} +``` + +```jsx filename="app/layout.js" switcher +// SearchBar 是客户端组件 +import SearchBar from './searchbar' +// Logo 是服务端组件 +import Logo from './logo' + +// 默认情况下 Layout 是服务端组件 +export default function Layout({ children }) { + return ( + <> + +
{children}
+ + ) +} +``` + +### 从服务端组件向客户端组件传递 props(序列化) + +如果在服务端组件获取数据,您可能希望将数据作为 props 传递给客户端组件。从服务端传递给客户端组件的 props 需要能被 React [序列化 (serializable)](https://developer.mozilla.org/docs/Glossary/Serialization)。 + +如果您的客户端组件依赖不可序列化的数据,可以通过 [第三方库在客户端获取数据](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-client-with-third-party-libraries) 或在服务端通过 [路由处理器 (Route Handler)](/docs/app/building-your-application/routing/route-handlers) 获取。 + +## 服务端组件与客户端组件交错渲染 + +在交错使用服务端组件 (Server Components) 和客户端组件 (Client Components) 时,将您的 UI 可视化为组件树会很有帮助。从作为服务端组件的 [根布局 (root layout)](/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required) 开始,您可以通过添加 `"use client"` 指令在客户端渲染特定的组件子树。 + +{/* 图表 - 交错渲染 */} + +在这些客户端子树中,您仍然可以嵌套服务端组件或调用服务端操作 (Server Actions),但需要注意以下几点: + +- 在请求-响应生命周期中,您的代码会从服务端移动到客户端。如果您需要在客户端访问服务端的数据或资源,您将向服务端发起**新的**请求——而不是来回切换。 +- 当向服务端发起新请求时,所有服务端组件会首先渲染,包括嵌套在客户端组件内部的那些。渲染结果(RSC Payload)将包含对客户端组件位置的引用。然后,在客户端上,React 会使用 RSC Payload 将服务端和客户端组件协调成单一的树结构。 + +{/* 图表 */} + +- 由于客户端组件是在服务端组件之后渲染的,因此您不能将服务端组件导入到客户端组件模块中(因为这会需要向服务端发起新的请求)。相反,您可以将服务端组件作为 `props` 传递给客户端组件。请参阅下面的[不支持的模式](#unsupported-pattern-importing-server-components-into-client-components)和[支持的模式](#supported-pattern-passing-server-components-to-client-components-as-props)部分。 + +### 不支持的模式:将服务端组件导入客户端组件 + +以下模式不被支持。您不能将服务端组件导入到客户端组件中: + +```tsx filename="app/client-component.tsx" switcher highlight={4,17} +'use client' + +// 不能将服务端组件导入到客户端组件中 +import ServerComponent from './Server-Component' + +export default function ClientComponent({ + children, +}: { + children: React.ReactNode +}) { + const [count, setCount] = useState(0) + + return ( + <> + + + + + ) +} +``` + +```jsx filename="app/client-component.js" switcher highlight={3,13} +'use client' + +// 不能将服务端组件导入到客户端组件中 +import ServerComponent from './Server-Component' + +export default function ClientComponent({ children }) { + const [count, setCount] = useState(0) + + return ( + <> + + + + + ) +} +``` + +### 支持的模式:将服务端组件作为 Props 传递给客户端组件 + +以下模式是支持的。您可以将服务端组件作为 prop 传递给客户端组件。 + +一种常见的模式是使用 React 的 `children` prop 在您的客户端组件中创建一个“插槽”。 + +在下面的示例中,`` 接受一个 `children` prop: + +```tsx filename="app/client-component.tsx" switcher highlight={6,15} +'use client' + +import { useState } from 'react' + +export default function ClientComponent({ + children, +}: { + children: React.ReactNode +}) { + const [count, setCount] = useState(0) + + return ( + <> + + {children} + + ) +} +``` + +```jsx filename="app/client-component.js" switcher highlight={5,12} +'use client' + +import { useState } from 'react' + +export default function ClientComponent({ children }) { + const [count, setCount] = useState(0) + + return ( + <> + + + {children} + + ) +} +``` + +`` 并不知道 `children` 最终会被服务端组件的结果填充。`` 的唯一职责是决定 `children` 最终被放置的**位置**。 + +在父级服务端组件中,您可以同时导入 `` 和 ``,并将 `` 作为 `` 的子组件传递: + +```tsx filename="app/page.tsx" highlight={11} switcher +// 这种模式是可行的: +// 您可以将服务端组件作为子组件或 prop 传递给客户端组件 +import ClientComponent from './client-component' +import ServerComponent from './server-component' + +// Next.js 中的页面默认是服务端组件 +export default function Page() { + return ( + + + + ) +} +``` + +```jsx filename="app/page.js" highlight={11} switcher +// 这种模式是可行的: +// 您可以将服务端组件作为子组件或 prop 传递给客户端组件 +import ClientComponent from './client-component' +import ServerComponent from './server-component' + +// Next.js 中的页面默认是服务端组件 +export default function Page() { + return ( + + + + ) +} +``` + +通过这种方法,`` 和 `` 是解耦的,可以独立渲染。在这种情况下,子组件 `` 可以在服务端渲染,远早于 `` 在客户端渲染。 + +> **须知:** +> +> - “提升内容”的模式已被用于避免在父组件重新渲染时重新渲染嵌套的子组件。 +> - 您不仅限于使用 `children` prop。您可以使用任何 prop 来传递 JSX。 diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx new file mode 100644 index 00000000..fca495a5 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/04-edge-and-nodejs-runtimes.mdx @@ -0,0 +1,86 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:50.709Z +title: Edge 与 Node.js 运行时 +description: 了解 Next.js 中可切换的运行时环境(Edge 和 Node.js)。 +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加仅适用于页面路由的内容。所有共享内容不应包裹在任何组件中。*/} + +在 Next.js 中,运行时 (runtime) 指的是代码执行期间可用的库、API 和通用功能的集合。 + +在服务端,您的应用代码可以在两种运行时环境中渲染: + +- **Node.js 运行时**(默认)可访问所有 Node.js API 及生态系统中兼容的包。 +- **Edge 运行时**基于 [Web API](/docs/app/api-reference/edge)。 + +## 运行时差异 + +选择运行时需考虑诸多因素。下表简要展示了主要区别。如需更深入的分析,请查看下文各节。 + +| | Node | 无服务 (Serverless) | Edge | +| --------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------- | ---------------- | +| [冷启动](https://vercel.com/docs/concepts/get-started/compute#cold-and-hot-boots?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) | / | ~250毫秒 | 即时 | +| [HTTP 流式传输](/docs/app/building-your-application/routing/loading-ui-and-streaming) | 支持 | 支持 | 支持 | +| IO 操作 | 全部 | 全部 | 仅限 `fetch` | +| 可扩展性 | / | 高 | 最高 | +| 安全性 | 常规 | 高 | 高 | +| 延迟 | 常规 | 低 | 最低 | +| npm 包支持 | 全部 | 全部 | 有限子集 | +| [静态渲染](/docs/app/building-your-application/rendering/server-components#static-rendering-default) | 支持 | 支持 | 不支持 | +| [动态渲染](/docs/app/building-your-application/rendering/server-components#dynamic-rendering) | 支持 | 支持 | 支持 | +| [通过 `fetch` 重新验证数据](/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#revalidating-data) | 支持 | 支持 | 支持 | + +### Edge 运行时 + +在 Next.js 中,轻量级的 Edge 运行时是 Node.js API 的一个子集。 + +如果您需要以低延迟交付动态个性化内容,且函数体量小、逻辑简单,Edge 运行时是理想选择。其速度优势源于资源的最小化使用,但这也会在许多场景中形成限制。 + +例如,在 Vercel 上 Edge 运行时执行的代码[体积必须介于 1 MB 到 4 MB 之间](https://vercel.com/docs/concepts/limits/overview#edge-middleware-and-edge-functions-size),此限制包含导入的包、字体和文件,具体数值取决于您的部署基础设施。 + +### Node.js 运行时 + +使用 Node.js 运行时可以访问所有 Node.js API 及依赖这些 API 的 npm 包。但其启动速度不如使用 Edge 运行时的路由。 + +将 Next.js 应用部署到 Node.js 服务器需要自行管理、扩展和配置基础设施。您也可以考虑将应用部署到 Vercel 等无服务平台,这些平台会为您处理这些事务。 + +### 无服务 Node.js + +如果您需要能处理比 Edge 运行时更复杂计算负载的可扩展方案,无服务架构是理想选择。例如在 Vercel 上,无服务函数的总体代码大小限制为[50MB](https://vercel.com/docs/concepts/limits/overview#serverless-function-size),包含导入的包、字体和文件。 + +相比使用 [Edge](https://vercel.com/docs/concepts/functions/edge-functions) 的路由,其劣势在于无服务函数可能需要数百毫秒的冷启动时间才能开始处理请求。根据站点流量情况,这种现象可能频繁发生,因为函数并非总是处于"热"状态。 + + + +## 示例 + +### 路由段运行时配置 + +您可以为 Next.js 应用中的单个路由段指定运行时。具体方法是[声明并导出一个名为 `runtime` 的变量](/docs/app/api-reference/file-conventions/route-segment-config)。该变量必须是字符串,且值只能为 `'nodejs'` 或 `'edge'` 运行时。 + +以下示例展示了一个页面路由段导出 `runtime` 值为 `'edge'` 的情况: + +```tsx filename="app/page.tsx" switcher +export const runtime = 'edge' // 可选值: 'nodejs'(默认)| 'edge' +``` + +```jsx filename="app/page.js" switcher +export const runtime = 'edge' // 可选值: 'nodejs'(默认)| 'edge' +``` + +您也可以在布局层级定义 `runtime`,这将使该布局下的所有路由都运行在 edge 运行时: + +```tsx filename="app/layout.tsx" switcher +export const runtime = 'edge' // 可选值: 'nodejs'(默认)| 'edge' +``` + +```jsx filename="app/layout.js" switcher +export const runtime = 'edge' // 可选值: 'nodejs'(默认)| 'edge' +``` + +如果未设置路由段运行时,将默认使用 `nodejs` 运行时。如果您不打算更改 Node.js 运行时,则无需使用 `runtime` 选项。 + + + +> 完整 API 列表请参阅 [Node.js 文档](https://nodejs.org/docs/latest/api/) 和 [Edge 文档](/docs/app/api-reference/edge)。根据部署基础设施的不同,两种运行时也都支持[流式传输](/docs/app/building-your-application/routing/loading-ui-and-streaming)。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/index.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/index.mdx new file mode 100644 index 00000000..b6be17f9 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/03-rendering/index.mdx @@ -0,0 +1,80 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:06.543Z +title: 渲染 +description: 了解 Next.js 中不同渲染环境、策略和运行时的区别。 +--- + +渲染将您编写的代码转换为用户界面。React 和 Next.js 允许您创建混合式 Web 应用,其中部分代码可以在服务端或客户端渲染。本节将帮助您理解这些渲染环境、策略和运行时之间的区别。 + +## 基础概念 + +首先,熟悉以下三个基础的 Web 概念会很有帮助: + +- 代码执行的[环境](#渲染环境):服务端和客户端 +- 用户访问或与应用交互时触发的[请求-响应生命周期](#请求-响应生命周期) +- 分离服务端和客户端代码的[网络边界](#网络边界) + +### 渲染环境 + +Web 应用可以在两种环境中渲染:客户端和服务端。 + +客户端与服务端环境 + +- **客户端** 指用户设备上的浏览器,它向服务器发送应用代码请求,并将服务器响应转换为用户界面 +- **服务端** 指数据中心存储应用代码的计算机,接收客户端请求并返回相应响应 + +传统上,开发者在编写服务端和客户端代码时需要使用不同语言(如 JavaScript、PHP)和框架。通过 React,开发者可以使用**相同的语言**(JavaScript)和**相同的框架**(如 Next.js 或您选择的框架)。这种灵活性让您无需切换上下文就能为两种环境编写代码。 + +但每个环境都有其特定的能力和限制,因此服务端和客户端代码并不总是相同。某些操作(如数据获取或管理用户状态)更适合在特定环境中执行。 + +理解这些差异是有效使用 React 和 Next.js 的关键。我们将在[服务端组件](/docs/app/building-your-application/rendering/server-components)和[客户端组件](/docs/app/building-your-application/rendering/client-components)页面详细讨论这些差异和使用场景,现在让我们继续夯实基础。 + +### 请求-响应生命周期 + +广义上说,所有网站都遵循相同的**请求-响应生命周期**: + +1. **用户操作**:用户与 Web 应用交互,可能是点击链接、提交表单或在浏览器地址栏直接输入 URL +2. **HTTP 请求**:客户端向服务器发送 [HTTP](https://developer.mozilla.org/docs/Web/HTTP) 请求,包含请求资源、使用的方法(如 `GET`、`POST`)及必要数据等信息 +3. **服务端处理**:服务器处理请求并返回相应资源,此过程可能涉及路由、数据获取等步骤 +4. **HTTP 响应**:处理完成后,服务器向客户端发送 HTTP 响应,包含状态码(表明请求是否成功)和请求的资源(如 HTML、CSS、JavaScript、静态资源等) +5. **客户端解析**:客户端解析资源并渲染用户界面 +6. **用户操作**:界面渲染完成后,用户可与之交互,整个循环重新开始 + +构建混合式 Web 应用的关键在于决定如何在生命周期中分配工作,以及如何设置网络边界。 + +### 网络边界 + +在 Web 开发中,**网络边界**是分离不同环境的概念分界线,例如客户端与服务端之间,或服务端与数据存储之间。 + +{/* 图示:网络边界 */} + +在 React 中,您可以根据实际需求自由选择客户端-服务端网络边界的位置。 + +底层实现上,工作被分为两部分:**客户端模块图**和**服务端模块图**。服务端模块图包含所有在服务端渲染的组件,客户端模块图包含所有在客户端渲染的组件。 + +{/* 图示:客户端与服务端模块图 */} + +将模块图视为应用中文件依赖关系的可视化表示会很有帮助。 + +{/* 例如,如果在服务端有 `Page.jsx` 文件导入了 `Button.jsx` 文件,模块图会呈现如下结构:- 图示 - */} + +您可以使用 React 的 `"use client"` 约定来定义边界。还有 `"use server"` 约定,用于告知 React 在客户端时在服务端执行某些计算工作。 + +## 构建混合应用 + +在这些环境中工作时,将应用代码流视为**单向流动**会很有帮助。换句话说,在响应过程中,您的应用代码沿单一方向流动:从服务端到客户端。 + +{/* 图示:响应流 */} + +如果需要从客户端访问服务端,您应该向服务端发送**新的**请求,而不是复用同一请求。这有助于理解组件应在何处渲染以及网络边界的位置。 + +实践中,这种模式鼓励开发者先考虑在服务端执行哪些操作,再将结果发送至客户端使应用具备交互性。 + +当我们了解如何[在组件树中交错使用客户端和服务端组件](/docs/app/building-your-application/rendering/composition-patterns)时,这个概念会更加清晰。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/04-caching/index.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/04-caching/index.mdx new file mode 100644 index 00000000..07a80a06 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/04-caching/index.mdx @@ -0,0 +1,603 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:07:50.834Z +title: Next.js 中的缓存机制 +nav_title: 缓存 +description: 概述 Next.js 中的缓存机制。 +--- + +Next.js 通过缓存渲染工作和数据请求来提升应用性能并降低成本。本文将深入探讨 Next.js 的缓存机制、可用的配置 API 以及它们之间的交互方式。 + +> **须知**:本文帮助您理解 Next.js 的底层工作原理,但并非高效使用 Next.js 的必备知识。Next.js 的大部分缓存启发式规则由您的 API 使用情况决定,并提供了零配置或最小配置即可获得最佳性能的默认设置。 + +## 概览 + +以下是不同缓存机制及其用途的高级概览: + +| 机制 | 缓存内容 | 位置 | 目的 | 持续时间 | +| ------------------------------------- | ------------------------ | ------ | --------------------------------------------- | ------------------------------- | +| [请求记忆化](#request-memoization) | 函数返回值 | 服务端 | 在 React 组件树中复用数据 | 单次请求生命周期 | +| [数据缓存](#data-cache) | 数据 | 服务端 | 跨用户请求和部署存储数据 | 持久化(可重新验证) | +| [全路由缓存](#full-route-cache) | HTML 和 RSC 负载 | 服务端 | 降低渲染成本并提升性能 | 持久化(可重新验证) | +| [路由缓存](#router-cache) | RSC 负载 | 客户端 | 减少导航时的服务端请求 | 用户会话或基于时间 | + +默认情况下,Next.js 会尽可能多地缓存以提升性能并降低成本。这意味着路由会**静态渲染**且数据请求会被**缓存**,除非您主动选择退出。下图展示了默认缓存行为:当路由在构建时静态渲染以及首次访问静态路由时的情况。 + +展示 Next.js 中四种机制默认缓存行为的示意图,包含构建时和首次访问路由时的 HIT、MISS 和 SET 状态。 + +缓存行为会根据路由是静态还是动态渲染、数据是否缓存以及请求是首次访问还是后续导航而变化。根据您的使用场景,可以为单个路由和数据请求配置缓存行为。 + +## 请求记忆化 + +React 扩展了 [`fetch` API](#fetch) 以自动**记忆化**具有相同 URL 和选项的请求。这意味着您可以在 React 组件树的多个位置调用相同的 fetch 函数,而实际上只会执行一次。 + +去重后的 Fetch 请求 + +例如,如果您需要在路由中跨组件使用相同数据(如在布局、页面和多个组件中),您不必在组件树顶部获取数据并通过 props 向下传递。相反,您可以在需要数据的组件中直接获取,而无需担心因多次网络请求相同数据而影响性能。 + +```tsx filename="app/example.tsx" switcher +async function getItem() { + // `fetch` 函数会自动记忆化,结果会被缓存 + const res = await fetch('https://.../item/1') + return res.json() +} + +// 此函数被调用两次,但仅第一次会执行 +const item = await getItem() // cache MISS + +// 第二次调用可以发生在路由的任何位置 +const item = await getItem() // cache HIT +``` + +```jsx filename="app/example.js" switcher +async function getItem() { + // `fetch` 函数会自动记忆化,结果会被缓存 + const res = await fetch('https://.../item/1') + return res.json() +} + +// 此函数被调用两次,但仅第一次会执行 +const item = await getItem() // cache MISS + +// 第二次调用可以发生在路由的任何位置 +const item = await getItem() // cache HIT +``` + +**请求记忆化工作原理** + +展示 React 渲染期间 fetch 记忆化工作原理的示意图。 + +- 在渲染路由时,首次调用特定请求时,其结果不在内存中,因此是缓存 `MISS`。 +- 函数将被执行,从外部源获取数据,结果存储在内存中。 +- 同一渲染过程中对该请求的后续调用将是缓存 `HIT`,数据直接从内存返回而无需执行函数。 +- 路由渲染完成后,内存会被"重置",所有请求记忆化条目被清除。 + +> **须知**: +> +> - 请求记忆化是 React 特性,而非 Next.js 特性。此处提及是为了展示它与其他缓存机制的交互。 +> - 记忆化仅适用于 `fetch` 请求的 `GET` 方法。 +> - 记忆化仅适用于 React 组件树,这意味着: +> - 它适用于 `generateMetadata`、`generateStaticParams`、布局、页面和其他服务端组件中的 `fetch` 请求。 +> - 不适用于路由处理器中的 `fetch` 请求,因为它们不属于 React 组件树。 +> - 对于不适用 `fetch` 的情况(如某些数据库客户端、CMS 客户端或 GraphQL 客户端),可以使用 [React `cache` 函数](#react-cache-function) 来记忆化函数。 + +### 持续时间 + +缓存持续到服务端请求生命周期结束,即 React 组件树完成渲染时。 + +### 重新验证 + +由于记忆化不跨服务端请求共享且仅在渲染期间有效,因此无需重新验证。 + +### 选择退出 + +要在 `fetch` 请求中退出记忆化,可以向请求传递 `AbortController` 的 `signal`。 + +```js filename="app/example.js" +const { signal } = new AbortController() +fetch(url, { signal }) +``` + +## 数据缓存 + +Next.js 内置了数据缓存,可在**服务端请求**和**部署**间持久化数据获取结果。这是通过扩展原生 `fetch` API 实现的,允许每个服务端请求设置自己的持久化缓存语义。 + +> **须知**:在浏览器中,`fetch` 的 `cache` 选项表示请求如何与浏览器的 HTTP 缓存交互;在 Next.js 中,`cache` 选项表示服务端请求如何与服务器的数据缓存交互。 + +默认情况下,使用 `fetch` 的数据请求会被**缓存**。您可以使用 `fetch` 的 [`cache`](#fetch-optionscache) 和 [`next.revalidate`](#fetch-optionsnextrevalidate) 选项来配置缓存行为。 + +**数据缓存工作原理** + +展示缓存和非缓存 fetch 请求如何与数据缓存交互的示意图。缓存请求存储在数据缓存中并被记忆化,非缓存请求从数据源获取,不存储在数据缓存中,但被记忆化。 + +- 渲染期间首次调用 `fetch` 请求时,Next.js 会检查数据缓存中是否有缓存响应。 +- 如果找到缓存响应,则立即返回并[记忆化](#request-memoization)。 +- 如果未找到缓存响应,则向数据源发起请求,结果存储在数据缓存中并被记忆化。 +- 对于非缓存数据(如 `{ cache: 'no-store' }`),总是从数据源获取结果并被记忆化。 +- 无论数据是否缓存,请求都会被记忆化以避免在 React 渲染过程中对相同数据发起重复请求。 + +> **数据缓存与请求记忆化的区别** +> +> 虽然两种缓存机制都通过重用缓存数据提升性能,但数据缓存在请求和部署间持久化,而记忆化仅持续单次请求的生命周期。 +> +> 通过记忆化,我们减少了同一渲染过程中需要跨越网络边界(从渲染服务器到数据缓存服务器,如 CDN 或边缘网络)或数据源(如数据库或 CMS)的**重复**请求数量。通过数据缓存,我们减少了对原始数据源的请求数量。 + +### 持续时间 + +除非重新验证或选择退出,否则数据缓存在请求和部署间持久化。 + +### 重新验证 + +缓存数据可通过两种方式重新验证: + +- **基于时间的重新验证**:在一定时间间隔后重新验证数据。适用于变化不频繁且时效性要求不高的数据。 +- **按需重新验证**:基于事件(如表单提交)重新验证数据。可按标签或路径一次性重新验证一组数据。适用于需要尽快展示最新数据的场景(如无头 CMS 内容更新时)。 + +#### 基于时间的重新验证 + +要按时间间隔重新验证数据,可使用 `fetch` 的 `next.revalidate` 选项设置资源的缓存生命周期(秒)。 + +```js +// 最多每小时重新验证一次 +fetch('https://...', { next: { revalidate: 3600 } }) +``` + +或者,可以使用[路由段配置选项](#segment-config-options)来配置段内所有 `fetch` 请求,或在不使用 `fetch` 的情况下配置。 + +**基于时间的重新验证工作原理** + +展示基于时间重新验证工作原理的示意图,重新验证周期后,首次请求返回过期数据,随后数据被重新验证。 + +- 首次调用带 `revalidate` 的 `fetch` 请求时,数据从外部数据源获取并存储在数据缓存中。 +- 在指定时间范围内(如 60 秒)的任何请求都将返回缓存数据。 +- 时间范围过后,下一次请求仍返回缓存(现已过期)数据。 + - Next.js 将在后台触发数据重新验证。 + - 成功获取数据后,Next.js 会用新数据更新数据缓存。 + - 如果后台重新验证失败,则保留原数据不变。 + +这与 [**stale-while-revalidate**](https://web.dev/stale-while-revalidate/) 行为类似。 + +#### 按需重新验证 + +可通过路径 ([`revalidatePath`](#revalidatepath)) 或缓存标签 ([`revalidateTag`](#fetch-optionsnexttags-and-revalidatetag)) 按需重新验证数据。 + +**按需重新验证工作原理** + +展示按需重新验证工作原理的示意图,重新验证请求后数据缓存更新为新数据。 + +- 首次调用 `fetch` 请求时,数据从外部数据源获取并存储在数据缓存中。 +- 触发按需重新验证时,相应的缓存条目将从缓存中清除。 + - 这与基于时间的重新验证不同,后者会在获取新数据前保留过期数据。 +- 下次请求时,将再次出现缓存 `MISS`,数据将从外部数据源获取并存储在数据缓存中。 + +### 选择退出 + +对于单个数据获取,可通过将 [`cache`](#fetch-optionscache) 选项设为 `no-store` 来退出缓存。这意味着每次调用 `fetch` 都会获取数据。 + +```jsx +// 为单个 `fetch` 请求退出缓存 +fetch(`https://...`, { cache: 'no-store' }) +``` + +或者,也可以使用[路由段配置选项](#segment-config-options)为特定路由段退出缓存。这将影响段内所有数据请求,包括第三方库。 + +```jsx +// 为路由段内所有数据请求退出缓存 +export const dynamic = 'force-dynamic' +``` + +> **Vercel 数据缓存** +> +> 如果您的 Next.js 应用部署在 Vercel 上,建议阅读 [Vercel 数据缓存](https://vercel.com/docs/infrastructure/data-cache) 文档以了解 Vercel 特有功能。 + +## 全路由缓存 + +> **相关术语**: +> +> 您可能会看到**自动静态优化**、**静态站点生成**或**静态渲染**等术语互换使用,均指在构建时渲染和缓存应用路由的过程。 + +Next.js 会在构建时自动渲染和缓存路由。这项优化允许您直接提供缓存路由,而无需为每个请求在服务端渲染,从而实现更快的页面加载。 + +要理解全路由缓存的工作原理,了解 React 如何处理渲染以及 Next.js 如何缓存结果很有帮助: + +### 1. 服务端的 React 渲染 + +在服务端,Next.js 使用 React 的 API 编排渲染。渲染工作被拆分为多个块:按单个路由段和 Suspense 边界划分。 + +每个块的渲染分为两步: + +1. React 将服务端组件渲染为一种专为流式传输优化的特殊数据格式,称为 **React 服务端组件负载**。 +2. Next.js 使用 React 服务端组件负载和客户端组件 JavaScript 指令在服务端渲染 **HTML**。 + +这意味着我们不必等待所有内容渲染完成即可缓存工作或发送响应。相反,我们可以在工作完成时流式传输响应。 + +> **什么是 React 服务端组件负载?** +> +> React 服务端组件负载是渲染后的 React 服务端组件树的紧凑二进制表示。React 在客户端使用它来更新浏览器的 DOM。React 服务端组件负载包含: +> +> - 服务端组件的渲染结果 +> - 客户端组件应渲染位置的占位符及其 JavaScript 文件的引用 +> - 从服务端组件传递给客户端组件的任何 props +> +> 了解更多,请参阅[服务端组件](/docs/app/building-your-application/rendering/server-components)文档。 + +### 2. Next.js 服务端缓存(全路由缓存) + +全路由缓存的默认行为,展示 React 服务端组件负载和 HTML 如何为静态渲染路由在服务端缓存。 + +Next.js 的默认行为是在服务端缓存路由的渲染结果(React 服务端组件负载和 HTML)。这适用于构建时静态渲染的路由或在重新验证期间的路由。 + +### 3. 客户端的 React 水合与协调 + +在请求时,客户端会: + +1. 使用 HTML 快速展示客户端和服务端组件的非交互式初始预览。 +2. 使用 React 服务端组件负载协调客户端和已渲染的服务端组件树,并更新 DOM。 +3. 使用 JavaScript 指令[水合](https://react.dev/reference/react-dom/client/hydrateRoot)客户端组件,使应用可交互。 + +### 4. Next.js 客户端缓存(路由缓存) + +React 服务端组件负载存储在客户端[路由缓存](#router-cache)中——这是一个按单个路由段分割的独立内存缓存。路由缓存通过存储先前访问的路由和预取未来路由来提升导航体验。 + +### 5. 后续导航 + +在后续导航或预取过程中,Next.js 会检查路由缓存 (Router Cache) 中是否已存储 React 服务端组件负载 (React Server Components Payload)。若存在,则会跳过向服务器发送新请求。 + +如果路由段未在缓存中,Next.js 将从服务器获取 React 服务端组件负载,并在客户端填充路由缓存。 + +### 静态与动态渲染 + +路由是否在构建时被缓存取决于其采用静态渲染 (Static Rendering) 还是动态渲染 (Dynamic Rendering)。静态路由默认会被缓存,而动态路由则在请求时渲染且不被缓存。 + +下图展示了静态与动态渲染路由的区别,以及缓存与未缓存数据的差异: + +静态与动态渲染对全路由缓存 (Full Route Cache) 的影响。静态路由在构建时或数据重新验证后被缓存,而动态路由从不缓存 + +了解更多关于[静态与动态渲染](/docs/app/building-your-application/rendering/server-components#server-rendering-strategies)的内容。 + +### 持续时间 + +默认情况下,全路由缓存是持久化的。这意味着渲染输出会在用户请求间持续缓存。 + +### 失效机制 + +有两种方式可以使全路由缓存失效: + +- **[数据重新验证 (Revalidating Data)](/docs/app/building-your-application/caching#revalidating)**:重新验证[数据缓存 (Data Cache)](#data-cache) 会触发服务端组件重新渲染,并缓存新的渲染输出,从而使路由缓存失效。 +- **重新部署**:与跨部署持久化的数据缓存不同,全路由缓存会在新部署时被清除。 + +### 退出机制 + +您可以通过以下方式退出全路由缓存(即针对每个传入请求动态渲染组件): + +- **使用[动态函数 (Dynamic Function)](#dynamic-functions)**:这将使路由退出全路由缓存,并在请求时动态渲染。数据缓存仍可被使用。 +- **使用路由段配置选项 `dynamic = 'force-dynamic'` 或 `revalidate = 0`**:这会跳过全路由缓存和数据缓存。意味着每次传入请求时都会在服务端渲染组件并获取数据。路由缓存仍会生效,因为它是客户端缓存。 +- **退出[数据缓存 (Data Cache)](#data-cache)**:如果路由包含未被缓存的 `fetch` 请求,该路由将退出全路由缓存。针对该特定 `fetch` 请求的数据会在每次传入请求时获取。其他未退出缓存的 `fetch` 请求仍会被保存在数据缓存中。这允许混合使用缓存与非缓存数据。 + +## 路由缓存 + +> **相关术语:** +> +> 路由缓存 (Router Cache) 可能被称为**客户端缓存 (Client-side Cache)** 或**预取缓存 (Prefetch Cache)**。其中**预取缓存**特指预取的路由段,而**客户端缓存**指整个路由缓存,包含已访问和预取的路由段。 +> 此缓存专用于 Next.js 和服务端组件,与浏览器的 [bfcache](https://web.dev/bfcache/) 不同,尽管效果相似。 + +Next.js 拥有一个内存中的客户端缓存,用于在用户会话期间存储按路由段分割的 React 服务端组件负载。这被称为路由缓存。 + +**路由缓存工作原理** + +路由缓存在静态与动态路由中的工作方式,展示初始导航与后续导航的 MISS 和 HIT 状态。 + +当用户在路由间导航时,Next.js 会缓存已访问的路由段,并[预取](/docs/app/building-your-application/routing/linking-and-navigating#1-prefetching)用户可能导航到的路由(基于视窗中的 `` 组件)。 + +这会为用户带来更优的导航体验: + +- 即时前进/后退导航,因为已访问路由被缓存;快速导航到新路由,得益于预取和[部分渲染 (Partial Rendering)](/docs/app/building-your-application/routing/linking-and-navigating#3-partial-rendering)。 +- 导航间无需整页刷新,且 React 状态与浏览器状态得以保留。 + +> **路由缓存与全路由缓存的区别**: +> +> 路由缓存临时在浏览器中存储 React 服务端组件负载(用户会话期间),而全路由缓存在服务器端持久化存储 React 服务端组件负载和 HTML(跨用户请求)。 +> +> 全路由缓存仅缓存静态渲染的路由,而路由缓存同时适用于静态和动态渲染的路由。 + +### 持续时间 + +缓存存储在浏览器的临时内存中。两个因素决定路由缓存的持续时间: + +- **会话 (Session)**:缓存在导航间持续存在,但页面刷新时会被清除。 +- **自动失效周期 (Automatic Invalidation Period)**:单个路由段的缓存会在特定时间后自动失效。持续时间取决于路由是[静态](/docs/app/building-your-application/rendering/server-components#static-rendering-default)还是[动态](/docs/app/building-your-application/rendering/server-components#dynamic-rendering)渲染: + - **动态渲染**:30 秒 + - **静态渲染**:5 分钟 + +页面刷新会清除**所有**缓存的路由段,而自动失效周期仅影响自上次访问或创建后的单个路由段。 + +通过为动态渲染的路由添加 `prefetch={true}` 或调用 `router.prefetch`,您可以选择将缓存时间延长至 5 分钟。 + +### 失效机制 + +有两种方式可以使路由缓存失效: + +- 在**服务端操作 (Server Action)** 中: + - 按路径 ([`revalidatePath`](/docs/app/api-reference/functions/revalidatePath)) 或缓存标签 ([`revalidateTag`](/docs/app/api-reference/functions/revalidateTag)) 按需重新验证数据 + - 使用 [`cookies.set`](/docs/app/api-reference/functions/cookies#cookiessetname-value-options) 或 [`cookies.delete`](/docs/app/api-reference/functions/cookies#deleting-cookies) 会使路由缓存失效,以防止使用 cookie 的路由(如身份验证)过期。 +- 调用 [`router.refresh`](/docs/app/api-reference/functions/use-router) 会使路由缓存失效,并为当前路由向服务器发起新请求。 + +### 退出机制 + +无法完全退出路由缓存。 + +您可以通过将 `` 组件的 `prefetch` 属性设为 `false` 来退出**预取**。但这仍会临时存储路由段 30 秒,以实现嵌套路由段(如标签栏)间的即时导航或前进/后退导航。已访问的路由仍会被缓存。 + +## 缓存交互 + +配置不同缓存机制时,理解它们之间的交互方式至关重要: + +### 数据缓存与全路由缓存 + +- 重新验证或退出数据缓存**会**使全路由缓存失效,因为渲染输出依赖于数据。 +- 使全路由缓存失效或退出全路由缓存**不会**影响数据缓存。您可以动态渲染一个同时包含缓存和非缓存数据的路由。这在页面大部分使用缓存数据,但部分组件依赖需实时获取的数据时非常有用。您可以动态渲染,而无需担心重新获取所有数据对性能的影响。 + +### 数据缓存与客户端路由缓存 + +- 在[路由处理器 (Route Handler)](/docs/app/building-your-application/routing/route-handlers) 中重新验证数据缓存**不会**立即使路由缓存失效,因为路由处理器不与特定路由绑定。这意味着路由缓存将继续提供之前的负载,直到强制刷新或自动失效周期结束。 +- 要立即使数据缓存和路由缓存失效,您可以在[服务端操作 (Server Action)](/docs/app/building-your-application/data-fetching/forms-and-mutations) 中使用 [`revalidatePath`](#revalidatepath) 或 [`revalidateTag`](#fetch-optionsnexttags-and-revalidatetag)。 + +## API + +下表概述了不同 Next.js API 对缓存的影响: + +| API | 路由缓存 | 全路由缓存 | 数据缓存 | React 缓存 | +| ----------------------------------------------------------------------- | ---------------------- | --------------------- | --------------------- | ----------- | +| [``](#link) | 缓存 | | | | +| [`router.prefetch`](#routerprefetch) | 缓存 | | | | +| [`router.refresh`](#routerrefresh) | 重新验证 | | | | +| [`fetch`](#fetch) | | | 缓存 | 缓存 | +| [`fetch` `options.cache`](#fetch-optionscache) | | | 缓存或退出 | | +| [`fetch` `options.next.revalidate`](#fetch-optionsnextrevalidate) | | 重新验证 | 重新验证 | | +| [`fetch` `options.next.tags`](#fetch-optionsnexttags-and-revalidatetag) | | 缓存 | 缓存 | | +| [`revalidateTag`](#fetch-optionsnexttags-and-revalidatetag) | 重新验证(服务端操作) | 重新验证 | 重新验证 | | +| [`revalidatePath`](#revalidatepath) | 重新验证(服务端操作) | 重新验证 | 重新验证 | | +| [`const revalidate`](#segment-config-options) | | 重新验证或退出 | 重新验证或退出 | | +| [`const dynamic`](#segment-config-options) | | 缓存或退出 | 缓存或退出 | | +| [`cookies`](#cookies) | 重新验证(服务端操作) | 退出 | | | +| [`headers`, `useSearchParams`, `searchParams`](#dynamic-functions) | | 退出 | | | +| [`generateStaticParams`](#generatestaticparams) | | 缓存 | | | +| [`React.cache`](#react-cache-function) | | | | 缓存 | +| [`unstable_cache`](#unstable_cache) (即将推出) | | | | | + +### `` + +默认情况下,`` 组件会自动从全路由缓存预取路由,并将 React 服务端组件负载添加到路由缓存。 + +要禁用预取,可将 `prefetch` 属性设为 `false`。但这不会永久跳过缓存,用户访问路由时仍会在客户端缓存该路由段。 + +了解更多关于 [`` 组件](/docs/app/api-reference/components/link)的内容。 + +### `router.prefetch` + +`useRouter` 钩子的 `prefetch` 选项可用于手动预取路由。这会将 React 服务端组件负载添加到路由缓存。 + +查看 [`useRouter` 钩子](/docs/app/api-reference/functions/use-router) API 参考。 + +### `router.refresh` + +`useRouter` 钩子的 `refresh` 选项可用于手动刷新路由。这会完全清除路由缓存,并为当前路由向服务器发起新请求。`refresh` 不会影响数据缓存或全路由缓存。 + +渲染结果将在客户端协调,同时保留 React 状态和浏览器状态。 + +查看 [`useRouter` 钩子](/docs/app/api-reference/functions/use-router) API 参考。 + +### `fetch` + +通过 `fetch` 返回的数据会自动缓存在数据缓存中。 + +```jsx +// 默认缓存。`force-cache` 是默认选项,可省略。 +fetch(`https://...`, { cache: 'force-cache' }) +``` + +查看 [`fetch` API 参考](/docs/app/api-reference/functions/fetch) 获取更多选项。 + +### `fetch options.cache` + +您可以通过将 `cache` 选项设为 `no-store` 来退出单个 `fetch` 请求的数据缓存: + +```jsx +// 退出缓存 +fetch(`https://...`, { cache: 'no-store' }) +``` + +由于渲染输出依赖于数据,使用 `cache: 'no-store'` 还会跳过使用该 `fetch` 请求的路由的全路由缓存。即该路由会在每次请求时动态渲染,但同一路由中仍可包含其他缓存的数据请求。 + +查看 [`fetch` API 参考](/docs/app/api-reference/functions/fetch) 获取更多选项。 + +### `fetch options.next.revalidate` + +您可以使用 `fetch` 的 `next.revalidate` 选项设置单个 `fetch` 请求的重新验证周期(秒)。这将重新验证数据缓存,进而重新验证全路由缓存。将获取新数据,并在服务端重新渲染组件。 + +```jsx +// 最多 1 小时后重新验证 +fetch(`https://...`, { next: { revalidate: 3600 } }) +``` + +查看 [`fetch` API 参考](/docs/app/api-reference/functions/fetch) 获取更多选项。 + +### `fetch options.next.tags` 与 `revalidateTag` + +Next.js 拥有一个缓存标签系统,用于细粒度的数据缓存和重新验证。 + +1. 使用 `fetch` 或 `unstable_cache` 时,您可以选择用一个或多个标签标记缓存条目。 +2. 然后,您可以调用 `revalidateTag` 清除与该标签关联的缓存条目。 + +例如,您可以在获取数据时设置标签: + +```jsx +// 使用标签缓存数据 +fetch(`https://...`, { next: { tags: ['a', 'b', 'c'] } }) +``` + +然后,调用 `revalidateTag` 并传入标签以清除缓存条目: + +```jsx +// 重新验证具有特定标签的条目 +revalidateTag('a') +``` + +根据您的需求,可以在两个位置使用 `revalidateTag`: + +1. [路由处理器 (Route Handlers)](/docs/app/building-your-application/routing/route-handlers) - 在第三方事件(如 webhook)响应中重新验证数据。这不会立即使路由缓存失效,因为路由处理器不与特定路由绑定。 +2. [服务端操作 (Server Actions)](/docs/app/building-your-application/data-fetching/forms-and-mutations) - 在用户操作(如表单提交)后重新验证数据。这将立即使相关路由的路由缓存失效。 + +### `revalidatePath` + +`revalidatePath` 允许您手动重新验证数据 **并** 在单次操作中重新渲染特定路径下的路由段。调用 `revalidatePath` 方法会重新验证数据缓存 (Data Cache),进而使完整路由缓存 (Full Route Cache) 失效。 + +```jsx +revalidatePath('/') +``` + +根据您的需求,可以在以下两种场景中使用 `revalidatePath`: + +1. [路由处理器 (Route Handlers)](/docs/app/building-your-application/routing/route-handlers) —— 用于响应第三方事件(如 Webhook)时重新验证数据。 +2. [服务器操作 (Server Actions)](/docs/app/building-your-application/data-fetching/forms-and-mutations) —— 在用户交互(如表单提交、点击按钮)后重新验证数据。 + +更多信息请参阅 [`revalidatePath` API 参考文档](/docs/app/api-reference/functions/revalidatePath)。 + +> **`revalidatePath`** 与 **`router.refresh`** 的区别: +> +> 调用 `router.refresh` 会清除路由缓存 (Router Cache),并在服务端重新渲染路由段,但不会使数据缓存 (Data Cache) 或完整路由缓存 (Full Route Cache) 失效。 +> +> 两者的区别在于:`revalidatePath` 会清空数据缓存和完整路由缓存,而 `router.refresh()` 作为客户端 API 不会改变数据缓存和完整路由缓存的状态。 + +### 动态函数 (Dynamic Functions) + +`cookies`、`headers`、`useSearchParams` 和 `searchParams` 都是依赖运行时请求信息的动态函数。使用这些函数会使路由退出完整路由缓存 (Full Route Cache),即该路由将转为动态渲染。 + +#### `cookies` + +在服务器操作 (Server Action) 中使用 `cookies.set` 或 `cookies.delete` 会使路由缓存 (Router Cache) 失效,从而防止使用 cookie 的路由变得过时(例如反映身份验证状态的变化)。 + +参阅 [`cookies` API 参考文档](/docs/app/api-reference/functions/cookies)。 + +### 路由段配置选项 (Segment Config Options) + +路由段配置选项可用于覆盖默认设置,或在无法使用 `fetch` API 时(如使用数据库客户端或第三方库)进行配置。 + +以下路由段配置选项会使数据缓存 (Data Cache) 和完整路由缓存 (Full Route Cache) 失效: + +- `const dynamic = 'force-dynamic'` +- `const revalidate = 0` + +更多选项请参阅 [路由段配置文档](/docs/app/api-reference/file-conventions/route-segment-config)。 + +### `generateStaticParams` + +对于[动态路由 (dynamic segments)](/docs/app/building-your-application/routing/dynamic-routes)(如 `app/blog/[slug]/page.js`),由 `generateStaticParams` 提供的路径会在构建时被缓存到完整路由缓存 (Full Route Cache) 中。在请求时,Next.js 也会在首次访问时缓存那些构建时未知的路径。 + +您可以通过在路由段中使用 `export const dynamicParams = false` 选项来禁用请求时的缓存。启用此配置后,只有 `generateStaticParams` 提供的路径会被响应,其他路由将返回 404 或匹配(对于[通配路由 (catch-all routes)](/docs/app/building-your-application/routing/dynamic-routes#catch-all-segments) 的情况)。 + +参阅 [`generateStaticParams` API 参考文档](/docs/app/api-reference/functions/generate-static-params)。 + +### React `cache` 函数 + +React `cache` 函数允许您对函数的返回值进行记忆化 (memoize),从而在多次调用同一函数时只执行一次。 + +由于 `fetch` 请求会自动记忆化,您无需再用 React `cache` 进行包装。但在不适合使用 `fetch` API 的场景下(如某些数据库客户端、CMS 客户端或 GraphQL 客户端),可以使用 `cache` 手动记忆化数据请求。 + +```tsx filename="utils/get-item.ts" switcher +import { cache } from 'react' +import db from '@/lib/db' + +export const getItem = cache(async (id: string) => { + const item = await db.item.findUnique({ id }) + return item +}) +``` + +```jsx filename="utils/get-item.js" switcher +import { cache } from 'react' +import db from '@/lib/db' + +export const getItem = cache(async (id) => { + const item = await db.item.findUnique({ id }) + return item +}) +``` + +### `unstable_cache` + +`unstable_cache` 是一个实验性 API,用于在不适合使用 `fetch` API 时(如使用数据库客户端、CMS 客户端或 GraphQL)向数据缓存 (Data Cache) 添加值。 + +```jsx +import { unstable_cache } from 'next/cache' + +export default async function Page() { + const cachedData = await unstable_cache( + async () => { + const data = await db.query('...') + return data + }, + ['cache-key'], + { + tags: ['a', 'b', 'c'], + revalidate: 10, + } + )() +} +``` + +> **警告**:此 API 正在开发中,我们不建议在生产环境中使用。此处列出是为了展示数据缓存的未来发展方向。 diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/01-css-modules.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/01-css-modules.mdx new file mode 100644 index 00000000..26c0f0a9 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/01-css-modules.mdx @@ -0,0 +1,297 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:36.342Z +title: CSS 模块 +description: 使用 CSS 模块为 Next.js 应用程序添加样式。 +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加特定于页面路由的内容。任何共享内容不应包裹在组件中。*/} + + + +
+ 示例 + +- [基础 CSS 示例](https://github.com/vercel/next.js/tree/canary/examples/basic-css) + +
+ +
+ +Next.js 内置支持使用 `.module.css` 扩展名的 CSS 模块。 + +CSS 模块通过自动创建唯一的类名来实现 CSS 的局部作用域。这允许您在不同的文件中使用相同的类名而无需担心冲突。此特性使得 CSS 模块成为包含组件级 CSS 的理想方式。 + +## 示例 + + +CSS 模块可以导入到 `app` 目录下的任何文件中: + +```tsx filename="app/dashboard/layout.tsx" switcher +import styles from './styles.module.css' + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode +}) { + return
{children}
+} +``` + +```jsx filename="app/dashboard/layout.js" switcher +import styles from './styles.module.css' + +export default function DashboardLayout({ children }) { + return
{children}
+} +``` + +```css filename="app/dashboard/styles.module.css" +.dashboard { + padding: 24px; +} +``` + +
+ + + +例如,考虑 `components/` 文件夹中的一个可重用 `Button` 组件: + +首先,创建包含以下内容的 `components/Button.module.css`: + +```css filename="Button.module.css" +/* +您无需担心 .error {} 会与其他 `.css` 或 `.module.css` 文件冲突! +*/ +.error { + color: white; + background-color: red; +} +``` + +然后,创建 `components/Button.js`,导入并使用上述 CSS 文件: + +```jsx filename="components/Button.js" +import styles from './Button.module.css' + +export function Button() { + return ( + + ) +} +``` + + + +CSS 模块是一个_可选功能_,**仅对具有 `.module.css` 扩展名的文件启用**。 +常规的 `` 样式表和全局 CSS 文件仍然受支持。 + +在生产环境中,所有 CSS 模块文件将自动合并为**多个经过压缩和代码拆分**的 `.css` 文件。 +这些 `.css` 文件代表了应用程序中的热执行路径,确保为应用程序加载最少数量的 CSS。 + +## 全局样式 + + +全局样式可以导入到 `app` 目录下的任何布局、页面或组件中。 + +> **须知**:这与 `pages` 目录不同,在 `pages` 目录中,您只能在 `_app.js` 文件中导入全局样式。 + +例如,考虑一个名为 `app/global.css` 的样式表: + +```css +body { + padding: 20px 20px 60px; + max-width: 680px; + margin: 0 auto; +} +``` + +在根布局 (`app/layout.js`) 中,导入 `global.css` 样式表以将样式应用到应用程序中的每个路由: + +```tsx filename="app/layout.tsx" switcher +// 这些样式将应用到应用程序中的每个路由 +import './global.css' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +// 这些样式将应用到应用程序中的每个路由 +import './global.css' + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + + + + + +要向应用程序添加样式表,请在 `pages/_app.js` 中导入 CSS 文件。 + +例如,考虑以下名为 `styles.css` 的样式表: + +```css filename="styles.css" +body { + font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica', + 'Arial', sans-serif; + padding: 20px 20px 60px; + max-width: 680px; + margin: 0 auto; +} +``` + +如果尚未存在,请创建一个 [`pages/_app.js` 文件](/docs/pages/building-your-application/routing/custom-app)。 +然后,[`import`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) `styles.css` 文件。 + +```jsx filename="pages/_app.js" +import '../styles.css' + +// 新的 `pages/_app.js` 文件中需要此默认导出。 +export default function MyApp({ Component, pageProps }) { + return +} +``` + +这些样式 (`styles.css`) 将应用到应用程序中的所有页面和组件。 +由于样式表的全局性质,为避免冲突,您**只能在 [`pages/_app.js`](/docs/pages/building-your-application/routing/custom-app) 中导入它们**。 + +在开发环境中,以这种方式表达样式表允许您在编辑时热重载样式——这意味着您可以保持应用程序状态。 + +在生产环境中,所有 CSS 文件将自动合并为一个压缩后的 `.css` 文件。CSS 的合并顺序将与 CSS 导入到 `_app.js` 文件的顺序相匹配。特别注意包含自身 CSS 的导入 JS 模块;JS 模块的 CSS 将按照与导入 CSS 文件相同的排序规则进行合并。例如: + +```jsx +import '../styles.css' +// ErrorBoundary 的 CSS 依赖于 styles.css 中的全局 CSS, +// 因此我们在 styles.css 之后导入它。 +import ErrorBoundary from '../components/ErrorBoundary' + +export default function MyApp({ Component, pageProps }) { + return ( + + + + ) +} +``` + + + +## 外部样式表 + + + +外部包发布的样式表可以导入到 `app` 目录中的任何位置,包括并置的组件: + +```tsx filename="app/layout.tsx" switcher +import 'bootstrap/dist/css/bootstrap.css' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import 'bootstrap/dist/css/bootstrap.css' + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +> **须知**:外部样式表必须直接从 npm 包导入或下载并与您的代码库并置。您不能使用 ``。 + + + + + +Next.js 允许您从 JavaScript 文件导入 CSS 文件。 +这是可能的,因为 Next.js 扩展了 [`import`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) 的概念,使其不仅限于 JavaScript。 + +### 从 `node_modules` 导入样式 + +自 Next.js **9.5.4** 起,允许从 `node_modules` 导入 CSS 文件到应用程序的任何位置。 + +对于全局样式表,如 `bootstrap` 或 `nprogress`,您应该在 `pages/_app.js` 中导入文件。 +例如: + +```jsx filename="pages/_app.js" +import 'bootstrap/dist/css/bootstrap.css' + +export default function MyApp({ Component, pageProps }) { + return +} +``` + +对于导入第三方组件所需的 CSS,您可以在组件中进行。例如: + +```jsx filename="components/example-dialog.js" +import { useState } from 'react' +import { Dialog } from '@reach/dialog' +import VisuallyHidden from '@reach/visually-hidden' +import '@reach/dialog/styles.css' + +function ExampleDialog(props) { + const [showDialog, setShowDialog] = useState(false) + const open = () => setShowDialog(true) + const close = () => setShowDialog(false) + + return ( +
+ + + +

你好。我是一个对话框

+
+
+ ) +} +``` + +
+ +## 附加功能 + +Next.js 包含以下附加功能以提升样式编写的体验: + +- 使用 `next dev` 本地运行时,本地样式表(无论是全局还是 CSS 模块)将利用[快速刷新](/docs/architecture/fast-refresh)功能,在保存编辑时立即反映更改。 +- 使用 `next build` 构建生产环境时,CSS 文件将被捆绑为更少的压缩 `.css` 文件,以减少获取样式所需的网络请求数量。 +- 如果禁用 JavaScript,在生产构建 (`next start`) 中仍会加载样式。然而,`next dev` 仍需要 JavaScript 以启用[快速刷新](/docs/architecture/fast-refresh)。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/02-tailwind-css.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/02-tailwind-css.mdx new file mode 100644 index 00000000..07f7a539 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/02-tailwind-css.mdx @@ -0,0 +1,183 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:07.199Z +title: Tailwind CSS +description: 使用 Tailwind CSS 为您的 Next.js 应用添加样式。 +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加特定于页面路由的内容。任何共享内容不应包裹在组件中。*/} + + + +
+ 示例 + +- [使用 Tailwind CSS](https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss) + +
+ +
+ +[Tailwind CSS](https://tailwindcss.com/) 是一个实用优先的 CSS 框架,与 Next.js 配合使用效果极佳。 + +## 安装 Tailwind + +安装 Tailwind CSS 相关包并运行 `init` 命令生成 `tailwind.config.js` 和 `postcss.config.js` 文件: + +```bash filename="终端" +npm install -D tailwindcss postcss autoprefixer +npx tailwindcss init -p +``` + +## 配置 Tailwind + +在 `tailwind.config.js` 中,添加需要使用 Tailwind CSS 类名的文件路径: + +```js filename="tailwind.config.js" +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './app/**/*.{js,ts,jsx,tsx,mdx}', // 注意新增了 `app` 目录 + './pages/**/*.{js,ts,jsx,tsx,mdx}', + './components/**/*.{js,ts,jsx,tsx,mdx}', + + // 如果使用 `src` 目录: + './src/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: {}, + }, + plugins: [], +} +``` + +无需修改 `postcss.config.js`。 + + + +## 导入样式 + +将 [Tailwind CSS 指令](https://tailwindcss.com/docs/functions-and-directives#directives) 添加到应用的 [全局样式表](/docs/app/building-your-application/styling/css-modules#global-styles) 中,Tailwind 将使用这些指令注入生成的样式,例如: + +```css filename="app/globals.css" +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +在 [根布局](/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required) (`app/layout.tsx`) 中导入 `globals.css` 样式表,将样式应用到应用中的每个路由。 + +```tsx filename="app/layout.tsx" switcher +import type { Metadata } from 'next' + +// 这些样式将应用到应用中的每个路由 +import './globals.css' + +export const metadata: Metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +// 这些样式将应用到应用中的每个路由 +import './globals.css' + +export const metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +} + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +## 使用类名 + +安装 Tailwind CSS 并添加全局样式后,您可以在应用中使用 Tailwind 的实用类。 + +```tsx filename="app/page.tsx" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +```jsx filename="app/page.js" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +
+ + + +## 导入样式 + +将 [Tailwind CSS 指令](https://tailwindcss.com/docs/functions-and-directives#directives) 添加到应用的 [全局样式表](/docs/pages/building-your-application/styling/css-modules#global-styles) 中,Tailwind 将使用这些指令注入生成的样式,例如: + +```css filename="styles/globals.css" +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +在 [自定义应用文件](/docs/pages/building-your-application/routing/custom-app) (`pages/_app.js`) 中导入 `globals.css` 样式表,将样式应用到应用中的每个路由。 + +```tsx filename="pages/_app.tsx" switcher +// 这些样式将应用到应用中的每个路由 +import '@/styles/globals.css' +import type { AppProps } from 'next/app' + +export default function App({ Component, pageProps }: AppProps) { + return +} +``` + +```jsx filename="pages/_app.js" switcher +// 这些样式将应用到应用中的每个路由 +import '@/styles/globals.css' + +export default function App({ Component, pageProps }) { + return +} +``` + +## 使用类名 + +安装 Tailwind CSS 并添加全局样式后,您可以在应用中使用 Tailwind 的实用类。 + +```tsx filename="pages/index.tsx" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +```jsx filename="pages/index.js" switcher +export default function Page() { + return

Hello, Next.js!

+} +``` + +
+ +## 与 Turbopack 一起使用 + +从 Next.js 13.1 开始,[Turbopack](https://turbo.build/pack/docs/features/css#tailwind-css) 已支持 Tailwind CSS 和 PostCSS。 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/03-css-in-js.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/03-css-in-js.mdx new file mode 100644 index 00000000..70012c52 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/03-css-in-js.mdx @@ -0,0 +1,313 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:45.931Z +title: CSS-in-JS +description: 在 Next.js 中使用 CSS-in-JS 库 +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加仅适用于页面路由的内容。任何共享内容不应包裹在组件中。 */} + + + +> **警告**:目前服务端组件不支持需要运行时 JavaScript 的 CSS-in-JS 库。要在服务端组件和流式传输等新 React 功能中使用 CSS-in-JS,需要库作者支持最新版本的 React,包括[并发渲染](https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react)。 +> +> 我们正与 React 团队合作开发上游 API,以处理支持 React 服务端组件和流式架构的 CSS 和 JavaScript 资源。 + +以下库在 `app` 目录的客户端组件中受支持(按字母顺序排列): + +- [`kuma-ui`](https://kuma-ui.com) +- [`@mui/material`](https://mui.com/material-ui/guides/next-js-app-router/) +- [`pandacss`](https://panda-css.com) +- [`styled-jsx`](#styled-jsx) +- [`styled-components`](#styled-components) +- [`style9`](https://github.com/johanholmerin/style9) +- [`tamagui`](https://tamagui.dev/docs/guides/next-js#server-components) +- [`tss-react`](https://tss-react.dev/) +- [`vanilla-extract`](https://github.com/vercel/next.js/tree/canary/examples/with-vanilla-extract) + +以下库正在开发支持中: + +- [`emotion`](https://github.com/emotion-js/emotion/issues/2928) + +> **须知**:我们正在测试不同的 CSS-in-JS 库,并将为支持 React 18 功能和/或 `app` 目录的库添加更多示例。 + +如果要为服务端组件添加样式,建议使用 [CSS 模块](/docs/app/building-your-application/styling/css-modules)或其他输出 CSS 文件的解决方案,如 PostCSS 或 [Tailwind CSS](/docs/app/building-your-application/styling/tailwind-css)。 + +## 在 `app` 中配置 CSS-in-JS + +配置 CSS-in-JS 是一个三步选择加入过程,包括: + +1. 一个**样式注册表**,用于收集渲染中的所有 CSS 规则。 +2. 新的 `useServerInsertedHTML` 钩子,用于在使用这些规则的任何内容之前注入规则。 +3. 一个客户端组件,在初始服务端渲染期间用样式注册表包裹您的应用。 + +### `styled-jsx` + +在客户端组件中使用 `styled-jsx` 需要 `v5.1.0` 版本。首先,创建一个新的注册表: + +```tsx filename="app/registry.tsx" switcher +'use client' + +import React, { useState } from 'react' +import { useServerInsertedHTML } from 'next/navigation' +import { StyleRegistry, createStyleRegistry } from 'styled-jsx' + +export default function StyledJsxRegistry({ + children, +}: { + children: React.ReactNode +}) { + // 仅使用惰性初始状态创建样式表一次 + // 参考:https://reactjs.org/docs/hooks-reference.html#lazy-initial-state + const [jsxStyleRegistry] = useState(() => createStyleRegistry()) + + useServerInsertedHTML(() => { + const styles = jsxStyleRegistry.styles() + jsxStyleRegistry.flush() + return <>{styles} + }) + + return {children} +} +``` + +```jsx filename="app/registry.js" switcher +'use client' + +import React, { useState } from 'react' +import { useServerInsertedHTML } from 'next/navigation' +import { StyleRegistry, createStyleRegistry } from 'styled-jsx' + +export default function StyledJsxRegistry({ children }) { + // 仅使用惰性初始状态创建样式表一次 + // 参考:https://reactjs.org/docs/hooks-reference.html#lazy-initial-state + const [jsxStyleRegistry] = useState(() => createStyleRegistry()) + + useServerInsertedHTML(() => { + const styles = jsxStyleRegistry.styles() + jsxStyleRegistry.flush() + return <>{styles} + }) + + return {children} +} +``` + +然后,用注册表包裹您的[根布局](/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required): + +```tsx filename="app/layout.tsx" switcher +import StyledJsxRegistry from './registry' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {children} + + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import StyledJsxRegistry from './registry' + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ) +} +``` + +[查看示例](https://github.com/vercel/app-playground/tree/main/app/styling/styled-jsx)。 + +### Styled Components + +以下是如何配置 `styled-components@6` 或更新版本的示例: + +首先,使用 `styled-components` API 创建一个全局注册表组件,用于收集渲染期间生成的所有 CSS 样式规则,以及一个返回这些规则的函数。然后使用 `useServerInsertedHTML` 钩子将注册表中收集的样式注入到根布局的 `` HTML 标签中。 + +```tsx filename="lib/registry.tsx" switcher +'use client' + +import React, { useState } from 'react' +import { useServerInsertedHTML } from 'next/navigation' +import { ServerStyleSheet, StyleSheetManager } from 'styled-components' + +export default function StyledComponentsRegistry({ + children, +}: { + children: React.ReactNode +}) { + // 仅使用惰性初始状态创建样式表一次 + // 参考:https://reactjs.org/docs/hooks-reference.html#lazy-initial-state + const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet()) + + useServerInsertedHTML(() => { + const styles = styledComponentsStyleSheet.getStyleElement() + styledComponentsStyleSheet.instance.clearTag() + return <>{styles} + }) + + if (typeof window !== 'undefined') return <>{children} + + return ( + + {children} + + ) +} +``` + +```jsx filename="lib/registry.js" switcher +'use client' + +import React, { useState } from 'react' +import { useServerInsertedHTML } from 'next/navigation' +import { ServerStyleSheet, StyleSheetManager } from 'styled-components' + +export default function StyledComponentsRegistry({ children }) { + // 仅使用惰性初始状态创建样式表一次 + // 参考:https://reactjs.org/docs/hooks-reference.html#lazy-initial-state + const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet()) + + useServerInsertedHTML(() => { + const styles = styledComponentsStyleSheet.getStyleElement() + styledComponentsStyleSheet.instance.clearTag() + return <>{styles} + }) + + if (typeof window !== 'undefined') return <>{children} + + return ( + + {children} + + ) +} +``` + +用样式注册表组件包裹根布局的 `children`: + +```tsx filename="app/layout.tsx" switcher +import StyledComponentsRegistry from './lib/registry' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + {children} + + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import StyledComponentsRegistry from './lib/registry' + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ) +} +``` + +[查看示例](https://github.com/vercel/app-playground/tree/main/app/styling/styled-components)。 + +> **须知**: +> +> - 在服务端渲染期间,样式将被提取到全局注册表并刷新到 HTML 的 `` 中。这确保样式规则在使用它们的任何内容之前放置。未来,我们可能会使用即将推出的 React 功能来确定注入样式的位置。 +> - 在流式传输期间,每个块的样式将被收集并附加到现有样式中。客户端水合完成后,`styled-components` 将像往常一样接管并注入任何进一步的动态样式。 +> - 我们特意在树的顶层使用客户端组件作为样式注册表,因为这种方式提取 CSS 规则更高效。它避免了在后续服务端渲染时重新生成样式,并防止它们被发送到服务端组件负载中。 + + + + + +
+ 示例 + +- [Styled JSX](https://github.com/vercel/next.js/tree/canary/examples/with-styled-jsx) +- [Styled Components](https://github.com/vercel/next.js/tree/canary/examples/with-styled-components) +- [Emotion](https://github.com/vercel/next.js/tree/canary/examples/with-emotion) +- [Linaria](https://github.com/vercel/next.js/tree/canary/examples/with-linaria) +- [Tailwind CSS + Emotion](https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss-emotion) +- [Styletron](https://github.com/vercel/next.js/tree/canary/examples/with-styletron) +- [Cxs](https://github.com/vercel/next.js/tree/canary/examples/with-cxs) +- [Aphrodite](https://github.com/vercel/next.js/tree/canary/examples/with-aphrodite) +- [Fela](https://github.com/vercel/next.js/tree/canary/examples/with-fela) +- [Stitches](https://github.com/vercel/next.js/tree/canary/examples/with-stitches) + +
+ +可以使用任何现有的 CSS-in-JS 解决方案。最简单的是内联样式: + +```jsx +function HiThere() { + return

hi there

+} + +export default HiThere +``` + +我们捆绑了 [styled-jsx](https://github.com/vercel/styled-jsx) 以提供对隔离作用域 CSS 的支持。目标是支持类似于 Web 组件的 "影子 CSS",但遗憾的是[不支持服务端渲染且仅限 JS](https://github.com/w3c/webcomponents/issues/71)。 + +查看上述示例以了解其他流行的 CSS-in-JS 解决方案(如 Styled Components)。 + +使用 `styled-jsx` 的组件如下所示: + +```jsx +function HelloWorld() { + return ( +
+ Hello world +

scoped!

+ + +
+ ) +} + +export default HelloWorld +``` + +更多示例请参阅 [styled-jsx 文档](https://github.com/vercel/styled-jsx)。 + +### 禁用 JavaScript + +是的,如果禁用 JavaScript,CSS 仍将在生产构建 (`next start`) 中加载。在开发过程中,我们要求启用 JavaScript 以通过[快速刷新](https://nextjs.org/blog/next-9-4#fast-refresh)提供最佳开发者体验。 + +
\ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/04-sass.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/04-sass.mdx new file mode 100644 index 00000000..7cddeba9 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/04-sass.mdx @@ -0,0 +1,82 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:03:22.981Z +title: Sass +description: 使用 Sass 为您的 Next.js 应用添加样式。 +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加页面路由专属内容。所有共享内容不应包裹在任何组件中。*/} + +Next.js 内置支持通过 `.scss` 和 `.sass` 扩展名使用 Sass。您可以通过 CSS 模块及 `.module.scss` 或 `.module.sass` 扩展名使用组件级 Sass。 + +首先安装 [`sass`](https://github.com/sass/sass): + +```bash filename="终端" +npm install --save-dev sass +``` + +> **须知**: +> +> Sass 支持 [两种不同语法](https://sass-lang.com/documentation/syntax),各自对应特定扩展名。 +> `.scss` 扩展名要求使用 [SCSS 语法](https://sass-lang.com/documentation/syntax#scss), +> 而 `.sass` 扩展名要求使用 [缩进语法("Sass")](https://sass-lang.com/documentation/syntax#the-indented-syntax)。 +> +> 若不确定如何选择,建议从 `.scss` 扩展名开始,它是 CSS 的超集,无需学习缩进语法("Sass")。 + +### 自定义 Sass 选项 + +如需配置 Sass 编译器,可在 `next.config.js` 中使用 `sassOptions`。 + +```js filename="next.config.js" +const path = require('path') + +module.exports = { + sassOptions: { + includePaths: [path.join(__dirname, 'styles')], + }, +} +``` + +### Sass 变量 + +Next.js 支持从 CSS 模块文件导出的 Sass 变量。 + +例如,使用导出的 `primaryColor` Sass 变量: + +```scss filename="app/variables.module.scss" +$primary-color: #64ff00; + +:export { + primaryColor: $primary-color; +} +``` + + + +```jsx filename="app/page.js" +// 映射至根路径 `/` + +import variables from './variables.module.scss' + +export default function Page() { + return

Hello, Next.js!

+} +``` + +
+ + + +```jsx filename="pages/_app.js" +import variables from '../styles/variables.module.scss' + +export default function MyApp({ Component, pageProps }) { + return ( + + + + ) +} +``` + + \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/index.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/index.mdx new file mode 100644 index 00000000..8e64c389 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/05-styling/index.mdx @@ -0,0 +1,18 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:03:08.736Z +title: 样式化 +description: 了解为 Next.js 应用程序设置样式的不同方式。 +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加专属于页面路由的内容。所有共享内容不应包裹在任何组件中。 */} + +Next.js 支持多种样式化方案,包括: + +- **全局 CSS**:使用简单且对传统 CSS 熟悉的开发者友好,但随着应用规模增长可能导致 CSS 包体积过大和样式管理困难 +- **CSS 模块**:创建局部作用域的 CSS 类,避免命名冲突并提高可维护性 +- **Tailwind CSS**:一个实用优先的 CSS 框架,通过组合实用类实现快速自定义设计 +- **Sass**:流行的 CSS 预处理器,通过变量、嵌套规则和混合等功能扩展了 CSS 能力 +- **CSS-in-JS**:将 CSS 直接嵌入 JavaScript 组件,实现动态且具有作用域的样式 + +通过查阅各自的文档了解每种方案的详细信息: \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/01-images.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/01-images.mdx new file mode 100644 index 00000000..ef2a6076 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/01-images.mdx @@ -0,0 +1,359 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:45.804Z +title: 图片优化 +nav_title: 图片 +description: 使用内置的 `next/image` 组件优化您的图片。 +related: + title: API 参考 + description: 了解更多关于 next/image API 的信息。 + links: + - app/api-reference/components/image +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加仅适用于页面路由的内容。任何共享内容不应包裹在组件中。 */} + +
+ 示例 + +- [图片组件](https://github.com/vercel/next.js/tree/canary/examples/image-component) + +
+ +根据 [Web Almanac](https://almanac.httparchive.org) 的数据,图片在典型网站的[页面权重](https://almanac.httparchive.org/en/2022/page-weight#content-type-and-file-formats)中占很大比例,并且对网站的 [LCP 性能](https://almanac.httparchive.org/en/2022/performance#lcp-image-optimization)有显著影响。 + +Next.js 的 Image 组件扩展了 HTML `` 元素,提供自动图片优化功能: + +- **尺寸优化:** 自动为每个设备提供正确尺寸的图片,使用 WebP 和 AVIF 等现代图片格式。 +- **视觉稳定性:** 图片加载时自动防止[布局偏移 (CLS)](/learn/seo/web-performance/cls)。 +- **更快页面加载:** 图片仅在进入视口时通过原生浏览器懒加载加载,可选模糊占位图。 +- **资源灵活性:** 按需调整图片尺寸,即使是远程服务器存储的图片。 + +> **🎥 观看视频:** 了解更多关于如何使用 `next/image` → [YouTube (9分钟)](https://youtu.be/IU_qq_c_lKA)。 + +## 使用方法 + +```js +import Image from 'next/image' +``` + +然后您可以定义图片的 `src`(本地或远程均可)。 + +### 本地图片 + +要使用本地图片,请 `import` 您的 `.jpg`、`.png` 或 `.webp` 图片文件。 + +Next.js 会根据导入的文件[自动确定](#image-sizing)图片的 `width` 和 `height`。这些值用于防止图片加载时的[累积布局偏移 (CLS)](https://nextjs.org/learn/seo/web-performance/cls)。 + + + +```jsx filename="app/page.js" +import Image from 'next/image' +import profilePic from './me.png' + +export default function Page() { + return ( + 作者照片 + ) +} +``` + + + + + +```jsx filename="pages/index.js" +import Image from 'next/image' +import profilePic from '../public/me.png' + +export default function Page() { + return ( + 作者照片 + ) +} +``` + + + +> **警告:** 不支持动态 `await import()` 或 `require()`。`import` 必须是静态的,以便在构建时进行分析。 + +### 远程图片 + +要使用远程图片,`src` 属性应为 URL 字符串。 + +由于 Next.js 在构建过程中无法访问远程文件,您需要手动提供 [`width`](/docs/app/api-reference/components/image#width)、[`height`](/docs/app/api-reference/components/image#height) 和可选的 [`blurDataURL`](/docs/app/api-reference/components/image#blurdataurl) 属性。 + +`width` 和 `height` 属性用于推断图片的正确宽高比,并避免图片加载时的布局偏移。`width` 和 `height` 并不决定图片文件的渲染尺寸。了解更多关于[图片尺寸](#image-sizing)。 + +```jsx filename="app/page.js" +import Image from 'next/image' + +export default function Page() { + return ( + 作者照片 + ) +} +``` + +为了安全地允许优化图片,请在 `next.config.js` 中定义支持的 URL 模式列表。尽可能具体以防止恶意使用。例如,以下配置仅允许来自特定 AWS S3 存储桶的图片: + +```js filename="next.config.js" +module.exports = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 's3.amazonaws.com', + port: '', + pathname: '/my-bucket/**', + }, + ], + }, +} +``` + +了解更多关于 [`remotePatterns`](/docs/app/api-reference/components/image#remotepatterns) 配置。如果想为图片 `src` 使用相对 URL,请使用 [`loader`](/docs/app/api-reference/components/image#loader)。 + +### 域名 + +有时您可能希望优化远程图片,但仍使用内置的 Next.js 图片优化 API。为此,将 `loader` 保留为默认设置,并为 Image `src` 属性输入绝对 URL。 + +为了保护您的应用程序免受恶意用户攻击,您必须定义要与 `next/image` 组件一起使用的远程主机名列表。 + +> 了解更多关于 [`remotePatterns`](/docs/app/api-reference/components/image#remotepatterns) 配置。 + +### 加载器 + +请注意,在[前面的示例](#local-images)中,为本地图片提供了部分 URL (`"/me.png"`)。这得益于加载器架构。 + +加载器是一个为您的图片生成 URL 的函数。它修改提供的 `src`,并生成多个 URL 以请求不同尺寸的图片。这些多个 URL 用于自动生成 [srcset](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/srcset),以便为访问者提供适合其视口大小的图片。 + +Next.js 应用程序的默认加载器使用内置的图片优化 API,该 API 优化来自网络任何位置的图片,然后直接从 Next.js 网络服务器提供。如果您希望直接从 CDN 或图片服务器提供图片,可以用几行 JavaScript 编写自己的加载器函数。 + +您可以通过 [`loader` 属性](/docs/app/api-reference/components/image#loader)为每个图片定义加载器,或通过 [`loaderFile` 配置](/docs/app/api-reference/components/image#loaderfile)在应用程序级别定义。 + +## 优先级 + +您应该为每个页面的 [最大内容绘制 (LCP) 元素](https://web.dev/lcp/#what-elements-are-considered)图片添加 `priority` 属性。这样做允许 Next.js 特别优先加载该图片(例如通过预加载标签或优先级提示),从而显著提升 LCP。 + +LCP 元素通常是页面视口中可见的最大图片或文本块。当您运行 `next dev` 时,如果 LCP 元素是没有 `priority` 属性的 ``,您会看到控制台警告。 + +一旦确定了 LCP 图片,您可以像这样添加属性: + + + +```jsx filename="app/page.js" +import Image from 'next/image' + +export default function Home() { + return ( + <> +

我的主页

+ 作者照片 +

欢迎来到我的主页!

+ + ) +} +``` + +
+ + + +```jsx filename="app/page.js" +import Image from 'next/image' +import profilePic from '../public/me.png' + +export default function Page() { + return 作者照片 +} +``` + + + +有关优先级的更多信息,请参阅 [`next/image` 组件文档](/docs/app/api-reference/components/image#priority)。 + +## 图片尺寸 + +图片最常见的性能损害方式之一是通过_布局偏移_,即图片在加载时将页面上的其他元素推开。这个性能问题对用户来说非常烦人,以至于它有自己的核心 Web 指标,称为[累积布局偏移 (CLS)](https://web.dev/cls/)。避免基于图片的布局偏移的方法是[始终为图片指定尺寸](https://web.dev/optimize-cls/#images-without-dimensions)。这允许浏览器在图片加载前预留足够的空间。 + +因为 `next/image` 旨在保证良好的性能结果,所以不能以会导致布局偏移的方式使用,并且**必须**通过以下三种方式之一指定尺寸: + +1. 自动,使用[静态导入](#local-images) +2. 显式,通过包含 [`width`](/docs/app/api-reference/components/image#width) 和 [`height`](/docs/app/api-reference/components/image#height) 属性 +3. 隐式,通过使用 [fill](/docs/app/api-reference/components/image#fill) 使图片扩展以填充其父元素。 + +> **如果我不知道图片的尺寸怎么办?** +> +> 如果您从不知道图片尺寸的来源访问图片,可以采取以下措施: +> +> **使用 `fill`** +> +> [`fill`](/docs/app/api-reference/components/image#fill) 属性允许您的图片由其父元素调整大小。考虑使用 CSS 为图片的父元素在页面上留出空间,并使用 [`sizes`](/docs/app/api-reference/components/image#sizes) 属性匹配任何媒体查询断点。您还可以将 [`object-fit`](https://developer.mozilla.org/docs/Web/CSS/object-fit) 与 `fill`、`contain` 或 `cover` 一起使用,以及 [`object-position`](https://developer.mozilla.org/docs/Web/CSS/object-position) 来定义图片应如何占据该空间。 +> +> **标准化您的图片** +> +> 如果您从您控制的来源提供图片,考虑修改您的图片管道以将图片标准化为特定尺寸。 +> +> **修改您的 API 调用** +> +> 如果您的应用程序通过 API 调用(例如 CMS)检索图片 URL,您可以修改 API 调用以返回图片尺寸以及 URL。 + +如果以上建议的方法都不适用于调整图片尺寸,`next/image` 组件设计为可以与标准 `` 元素在页面上良好配合使用。 + +## 样式 + +为 Image 组件设置样式与为普通 `` 元素设置样式类似,但有一些准则需要记住: + +- 使用 `className` 或 `style`,而不是 `styled-jsx`。 + - 在大多数情况下,我们建议使用 `className` 属性。这可以是导入的 [CSS 模块](/docs/app/building-your-application/styling/css-modules)、[全局样式表](/docs/app/building-your-application/styling/css-modules#global-styles)等。 + - 您也可以使用 `style` 属性分配内联样式。 + - 不能使用 [styled-jsx](/docs/app/building-your-application/styling/css-in-js),因为它的作用域限定在当前组件(除非将样式标记为 `global`)。 +- 使用 `fill` 时,父元素必须具有 `position: relative` + - 这是在该布局模式下正确渲染图片元素所必需的。 +- 使用 `fill` 时,父元素必须具有 `display: block` + - 这是 `
` 元素的默认值,但其他情况下应明确指定。 + +## 示例 + +### 响应式 + +响应式图片填充其父容器的宽度和高度 + +```jsx +import Image from 'next/image' +import mountains from '../public/mountains.jpg' + +export default function Responsive() { + return ( +
+ 山脉 +
+ ) +} +``` + +### 填充容器 + +图片网格填充父容器宽度 + +```jsx +import Image from 'next/image' +import mountains from '../public/mountains.jpg' + +export default function Fill() { + return ( +
+
+ 山脉 +
+ {/* 网格中的更多图片... */} +
+ ) +} +``` + +### 背景图片 + +背景图片占据页面的全宽和全高 + +```jsx +import Image from 'next/image' +import mountains from '../public/mountains.jpg' + +export default function Background() { + return ( + 山脉 + ) +} +``` + +有关 Image 组件与各种样式一起使用的示例,请参阅 [Image 组件演示](https://image-component.nextjs.gallery)。 + +## 其他属性 + +[**查看 `next/image` 组件的所有可用属性。**](/docs/app/api-reference/components/image) + +## 配置 + +`next/image` 组件和 Next.js 图片优化 API 可以在 [`next.config.js` 文件](/docs/app/api-reference/next-config-js)中配置。这些配置允许您[启用远程图片](/docs/app/api-reference/components/image#remotepatterns)、[定义自定义图片断点](/docs/app/api-reference/components/image#devicesizes)、[更改缓存行为](/docs/app/api-reference/components/image#caching-behavior)等。 + +[**阅读完整的图片配置文档以获取更多信息。**](/docs/app/api-reference/components/image#configuration-options) \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/02-fonts.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/02-fonts.mdx new file mode 100644 index 00000000..ce3e8768 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/02-fonts.mdx @@ -0,0 +1,644 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:05:12.990Z +title: 字体优化 +nav_title: 字体 +description: 使用内置的 `next/font` 加载器优化您应用的网页字体。 +related: + title: API 参考 + description: 了解更多关于 next/font API 的信息。 + links: + - app/api-reference/components/font +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加特定于页面路由的内容。任何共享内容不应包裹在组件中。 */} + +[**`next/font`**](/docs/app/api-reference/components/font) 会自动优化您的字体(包括自定义字体),并移除外部网络请求以提升隐私性和性能。 + +> **🎥 观看视频:** 了解更多关于如何使用 `next/font` → [YouTube (6分钟)](https://www.youtube.com/watch?v=L8_98i_bMMA)。 + +`next/font` 包含**内置的自动自托管**功能,适用于_任何_字体文件。这意味着您可以零布局偏移地最优加载网页字体,这得益于底层使用的 CSS `size-adjust` 属性。 + +这个新的字体系统还允许您便捷地使用所有 Google 字体,同时兼顾性能和隐私。CSS 和字体文件会在构建时下载,并与您的其他静态资源一起自托管。**浏览器不会向 Google 发送任何请求。** + +## Google 字体 + +自动自托管任何 Google 字体。字体包含在部署中,并从与您的部署相同的域名提供服务。**浏览器不会向 Google 发送任何请求。** + +首先从 `next/font/google` 导入您想使用的字体作为一个函数。我们推荐使用 [可变字体](https://fonts.google.com/variablefonts) 以获得最佳性能和灵活性。 + + + +```tsx filename="app/layout.tsx" switcher +import { Inter } from 'next/font/google' + +// 如果加载可变字体,无需指定字重 +const inter = Inter({ + subsets: ['latin'], + display: 'swap', +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import { Inter } from 'next/font/google' + +// 如果加载可变字体,无需指定字重 +const inter = Inter({ + subsets: ['latin'], + display: 'swap', +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + +如果无法使用可变字体,您**需要指定一个字重**: + +```tsx filename="app/layout.tsx" switcher +import { Roboto } from 'next/font/google' + +const roboto = Roboto({ + weight: '400', + subsets: ['latin'], + display: 'swap', +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import { Roboto } from 'next/font/google' + +const roboto = Roboto({ + weight: '400', + subsets: ['latin'], + display: 'swap', +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + + + + + +要在所有页面中使用该字体,将其添加到 `/pages` 下的 [`_app.js` 文件](/docs/pages/building-your-application/routing/custom-app) 中,如下所示: + +```jsx filename="pages/_app.js" +import { Inter } from 'next/font/google' + +// 如果加载可变字体,无需指定字重 +const inter = Inter({ subsets: ['latin'] }) + +export default function MyApp({ Component, pageProps }) { + return ( +
+ +
+ ) +} +``` + +如果无法使用可变字体,您**需要指定一个字重**: + +```jsx filename="pages/_app.js" +import { Roboto } from 'next/font/google' + +const roboto = Roboto({ + weight: '400', + subsets: ['latin'], +}) + +export default function MyApp({ Component, pageProps }) { + return ( +
+ +
+ ) +} +``` + +
+ +您可以通过数组指定多个字重和/或样式: + +```jsx filename="app/layout.js" +const roboto = Roboto({ + weight: ['400', '700'], + style: ['normal', 'italic'], + subsets: ['latin'], + display: 'swap', +}) +``` + +> **须知**:对于包含多个单词的字体名称,使用下划线(\_)。例如,`Roboto Mono` 应导入为 `Roboto_Mono`。 + + + +### 在 `` 中应用字体 + +您也可以不使用包装器和 `className`,而是通过将其注入 `` 来使用字体,如下所示: + +```jsx filename="pages/_app.js" +import { Inter } from 'next/font/google' + +const inter = Inter({ subsets: ['latin'] }) + +export default function MyApp({ Component, pageProps }) { + return ( + <> + + + + ) +} +``` + +### 单页面使用 + +要在单个页面上使用字体,将其添加到特定页面,如下所示: + +```jsx filename="pages/index.js" +import { Inter } from 'next/font/google' + +const inter = Inter({ subsets: ['latin'] }) + +export default function Home() { + return ( +
+

Hello World

+
+ ) +} +``` + +
+ +### 指定子集 + +Google 字体会自动进行[子集化](https://fonts.google.com/knowledge/glossary/subsetting)。这会减小字体文件的大小并提升性能。您需要定义要预加载哪些子集。如果 [`preload`](/docs/app/api-reference/components/font#preload) 为 `true` 但未指定任何子集,将导致警告。 + +可以通过将其添加到函数调用来完成: + + + +```tsx filename="app/layout.tsx" switcher +const inter = Inter({ subsets: ['latin'] }) +``` + +```jsx filename="app/layout.js" switcher +const inter = Inter({ subsets: ['latin'] }) +``` + + + + + +```jsx filename="pages/_app.js" +const inter = Inter({ subsets: ['latin'] }) +``` + + + +查看 [字体 API 参考](/docs/app/api-reference/components/font) 获取更多信息。 + +### 使用多种字体 + +您可以在应用中导入并使用多种字体。有两种方法可以实现。 + +第一种方法是创建一个工具函数,导出字体、导入它,并在需要的地方应用其 `className`。这确保字体仅在渲染时预加载: + +```ts filename="app/fonts.ts" switcher +import { Inter, Roboto_Mono } from 'next/font/google' + +export const inter = Inter({ + subsets: ['latin'], + display: 'swap', +}) + +export const roboto_mono = Roboto_Mono({ + subsets: ['latin'], + display: 'swap', +}) +``` + +```js filename="app/fonts.js" switcher +import { Inter, Roboto_Mono } from 'next/font/google' + +export const inter = Inter({ + subsets: ['latin'], + display: 'swap', +}) + +export const roboto_mono = Roboto_Mono({ + subsets: ['latin'], + display: 'swap', +}) +``` + + + +```tsx filename="app/layout.tsx" switcher +import { inter } from './fonts' + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + +
{children}
+ + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import { inter } from './fonts' + +export default function Layout({ children }) { + return ( + + +
{children}
+ + + ) +} +``` + +```tsx filename="app/page.tsx" switcher +import { roboto_mono } from './fonts' + +export default function Page() { + return ( + <> +

My page

+ + ) +} +``` + +```jsx filename="app/page.js" switcher +import { roboto_mono } from './fonts' + +export default function Page() { + return ( + <> +

My page

+ + ) +} +``` + +
+ +在上面的示例中,`Inter` 将全局应用,而 `Roboto Mono` 可以根据需要导入和应用。 + +或者,您可以创建一个 [CSS 变量](/docs/app/api-reference/components/font#variable) 并将其与您首选的 CSS 解决方案一起使用: + + + +```tsx filename="app/layout.tsx" switcher +import { Inter, Roboto_Mono } from 'next/font/google' +import styles from './global.css' + +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', + display: 'swap', +}) + +const roboto_mono = Roboto_Mono({ + subsets: ['latin'], + variable: '--font-roboto-mono', + display: 'swap', +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + +

My App

+
{children}
+ + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import { Inter, Roboto_Mono } from 'next/font/google' + +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', + display: 'swap', +}) + +const roboto_mono = Roboto_Mono({ + subsets: ['latin'], + variable: '--font-roboto-mono', + display: 'swap', +}) + +export default function RootLayout({ children }) { + return ( + + +

My App

+
{children}
+ + + ) +} +``` + +
+ +```css filename="app/global.css" +html { + font-family: var(--font-inter); +} + +h1 { + font-family: var(--font-roboto-mono); +} +``` + +在上面的示例中,`Inter` 将全局应用,而任何 `

` 标签将使用 `Roboto Mono` 样式。 + +> **建议**:谨慎使用多种字体,因为每种新字体都是客户端需要下载的额外资源。 + +## 本地字体 + +导入 `next/font/local` 并指定本地字体文件的 `src`。我们推荐使用 [可变字体](https://fonts.google.com/variablefonts) 以获得最佳性能和灵活性。 + + + +```tsx filename="app/layout.tsx" switcher +import localFont from 'next/font/local' + +// 字体文件可以放在 `app` 内 +const myFont = localFont({ + src: './my-font.woff2', + display: 'swap', +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import localFont from 'next/font/local' + +// 字体文件可以放在 `app` 内 +const myFont = localFont({ + src: './my-font.woff2', + display: 'swap', +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + + + + + +```jsx filename="pages/_app.js" +import localFont from 'next/font/local' + +// 字体文件可以放在 `pages` 内 +const myFont = localFont({ src: './my-font.woff2' }) + +export default function MyApp({ Component, pageProps }) { + return ( +
+ +
+ ) +} +``` + +
+ +如果想为单个字体系列使用多个文件,`src` 可以是一个数组: + +```js +const roboto = localFont({ + src: [ + { + path: './Roboto-Regular.woff2', + weight: '400', + style: 'normal', + }, + { + path: './Roboto-Italic.woff2', + weight: '400', + style: 'italic', + }, + { + path: './Roboto-Bold.woff2', + weight: '700', + style: 'normal', + }, + { + path: './Roboto-BoldItalic.woff2', + weight: '700', + style: 'italic', + }, + ], +}) +``` + +查看 [字体 API 参考](/docs/app/api-reference/components/font) 获取更多信息。 + +## 与 Tailwind CSS 一起使用 + +`next/font` 可以通过 [CSS 变量](/docs/app/api-reference/components/font#css-variables) 与 [Tailwind CSS](https://tailwindcss.com/) 一起使用。 + +在下面的示例中,我们使用来自 `next/font/google` 的字体 `Inter`(您可以使用来自 Google 或本地字体的任何字体)。使用 `variable` 选项加载您的字体以定义 CSS 变量名称,并将其分配给 `inter`。然后,使用 `inter.variable` 将 CSS 变量添加到您的 HTML 文档中。 + + + +```tsx filename="app/layout.tsx" switcher +import { Inter, Roboto_Mono } from 'next/font/google' + +const inter = Inter({ + subsets: ['latin'], + display: 'swap', + variable: '--font-inter', +}) + +const roboto_mono = Roboto_Mono({ + subsets: ['latin'], + display: 'swap', + variable: '--font-roboto-mono', +}) + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} +``` + +```jsx filename="app/layout.js" switcher +import { Inter, Roboto_Mono } from 'next/font/google' + +const inter = Inter({ + subsets: ['latin'], + display: 'swap', + variable: '--font-inter', +}) + +const roboto_mono = Roboto_Mono({ + subsets: ['latin'], + display: 'swap', + variable: '--font-roboto-mono', +}) + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} +``` + + + + + +```jsx filename="pages/_app.js" +import { Inter } from 'next/font/google' + +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', +}) + +export default function MyApp({ Component, pageProps }) { + return ( +
+ +
+ ) +} +``` + +
+ +最后,将 CSS 变量添加到您的 [Tailwind CSS 配置](/docs/app/building-your-application/styling/tailwind-css#configuring-tailwind) 中: + +```js filename="tailwind.config.js" +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + './app/**/*.{js,ts,jsx,tsx}', + ], + theme: { + extend: { + fontFamily: { + sans: ['var(--font-inter)'], + mono: ['var(--font-roboto-mono)'], + }, + }, + }, + plugins: [], +} +``` + +现在,您可以使用 `font-sans` 和 `font-mono` 工具类将字体应用到您的元素上。 + +## 预加载 + + +当在您站点的某个页面上调用字体函数时,它不会全局可用,也不会在所有路由上预加载。相反,字体仅基于使用它的文件类型在相关路由上预加载: + +- 如果是 [唯一页面](/docs/app/building-your-application/routing/pages-and-layouts#pages),它会在该页面的唯一路由上预加载。 +- 如果是 [布局](/docs/app/building-your-application/routing/pages-and-layouts#layouts),它会在布局包裹的所有路由上预加载。 +- 如果是 [根布局](/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required),它会在所有路由上预加载。 + + + + + +当在您站点的某个页面上调用字体函数时,它不会全局可用,也不会在所有路由上预加载。相反,字体仅基于使用它的文件类型在相关路由上预加载: + +- 如果是 [唯一页面](/docs/pages/building-your-application/routing/pages-and-layouts),它会在该页面的唯一路由上预加载。 +- 如果在 [自定义 App](/docs/pages/building-your-application/routing/custom-app) 中,它会在 `/pages` 下站点的所有路由上预加载。 + + + +## 重用字体 + +每次调用 `localFont` 或 Google 字体函数时,该字体会在您的应用中作为一个实例托管。因此,如果在多个文件中加载相同的字体函数,会托管相同字体的多个实例。在这种情况下,建议执行以下操作: + +- 在一个共享文件中调用字体加载器函数 +- 将其导出为一个常量 +- 在每个需要使用该字体的文件中导入该常量 \ No newline at end of file diff --git a/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/03-scripts.mdx b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/03-scripts.mdx new file mode 100644 index 00000000..299f2a74 --- /dev/null +++ b/apps/docs/content/zh-hans/docs/13/02-app/01-building-your-application/06-optimizing/03-scripts.mdx @@ -0,0 +1,377 @@ +--- +source-updated-at: 2025-05-16T04:52:11.000Z +translation-updated-at: 2025-05-21T21:04:03.412Z +title: 脚本优化 +nav_title: 脚本 +description: 使用内置的 Script 组件优化第三方脚本。 +related: + title: API 参考 + description: 了解更多关于 next/script 的 API。 + links: + - app/api-reference/components/script +--- + +{/* 本文档内容在应用路由和页面路由间共享。您可以使用 `内容` 组件添加仅适用于页面路由的内容。任何共享内容不应包裹在组件中。 */} + + + +### 布局脚本 + +要为多个路由加载第三方脚本,请导入 `next/script` 并将脚本直接包含在您的布局组件中: + +```tsx filename="app/dashboard/layout.tsx" switcher +import Script from 'next/script' + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + <> +
{children}
+ +``` + +或使用 `dangerouslySetInnerHTML` 属性: + +```jsx + +``` + +或者使用 `dangerouslySetInnerHTML` 属性: + +```jsx +