nitro-nightly 3.0.1-alpha.2 → 3.0.260415-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -0
- package/README.md +2 -2
- package/dist/THIRD-PARTY-LICENSES.md +1646 -0
- package/dist/_build/common.mjs +7259 -3815
- package/dist/_build/rolldown.mjs +32 -42
- package/dist/_build/rollup.mjs +28 -48
- package/dist/_build/vite.build.mjs +10 -10
- package/dist/_chunks/nitro.mjs +102 -327
- package/dist/_chunks/nitro2.mjs +8 -9
- package/dist/_chunks/nitro3.mjs +136 -0
- package/dist/_chunks/nitro4.mjs +127 -0
- package/dist/_chunks/utils.mjs +40 -29
- package/dist/_common.mjs +13 -25
- package/dist/_dev.d.mts +0 -3
- package/dist/{_chunks/dev.mjs → _dev.mjs} +132 -330
- package/dist/_libs/_.mjs +2 -0
- package/dist/_libs/_2.mjs +2 -0
- package/dist/_libs/_3.mjs +2 -0
- package/dist/_libs/_4.mjs +2 -0
- package/dist/_libs/_5.mjs +2 -0
- package/dist/_libs/_6.mjs +2 -0
- package/dist/_libs/_7.mjs +2 -0
- package/dist/_libs/c12+rc9.mjs +814 -0
- package/dist/_libs/{c12+giget+readdirp+chokidar.d.mts → c12+readdirp+chokidar.d.mts} +49 -40
- package/dist/_libs/citty.mjs +142 -41
- package/dist/_libs/commondir+is-reference.mjs +60 -56
- package/dist/_libs/compatx.d.mts +0 -2
- package/dist/_libs/compatx.mjs +1 -4
- package/dist/_libs/esbuild.d.mts +0 -1
- package/dist/_libs/escape-string-regexp.mjs +1 -4
- package/dist/_libs/{pluginutils+plugin-commonjs.d.mts → estree+pluginutils.d.mts} +0 -5
- package/dist/_libs/estree-walker.mjs +4 -92
- package/dist/_libs/exsolve.d.mts +1 -0
- package/dist/_libs/hasown+resolve+deepmerge.mjs +160 -246
- package/dist/_libs/httpxy.d.mts +20 -13
- package/dist/_libs/httpxy.mjs +519 -95
- package/dist/_libs/js-tokens.d.mts +1 -0
- package/dist/_libs/klona.mjs +1 -4
- package/dist/_libs/magic-string.d.mts +0 -2
- package/dist/_libs/mlly.d.mts +0 -2
- package/dist/_libs/nypm+tinyexec.mjs +822 -0
- package/dist/_libs/perfect-debounce.mjs +68 -0
- package/dist/_libs/pkg-types.d.mts +0 -3
- package/dist/_libs/plugin-alias.mjs +1 -5
- package/dist/_libs/plugin-inject.mjs +1 -7
- package/dist/_libs/plugin-json.mjs +1 -5
- package/dist/_libs/pluginutils.mjs +3 -35
- package/dist/_libs/readdirp+chokidar.mjs +187 -365
- package/dist/_libs/remapping.mjs +1 -5
- package/dist/_libs/resolve-uri+gen-mapping.mjs +15 -53
- package/dist/_libs/rou3.d.mts +1 -4
- package/dist/_libs/rou3.mjs +223 -78
- package/dist/_libs/std-env.d.mts +8 -2
- package/dist/_libs/strip-literal.d.mts +1 -0
- package/dist/_libs/tsconfck.mjs +21 -334
- package/dist/_libs/ultrahtml.mjs +3 -16
- package/dist/_libs/unimport+unplugin.mjs +244 -388
- package/dist/_libs/unplugin+unimport.d.mts +5 -15
- package/dist/_libs/unwasm.d.mts +0 -3
- package/dist/_presets.mjs +575 -488
- package/dist/builder.d.mts +5 -25
- package/dist/builder.mjs +3 -8
- package/dist/cli/_chunks/build.mjs +22 -25
- package/dist/cli/_chunks/common.mjs +1 -4
- package/dist/cli/_chunks/deploy.mjs +37 -0
- package/dist/cli/_chunks/dev.mjs +6 -11
- package/dist/cli/_chunks/docs.mjs +28 -0
- package/dist/cli/_chunks/list.mjs +4 -8
- package/dist/cli/_chunks/prepare.mjs +2 -6
- package/dist/cli/_chunks/preview.mjs +48 -0
- package/dist/cli/_chunks/run.mjs +7 -11
- package/dist/cli/_chunks/task.mjs +1 -5
- package/dist/cli/index.mjs +5 -6
- package/dist/docs/0.docs/0.index/index.md +56 -0
- package/dist/docs/0.docs/1.quick-start.md +110 -0
- package/dist/docs/0.docs/10.lifecycle.md +204 -0
- package/dist/docs/0.docs/11.openapi.md +291 -0
- package/dist/docs/0.docs/12.plugins.md +164 -0
- package/dist/docs/0.docs/13.tasks.md +277 -0
- package/dist/docs/0.docs/14.websocket.md +386 -0
- package/dist/docs/0.docs/15.migration.md +213 -0
- package/dist/docs/0.docs/16.nightly.md +31 -0
- package/dist/docs/0.docs/2.renderer.md +322 -0
- package/dist/docs/0.docs/3.routing.md +711 -0
- package/dist/docs/0.docs/4.server-entry.md +239 -0
- package/dist/docs/0.docs/5.cache.md +408 -0
- package/dist/docs/0.docs/6.storage.md +261 -0
- package/dist/docs/0.docs/7.assets.md +166 -0
- package/dist/docs/0.docs/8.configuration.md +256 -0
- package/dist/docs/0.docs/9.database.md +185 -0
- package/dist/docs/1.deploy/0.index/index.md +64 -0
- package/dist/docs/1.deploy/1.runtimes/0.node.md +63 -0
- package/dist/docs/1.deploy/1.runtimes/1.bun.md +15 -0
- package/dist/docs/1.deploy/1.runtimes/2.deno.md +19 -0
- package/dist/docs/1.deploy/2.providers/0.alwaysdata.md +50 -0
- package/dist/docs/1.deploy/2.providers/1.aws.md +47 -0
- package/dist/docs/1.deploy/2.providers/10.flightcontrol.md +67 -0
- package/dist/docs/1.deploy/2.providers/11.genezio.md +67 -0
- package/dist/docs/1.deploy/2.providers/12.github-pages.md +68 -0
- package/dist/docs/1.deploy/2.providers/13.gitlab-pages.md +37 -0
- package/dist/docs/1.deploy/2.providers/14.heroku.md +84 -0
- package/dist/docs/1.deploy/2.providers/15.iis.md +38 -0
- package/dist/docs/1.deploy/2.providers/16.koyeb.md +109 -0
- package/dist/docs/1.deploy/2.providers/17.netlify.md +47 -0
- package/dist/docs/1.deploy/2.providers/18.platform-sh.md +37 -0
- package/dist/docs/1.deploy/2.providers/19.render.md +37 -0
- package/dist/docs/1.deploy/2.providers/2.aws-amplify.md +81 -0
- package/dist/docs/1.deploy/2.providers/20.stormkit.md +24 -0
- package/dist/docs/1.deploy/2.providers/21.vercel.md +270 -0
- package/dist/docs/1.deploy/2.providers/22.zeabur.md +19 -0
- package/dist/docs/1.deploy/2.providers/23.zephyr.md +99 -0
- package/dist/docs/1.deploy/2.providers/24.zerops.md +102 -0
- package/dist/docs/1.deploy/2.providers/3.azure.md +72 -0
- package/dist/docs/1.deploy/2.providers/4.cleavr.md +33 -0
- package/dist/docs/1.deploy/2.providers/5.cloudflare.md +369 -0
- package/dist/docs/1.deploy/2.providers/6.deno-deploy.md +66 -0
- package/dist/docs/1.deploy/2.providers/7.digitalocean.md +46 -0
- package/dist/docs/1.deploy/2.providers/8.edgeone.md +20 -0
- package/dist/docs/1.deploy/2.providers/9.firebase.md +32 -0
- package/dist/docs/2.config/0.index/index.md +1221 -0
- package/dist/docs/3.examples/0.index/index.md +3 -0
- package/dist/docs/3.examples/1.api-routes.md +145 -0
- package/dist/docs/3.examples/10.hono.md +74 -0
- package/dist/docs/3.examples/11.import-alias.md +97 -0
- package/dist/docs/3.examples/12.middleware.md +91 -0
- package/dist/docs/3.examples/13.mono-jsx.md +68 -0
- package/dist/docs/3.examples/14.nano-jsx.md +70 -0
- package/dist/docs/3.examples/15.plugins.md +91 -0
- package/dist/docs/3.examples/16.renderer.md +113 -0
- package/dist/docs/3.examples/17.runtime-config.md +97 -0
- package/dist/docs/3.examples/18.server-fetch.md +87 -0
- package/dist/docs/3.examples/19.shiki.md +198 -0
- package/dist/docs/3.examples/2.auto-imports.md +92 -0
- package/dist/docs/3.examples/20.virtual-routes.md +74 -0
- package/dist/docs/3.examples/21.vite-nitro-plugin.md +79 -0
- package/dist/docs/3.examples/22.vite-rsc.md +1041 -0
- package/dist/docs/3.examples/23.vite-ssr-html.md +217 -0
- package/dist/docs/3.examples/24.vite-ssr-preact.md +247 -0
- package/dist/docs/3.examples/25.vite-ssr-react.md +226 -0
- package/dist/docs/3.examples/26.vite-ssr-solid.md +256 -0
- package/dist/docs/3.examples/27.vite-ssr-tsr-react.md +442 -0
- package/dist/docs/3.examples/28.vite-ssr-tss-react.md +444 -0
- package/dist/docs/3.examples/29.vite-ssr-vue-router.md +617 -0
- package/dist/docs/3.examples/3.cached-handler.md +81 -0
- package/dist/docs/3.examples/30.vite-trpc.md +335 -0
- package/dist/docs/3.examples/31.websocket.md +282 -0
- package/dist/docs/3.examples/4.custom-error-handler.md +98 -0
- package/dist/docs/3.examples/5.database.md +157 -0
- package/dist/docs/3.examples/6.elysia.md +70 -0
- package/dist/docs/3.examples/7.express.md +78 -0
- package/dist/docs/3.examples/8.fastify.md +79 -0
- package/dist/docs/3.examples/9.hello-world.md +68 -0
- package/dist/docs/README.md +86 -0
- package/dist/docs/index.md +121 -0
- package/dist/node_modules/@poppinss/dumper/build/formatters/html/main.js +8 -0
- package/dist/node_modules/@poppinss/dumper/package.json +1 -1
- package/dist/node_modules/@speed-highlight/core/dist/index.js +4 -4
- package/dist/node_modules/@speed-highlight/core/dist/terminal.js +2 -2
- package/dist/node_modules/@speed-highlight/core/package.json +1 -1
- package/dist/node_modules/cookie-es/dist/index.mjs +299 -250
- package/dist/node_modules/cookie-es/package.json +26 -23
- package/dist/node_modules/croner/dist/croner.js +1 -1
- package/dist/node_modules/croner/package.json +11 -1
- package/dist/node_modules/defu/dist/defu.mjs +2 -2
- package/dist/node_modules/defu/package.json +33 -28
- package/dist/node_modules/rendu/dist/_chunks/libs/cookie-es.mjs +151 -0
- package/dist/node_modules/rendu/dist/index.mjs +5 -112
- package/dist/node_modules/rendu/package.json +25 -24
- package/dist/node_modules/youch/build/helpers-B9BQYaS6.js +23 -0
- package/dist/node_modules/youch/build/index.js +204 -351
- package/dist/node_modules/youch/build/public/error_info/script.js +1 -1
- package/dist/node_modules/youch/build/public_dir-C5bujZKB.js +25 -0
- package/dist/node_modules/youch/build/src/templates/error_cause/main.js +44 -0
- package/dist/node_modules/youch/build/src/templates/error_info/main.js +72 -0
- package/dist/node_modules/youch/build/src/templates/error_metadata/main.js +65 -0
- package/dist/node_modules/youch/build/src/templates/error_stack/main.js +152 -0
- package/dist/node_modules/youch/build/src/templates/error_stack_source/main.js +46 -0
- package/dist/node_modules/youch/build/src/templates/header/main.js +24 -0
- package/dist/node_modules/youch/build/src/templates/layout/main.js +27 -0
- package/dist/node_modules/youch/package.json +35 -32
- package/dist/presets/_nitro/runtime/nitro-dev.mjs +12 -57
- package/dist/presets/_nitro/runtime/nitro-prerenderer.mjs +1 -0
- package/dist/presets/_nitro/runtime/service-worker.mjs +1 -2
- package/dist/presets/aws-lambda/runtime/_utils.mjs +6 -6
- package/dist/presets/aws-lambda/runtime/aws-lambda-streaming.mjs +3 -1
- package/dist/presets/azure/runtime/_utils.mjs +5 -5
- package/dist/presets/azure/runtime/azure-swa.mjs +5 -5
- package/dist/presets/bun/runtime/bun.mjs +9 -6
- package/dist/presets/cloudflare/runtime/_module-handler.mjs +1 -1
- package/dist/presets/cloudflare/runtime/cloudflare-durable.mjs +4 -4
- package/dist/presets/cloudflare/runtime/cloudflare-module.mjs +3 -3
- package/dist/presets/cloudflare/runtime/cloudflare-pages.mjs +2 -2
- package/dist/presets/cloudflare/runtime/plugin.dev.mjs +10 -21
- package/dist/presets/deno/runtime/deno-deploy.mjs +4 -4
- package/dist/presets/deno/runtime/deno-server.mjs +9 -6
- package/dist/presets/edgeone/runtime/edgeone.mjs +10 -0
- package/dist/presets/netlify/runtime/netlify-edge.mjs +2 -3
- package/dist/presets/netlify/runtime/netlify.mjs +1 -1
- package/dist/presets/node/runtime/node-cluster.mjs +5 -4
- package/dist/presets/node/runtime/node-middleware.mjs +1 -1
- package/dist/presets/node/runtime/node-server.mjs +8 -5
- package/dist/presets/stormkit/runtime/stormkit.mjs +1 -2
- package/dist/presets/vercel/runtime/cron-handler.mjs +25 -0
- package/dist/presets/vercel/runtime/queue-handler.mjs +21 -0
- package/dist/presets/vercel/runtime/vercel.node.mjs +3 -3
- package/dist/presets/vercel/runtime/vercel.web.mjs +3 -5
- package/dist/presets/winterjs/runtime/winterjs.mjs +10 -10
- package/dist/presets/zephyr/runtime/server.mjs +3 -0
- package/dist/runtime/internal/app.mjs +12 -11
- package/dist/runtime/internal/cache.d.mts +0 -4
- package/dist/runtime/internal/cache.mjs +32 -256
- package/dist/runtime/internal/context.d.mts +3 -1
- package/dist/runtime/internal/context.mjs +1 -9
- package/dist/runtime/internal/error/dev.d.mts +5 -4
- package/dist/runtime/internal/error/dev.mjs +47 -49
- package/dist/runtime/internal/error/hooks.d.mts +1 -1
- package/dist/runtime/internal/error/prod.d.mts +2 -5
- package/dist/runtime/internal/error/prod.mjs +19 -33
- package/dist/runtime/internal/error/utils.d.mts +4 -4
- package/dist/runtime/internal/meta.d.mts +1 -1
- package/dist/runtime/internal/plugin.d.mts +2 -2
- package/dist/runtime/internal/route-rules.d.mts +11 -4
- package/dist/runtime/internal/route-rules.mjs +15 -5
- package/dist/runtime/internal/routes/dev-tasks.d.mts +3 -2
- package/dist/runtime/internal/routes/dev-tasks.mjs +15 -10
- package/dist/runtime/internal/routes/openapi.mjs +2 -2
- package/dist/runtime/internal/routes/renderer-template.d.mts +1 -1
- package/dist/runtime/internal/routes/renderer-template.dev.d.mts +2 -1
- package/dist/runtime/internal/routes/scalar.mjs +3 -3
- package/dist/runtime/internal/routes/swagger.mjs +7 -4
- package/dist/runtime/internal/runtime-config.d.mts +1 -1
- package/dist/runtime/internal/runtime-config.mjs +2 -2
- package/dist/runtime/internal/static.mjs +5 -4
- package/dist/runtime/internal/task.d.mts +3 -1
- package/dist/runtime/internal/task.mjs +7 -7
- package/dist/runtime/internal/vite/dev-entry.mjs +6 -3
- package/dist/runtime/internal/vite/dev-worker.mjs +264 -0
- package/dist/runtime/meta.d.mts +1 -0
- package/dist/runtime/meta.mjs +2 -0
- package/dist/runtime/nitro.d.mts +2 -0
- package/dist/runtime/nitro.mjs +4 -2
- package/dist/runtime/virtual/error-handler.mjs +1 -1
- package/dist/runtime/virtual/renderer-template.mjs +1 -1
- package/dist/runtime/virtual/server-assets.d.mts +2 -1
- package/dist/runtime/virtual/tracing.d.mts +5 -0
- package/dist/runtime/virtual/tracing.mjs +1 -0
- package/dist/runtime/vite.d.mts +1 -1
- package/dist/types/index.d.mts +215 -195
- package/dist/types/index.mjs +1 -11
- package/dist/vite.d.mts +3 -8
- package/dist/vite.mjs +285 -253
- package/lib/tsconfig.json +3 -5
- package/package.json +97 -80
- package/dist/_libs/confbox.mjs +0 -2509
- package/dist/_libs/nypm+giget+tinyexec.mjs +0 -3603
- package/dist/_libs/rc9+c12+dotenv.mjs +0 -972
- package/dist/node_modules/hookable/dist/index.mjs +0 -266
- package/dist/node_modules/hookable/package.json +0 -52
- package/dist/node_modules/youch/build/chunk-4L7RY2JA.js +0 -42
- package/dist/node_modules/youch/build/chunk-7QV3D5YX.js +0 -83
- package/dist/node_modules/youch/build/chunk-AUGPHE32.js +0 -39
- package/dist/node_modules/youch/build/chunk-CM7DWJNZ.js +0 -44
- package/dist/node_modules/youch/build/chunk-EJH674NB.js +0 -223
- package/dist/node_modules/youch/build/chunk-OIJ3WD7L.js +0 -81
- package/dist/node_modules/youch/build/chunk-P36L72PL.js +0 -87
- package/dist/node_modules/youch/build/chunk-PE3GG3TN.js +0 -65
- package/dist/node_modules/youch/build/chunk-X53OIOJH.js +0 -70
- package/dist/presets/_nitro/runtime/nitro-dev.d.mts +0 -2
- package/dist/presets/_nitro/runtime/nitro-prerenderer.d.mts +0 -3
- package/dist/presets/_nitro/runtime/service-worker.d.mts +0 -2
- package/dist/presets/aws-amplify/runtime/aws-amplify.d.mts +0 -2
- package/dist/presets/aws-lambda/runtime/_utils.d.mts +0 -8
- package/dist/presets/aws-lambda/runtime/aws-lambda-streaming.d.mts +0 -2
- package/dist/presets/aws-lambda/runtime/aws-lambda.d.mts +0 -3
- package/dist/presets/azure/runtime/_utils.d.mts +0 -2
- package/dist/presets/azure/runtime/azure-swa.d.mts +0 -5
- package/dist/presets/bun/runtime/bun.d.mts +0 -3
- package/dist/presets/cloudflare/runtime/_module-handler.d.mts +0 -16
- package/dist/presets/cloudflare/runtime/cloudflare-durable.d.mts +0 -11
- package/dist/presets/cloudflare/runtime/cloudflare-module.d.mts +0 -3
- package/dist/presets/cloudflare/runtime/cloudflare-pages.d.mts +0 -20
- package/dist/presets/cloudflare/runtime/plugin.dev.d.mts +0 -3
- package/dist/presets/deno/runtime/deno-deploy.d.mts +0 -5
- package/dist/presets/deno/runtime/deno-server.d.mts +0 -3
- package/dist/presets/netlify/runtime/netlify-edge.d.mts +0 -3
- package/dist/presets/netlify/runtime/netlify.d.mts +0 -4
- package/dist/presets/node/runtime/node-cluster.d.mts +0 -3
- package/dist/presets/node/runtime/node-middleware.d.mts +0 -3
- package/dist/presets/node/runtime/node-server.d.mts +0 -3
- package/dist/presets/standard/runtime/server.d.mts +0 -3
- package/dist/presets/stormkit/runtime/stormkit.d.mts +0 -21
- package/dist/presets/vercel/runtime/isr.d.mts +0 -2
- package/dist/presets/vercel/runtime/vercel.node.d.mts +0 -3
- package/dist/presets/vercel/runtime/vercel.web.d.mts +0 -8
- package/dist/presets/winterjs/runtime/winterjs.d.mts +0 -2
- package/dist/presets/zeabur/runtime/zeabur.d.mts +0 -3
- package/dist/runtime/internal/vite/node-runner.mjs +0 -359
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Vite SSR HTML
|
|
2
|
+
|
|
3
|
+
> Server-side rendering with vanilla HTML, Vite, and Nitro.
|
|
4
|
+
|
|
5
|
+
<code-tree>
|
|
6
|
+
|
|
7
|
+
```html [index.html]
|
|
8
|
+
<!doctype html>
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="UTF-8" />
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
13
|
+
<title>Nitro Quotes</title>
|
|
14
|
+
<style>
|
|
15
|
+
@import "tailwindcss";
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body
|
|
19
|
+
class="min-h-screen flex items-center justify-center p-5 bg-gradient-to-br from-indigo-500 to-purple-600 font-sans"
|
|
20
|
+
>
|
|
21
|
+
<div class="max-w-xl w-full text-center text-white">
|
|
22
|
+
<div class="bg-white/10 backdrop-blur-md rounded-2xl p-10 shadow-xl border border-white/20">
|
|
23
|
+
<div
|
|
24
|
+
id="quote"
|
|
25
|
+
class="text-[clamp(1.2rem,4vw,1.8rem)] leading-relaxed mb-5 font-light opacity-70 transition-opacity duration-500"
|
|
26
|
+
>
|
|
27
|
+
<!--ssr-outlet-->
|
|
28
|
+
</div>
|
|
29
|
+
<div
|
|
30
|
+
id="author"
|
|
31
|
+
class="text-[clamp(1rem,3vw,1.2rem)] opacity-0 font-normal transition-opacity duration-500"
|
|
32
|
+
></div>
|
|
33
|
+
<button
|
|
34
|
+
id="refresh-btn"
|
|
35
|
+
class="mt-5 bg-white/20 border border-white/30 text-white px-6 py-3 rounded-full cursor-pointer text-sm transition hover:bg-white/30 hover:-translate-y-0.5"
|
|
36
|
+
onclick="fetchQuote()"
|
|
37
|
+
>
|
|
38
|
+
New Quote
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="mt-8 text-sm opacity-60">
|
|
42
|
+
Powered by
|
|
43
|
+
<a
|
|
44
|
+
class="text-white no-underline border-b border-white/30 hover:border-white transition-colors"
|
|
45
|
+
href="https://vitejs.dev/"
|
|
46
|
+
>Vite</a
|
|
47
|
+
>
|
|
48
|
+
and
|
|
49
|
+
<a
|
|
50
|
+
class="text-white no-underline border-b border-white/30 hover:border-white transition-colors"
|
|
51
|
+
href="https://github.com/nitrojs/nitro"
|
|
52
|
+
>Nitro v3</a
|
|
53
|
+
>.
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<script>
|
|
58
|
+
const quoteElement = document.getElementById("quote");
|
|
59
|
+
const authorElement = document.getElementById("author");
|
|
60
|
+
const refreshBtn = document.getElementById("refresh-btn");
|
|
61
|
+
|
|
62
|
+
const baseQuoteClasses =
|
|
63
|
+
"text-[clamp(1.2rem,4vw,1.8rem)] leading-relaxed mb-5 font-light transition-opacity duration-500";
|
|
64
|
+
const loadingQuoteClasses = baseQuoteClasses + " opacity-70";
|
|
65
|
+
const normalQuoteClasses = baseQuoteClasses + " opacity-100";
|
|
66
|
+
const errorQuoteClasses = baseQuoteClasses + " text-red-400 opacity-100 text-sm";
|
|
67
|
+
|
|
68
|
+
const baseAuthorClasses =
|
|
69
|
+
"text-[clamp(1rem,3vw,1.2rem)] font-normal transition-opacity duration-500";
|
|
70
|
+
const hiddenAuthorClasses = baseAuthorClasses + " opacity-0";
|
|
71
|
+
const visibleAuthorClasses = baseAuthorClasses + " opacity-80";
|
|
72
|
+
|
|
73
|
+
async function fetchQuote() {
|
|
74
|
+
try {
|
|
75
|
+
quoteElement.textContent = "Loading...";
|
|
76
|
+
quoteElement.className = loadingQuoteClasses;
|
|
77
|
+
authorElement.textContent = "";
|
|
78
|
+
authorElement.className = hiddenAuthorClasses;
|
|
79
|
+
refreshBtn.style.display = "none";
|
|
80
|
+
const response = await fetch("/quote");
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
83
|
+
}
|
|
84
|
+
const { text, author } = await response.json();
|
|
85
|
+
quoteElement.textContent = `"${text}"`;
|
|
86
|
+
quoteElement.className = normalQuoteClasses;
|
|
87
|
+
authorElement.textContent = `— ${author}`;
|
|
88
|
+
authorElement.className = visibleAuthorClasses;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error("Error fetching quote:", error);
|
|
91
|
+
quoteElement.textContent = "Failed to load quote. Please try again.";
|
|
92
|
+
quoteElement.className = errorQuoteClasses;
|
|
93
|
+
authorElement.textContent = "";
|
|
94
|
+
authorElement.className = hiddenAuthorClasses;
|
|
95
|
+
} finally {
|
|
96
|
+
refreshBtn.style.display = "inline-block";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
</script>
|
|
100
|
+
</body>
|
|
101
|
+
</html>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```json [package.json]
|
|
105
|
+
{
|
|
106
|
+
"type": "module",
|
|
107
|
+
"scripts": {
|
|
108
|
+
"build": "vite build",
|
|
109
|
+
"dev": "vite dev",
|
|
110
|
+
"preview": "vite preview"
|
|
111
|
+
},
|
|
112
|
+
"devDependencies": {
|
|
113
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
114
|
+
"nitro": "latest",
|
|
115
|
+
"tailwindcss": "^4.2.2",
|
|
116
|
+
"vite": "latest"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```json [tsconfig.json]
|
|
122
|
+
{
|
|
123
|
+
"extends": "nitro/tsconfig"
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```ts [vite.config.ts]
|
|
128
|
+
import { defineConfig } from "vite";
|
|
129
|
+
import { nitro } from "nitro/vite";
|
|
130
|
+
|
|
131
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
132
|
+
|
|
133
|
+
export default defineConfig({
|
|
134
|
+
plugins: [
|
|
135
|
+
nitro({
|
|
136
|
+
serverDir: "./",
|
|
137
|
+
}),
|
|
138
|
+
tailwindcss(),
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```ts [app/entry-server.ts]
|
|
144
|
+
import { fetch } from "nitro";
|
|
145
|
+
|
|
146
|
+
export default {
|
|
147
|
+
async fetch() {
|
|
148
|
+
const quote = (await fetch("/quote").then((res) => res.json())) as {
|
|
149
|
+
text: string;
|
|
150
|
+
};
|
|
151
|
+
return tokenizedStream(quote.text, 50);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
function tokenizedStream(text: string, delay: number): ReadableStream<Uint8Array> {
|
|
156
|
+
const tokens = text.split(" ");
|
|
157
|
+
return new ReadableStream({
|
|
158
|
+
start(controller) {
|
|
159
|
+
let index = 0;
|
|
160
|
+
function push() {
|
|
161
|
+
if (index < tokens.length) {
|
|
162
|
+
const word = tokens[index++] + (index < tokens.length ? " " : "");
|
|
163
|
+
controller.enqueue(new TextEncoder().encode(word));
|
|
164
|
+
setTimeout(push, delay);
|
|
165
|
+
} else {
|
|
166
|
+
controller.close();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
push();
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```ts [routes/quote.ts]
|
|
176
|
+
const QUOTES_URL =
|
|
177
|
+
"https://github.com/JamesFT/Database-Quotes-JSON/raw/refs/heads/master/quotes.json";
|
|
178
|
+
|
|
179
|
+
let _quotes: Promise<unknown> | undefined;
|
|
180
|
+
|
|
181
|
+
function getQuotes() {
|
|
182
|
+
return (_quotes ??= fetch(QUOTES_URL).then((res) => res.json())) as Promise<
|
|
183
|
+
{ quoteText: string; quoteAuthor: string }[]
|
|
184
|
+
>;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default async function quotesHandler() {
|
|
188
|
+
const quotes = await getQuotes();
|
|
189
|
+
const randomQuote = quotes[Math.floor(Math.random() * quotes.length)];
|
|
190
|
+
return Response.json({
|
|
191
|
+
text: randomQuote.quoteText,
|
|
192
|
+
author: randomQuote.quoteAuthor,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
</code-tree>
|
|
197
|
+
|
|
198
|
+
This example renders an HTML template with server-side data and streams the response word by word. It demonstrates how to use Nitro's Vite SSR integration without a framework.
|
|
199
|
+
|
|
200
|
+
## Overview
|
|
201
|
+
|
|
202
|
+
1. **Add the Nitro Vite plugin** to enable SSR
|
|
203
|
+
2. **Create an HTML template** with a `<!--ssr-outlet-->` comment where server content goes
|
|
204
|
+
3. **Create a server entry** that fetches data and returns a stream
|
|
205
|
+
4. **Add API routes** for server-side data
|
|
206
|
+
|
|
207
|
+
## How It Works
|
|
208
|
+
The `index.html` file contains an `<!--ssr-outlet-->` comment that marks where server-rendered content will be inserted. Nitro replaces this comment with the output from your server entry.
|
|
209
|
+
|
|
210
|
+
The server entry exports an object with a `fetch` method. It calls the `/quote` API route using Nitro's internal fetch, then returns a `ReadableStream` that emits the quote text word by word with a 50ms delay between each word.
|
|
211
|
+
|
|
212
|
+
The quote route fetches a JSON file of quotes from GitHub, caches the result, and returns a random quote. The server entry calls this route to get content for the page.
|
|
213
|
+
|
|
214
|
+
## Learn More
|
|
215
|
+
|
|
216
|
+
- [Renderer](/docs/renderer)
|
|
217
|
+
- [Server Entry](/docs/server-entry)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# SSR with Preact
|
|
2
|
+
|
|
3
|
+
> Server-side rendering with Preact in Nitro using Vite.
|
|
4
|
+
|
|
5
|
+
<code-tree>
|
|
6
|
+
|
|
7
|
+
```json [package.json]
|
|
8
|
+
{
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "vite build",
|
|
12
|
+
"preview": "vite preview",
|
|
13
|
+
"dev": "vite dev"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@preact/preset-vite": "^2.10.5",
|
|
17
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
18
|
+
"nitro": "latest",
|
|
19
|
+
"preact": "^10.29.0",
|
|
20
|
+
"preact-render-to-string": "^6.6.7",
|
|
21
|
+
"tailwindcss": "^4.2.2",
|
|
22
|
+
"vite": "latest"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```json [tsconfig.json]
|
|
28
|
+
{
|
|
29
|
+
"extends": "nitro/tsconfig",
|
|
30
|
+
"compilerOptions": {
|
|
31
|
+
"jsx": "react-jsx",
|
|
32
|
+
"jsxImportSource": "preact"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```js [vite.config.mjs]
|
|
38
|
+
import { defineConfig } from "vite";
|
|
39
|
+
import { nitro } from "nitro/vite";
|
|
40
|
+
import preact from "@preact/preset-vite";
|
|
41
|
+
|
|
42
|
+
export default defineConfig({
|
|
43
|
+
plugins: [nitro(), preact()],
|
|
44
|
+
environments: {
|
|
45
|
+
client: {
|
|
46
|
+
build: {
|
|
47
|
+
rollupOptions: {
|
|
48
|
+
input: "./src/entry-client.tsx",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```tsx [src/app.tsx]
|
|
57
|
+
import { useState } from "preact/hooks";
|
|
58
|
+
|
|
59
|
+
export function App() {
|
|
60
|
+
const [count, setCount] = useState(0);
|
|
61
|
+
return <button onClick={() => setCount((c) => c + 1)}>Count is {count}</button>;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```tsx [src/entry-client.tsx]
|
|
66
|
+
import { hydrate } from "preact";
|
|
67
|
+
import { App } from "./app.tsx";
|
|
68
|
+
|
|
69
|
+
function main() {
|
|
70
|
+
hydrate(<App />, document.querySelector("#app")!);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
main();
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```tsx [src/entry-server.tsx]
|
|
77
|
+
import "./styles.css";
|
|
78
|
+
import { renderToReadableStream } from "preact-render-to-string/stream";
|
|
79
|
+
import { App } from "./app.jsx";
|
|
80
|
+
|
|
81
|
+
import clientAssets from "./entry-client?assets=client";
|
|
82
|
+
import serverAssets from "./entry-server?assets=ssr";
|
|
83
|
+
|
|
84
|
+
export default {
|
|
85
|
+
async fetch(request: Request) {
|
|
86
|
+
const url = new URL(request.url);
|
|
87
|
+
const htmlStream = renderToReadableStream(<Root url={url} />);
|
|
88
|
+
return new Response(htmlStream, {
|
|
89
|
+
headers: { "Content-Type": "text/html;charset=utf-8" },
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function Root(props: { url: URL }) {
|
|
95
|
+
const assets = clientAssets.merge(serverAssets);
|
|
96
|
+
return (
|
|
97
|
+
<html lang="en">
|
|
98
|
+
<head>
|
|
99
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
100
|
+
{assets.css.map((attr: any) => (
|
|
101
|
+
<link key={attr.href} rel="stylesheet" {...attr} />
|
|
102
|
+
))}
|
|
103
|
+
{assets.js.map((attr: any) => (
|
|
104
|
+
<link key={attr.href} type="modulepreload" {...attr} />
|
|
105
|
+
))}
|
|
106
|
+
<script type="module" src={assets.entry} />
|
|
107
|
+
</head>
|
|
108
|
+
<body>
|
|
109
|
+
<h1 className="hero">Nitro + Vite + Preact</h1>
|
|
110
|
+
<p>URL: {props.url.href}</p>
|
|
111
|
+
<div id="app">
|
|
112
|
+
<App />
|
|
113
|
+
</div>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```css [src/styles.css]
|
|
121
|
+
.hero {
|
|
122
|
+
color: orange;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
button {
|
|
126
|
+
background-color: lightskyblue;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
</code-tree>
|
|
130
|
+
|
|
131
|
+
Set up server-side rendering (SSR) with Preact, Vite, and Nitro. This setup enables streaming HTML responses, automatic asset management, and client hydration.
|
|
132
|
+
|
|
133
|
+
## Overview
|
|
134
|
+
|
|
135
|
+
1. Add the Nitro Vite plugin to your Vite config
|
|
136
|
+
2. Configure client and server entry points
|
|
137
|
+
3. Create a server entry that renders your app to HTML
|
|
138
|
+
4. Create a client entry that hydrates the server-rendered HTML
|
|
139
|
+
|
|
140
|
+
## 1. Configure Vite
|
|
141
|
+
Add the Nitro and Preact plugins to your Vite config. Define the `client` environment with your client entry point:
|
|
142
|
+
|
|
143
|
+
```js [vite.config.mjs]
|
|
144
|
+
import { defineConfig } from "vite";
|
|
145
|
+
import { nitro } from "nitro/vite";
|
|
146
|
+
import preact from "@preact/preset-vite";
|
|
147
|
+
|
|
148
|
+
export default defineConfig({
|
|
149
|
+
plugins: [nitro(), preact()],
|
|
150
|
+
environments: {
|
|
151
|
+
client: {
|
|
152
|
+
build: {
|
|
153
|
+
rollupOptions: {
|
|
154
|
+
input: "./src/entry-client.tsx",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The `environments.client` configuration tells Vite which file to use as the browser entry point. Nitro automatically detects the server entry from files named `entry-server` or `server` in common directories.
|
|
163
|
+
|
|
164
|
+
## 2. Create the App Component
|
|
165
|
+
|
|
166
|
+
Create a shared Preact component that runs on both server and client:
|
|
167
|
+
|
|
168
|
+
```tsx [src/app.tsx]
|
|
169
|
+
import { useState } from "preact/hooks";
|
|
170
|
+
|
|
171
|
+
export function App() {
|
|
172
|
+
const [count, setCount] = useState(0);
|
|
173
|
+
return <button onClick={() => setCount((c) => c + 1)}>Count is {count}</button>;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 3. Create the Server Entry
|
|
178
|
+
|
|
179
|
+
The server entry renders your Preact app to a streaming HTML response using `preact-render-to-string/stream`:
|
|
180
|
+
|
|
181
|
+
```tsx [src/entry-server.tsx]
|
|
182
|
+
import "./styles.css";
|
|
183
|
+
import { renderToReadableStream } from "preact-render-to-string/stream";
|
|
184
|
+
import { App } from "./app.jsx";
|
|
185
|
+
|
|
186
|
+
import clientAssets from "./entry-client?assets=client";
|
|
187
|
+
import serverAssets from "./entry-server?assets=ssr";
|
|
188
|
+
|
|
189
|
+
export default {
|
|
190
|
+
async fetch(request: Request) {
|
|
191
|
+
const url = new URL(request.url);
|
|
192
|
+
const htmlStream = renderToReadableStream(<Root url={url} />);
|
|
193
|
+
return new Response(htmlStream, {
|
|
194
|
+
headers: { "Content-Type": "text/html;charset=utf-8" },
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
function Root(props: { url: URL }) {
|
|
200
|
+
const assets = clientAssets.merge(serverAssets);
|
|
201
|
+
return (
|
|
202
|
+
<html lang="en">
|
|
203
|
+
<head>
|
|
204
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
205
|
+
{assets.css.map((attr: any) => (
|
|
206
|
+
<link key={attr.href} rel="stylesheet" {...attr} />
|
|
207
|
+
))}
|
|
208
|
+
{assets.js.map((attr: any) => (
|
|
209
|
+
<link key={attr.href} type="modulepreload" {...attr} />
|
|
210
|
+
))}
|
|
211
|
+
<script type="module" src={assets.entry} />
|
|
212
|
+
</head>
|
|
213
|
+
<body>
|
|
214
|
+
<h1 className="hero">Nitro + Vite + Preact</h1>
|
|
215
|
+
<p>URL: {props.url.href}</p>
|
|
216
|
+
<div id="app">
|
|
217
|
+
<App />
|
|
218
|
+
</div>
|
|
219
|
+
</body>
|
|
220
|
+
</html>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Import assets using the `?assets=client` and `?assets=ssr` query parameters. Nitro collects CSS and JS assets from each entry point, and `merge()` combines them into a single manifest. The `assets` object provides arrays of stylesheet and script attributes, plus the client entry URL. Use `renderToReadableStream` to stream HTML as Preact renders, improving time-to-first-byte.
|
|
226
|
+
|
|
227
|
+
## 4. Create the Client Entry
|
|
228
|
+
|
|
229
|
+
The client entry hydrates the server-rendered HTML, attaching Preact's event handlers:
|
|
230
|
+
|
|
231
|
+
```tsx [src/entry-client.tsx]
|
|
232
|
+
import { hydrate } from "preact";
|
|
233
|
+
import { App } from "./app.tsx";
|
|
234
|
+
|
|
235
|
+
function main() {
|
|
236
|
+
hydrate(<App />, document.querySelector("#app")!);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
main();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The `hydrate` function attaches Preact to the existing server-rendered DOM inside `#app` without re-rendering it.
|
|
243
|
+
|
|
244
|
+
## Learn More
|
|
245
|
+
|
|
246
|
+
- [Renderer](/docs/renderer)
|
|
247
|
+
- [Server Entry](/docs/server-entry)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# SSR with React
|
|
2
|
+
|
|
3
|
+
> Server-side rendering with React in Nitro using Vite.
|
|
4
|
+
|
|
5
|
+
<code-tree>
|
|
6
|
+
|
|
7
|
+
```json [package.json]
|
|
8
|
+
{
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "vite build",
|
|
12
|
+
"preview": "vite preview",
|
|
13
|
+
"dev": "vite dev"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^19.2.14",
|
|
17
|
+
"@types/react-dom": "^19.2.3",
|
|
18
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
19
|
+
"nitro": "latest",
|
|
20
|
+
"react": "^19.2.4",
|
|
21
|
+
"react-dom": "^19.2.4",
|
|
22
|
+
"react-refresh": "^0.18.0",
|
|
23
|
+
"vite": "latest"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```json [tsconfig.json]
|
|
29
|
+
{
|
|
30
|
+
"extends": "nitro/tsconfig",
|
|
31
|
+
"compilerOptions": {
|
|
32
|
+
"jsx": "react-jsx",
|
|
33
|
+
"jsxImportSource": "react"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```js [vite.config.mjs]
|
|
39
|
+
import { defineConfig } from "vite";
|
|
40
|
+
import { nitro } from "nitro/vite";
|
|
41
|
+
import react from "@vitejs/plugin-react";
|
|
42
|
+
|
|
43
|
+
export default defineConfig({
|
|
44
|
+
plugins: [nitro(), react()],
|
|
45
|
+
environments: {
|
|
46
|
+
client: {
|
|
47
|
+
build: { rollupOptions: { input: "./src/entry-client.tsx" } },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```tsx [src/app.tsx]
|
|
54
|
+
import { useState } from "react";
|
|
55
|
+
|
|
56
|
+
export function App() {
|
|
57
|
+
const [count, setCount] = useState(0);
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
<h1 className="hero">Nitro + Vite + React</h1>
|
|
61
|
+
<button onClick={() => setCount((c) => c + 1)}>Count is {count}</button>
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```tsx [src/entry-client.tsx]
|
|
68
|
+
import "@vitejs/plugin-react/preamble";
|
|
69
|
+
import { hydrateRoot } from "react-dom/client";
|
|
70
|
+
import { App } from "./app.tsx";
|
|
71
|
+
|
|
72
|
+
hydrateRoot(document.querySelector("#app")!, <App />);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```tsx [src/entry-server.tsx]
|
|
76
|
+
import "./styles.css";
|
|
77
|
+
import { renderToReadableStream } from "react-dom/server.edge";
|
|
78
|
+
import { App } from "./app.tsx";
|
|
79
|
+
|
|
80
|
+
import clientAssets from "./entry-client?assets=client";
|
|
81
|
+
import serverAssets from "./entry-server?assets=ssr";
|
|
82
|
+
|
|
83
|
+
export default {
|
|
84
|
+
async fetch(_req: Request) {
|
|
85
|
+
const assets = clientAssets.merge(serverAssets);
|
|
86
|
+
return new Response(
|
|
87
|
+
await renderToReadableStream(
|
|
88
|
+
<html lang="en">
|
|
89
|
+
<head>
|
|
90
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
91
|
+
{assets.css.map((attr: any) => (
|
|
92
|
+
<link key={attr.href} rel="stylesheet" {...attr} />
|
|
93
|
+
))}
|
|
94
|
+
{assets.js.map((attr: any) => (
|
|
95
|
+
<link key={attr.href} type="modulepreload" {...attr} />
|
|
96
|
+
))}
|
|
97
|
+
<script type="module" src={assets.entry} />
|
|
98
|
+
</head>
|
|
99
|
+
<body id="app">
|
|
100
|
+
<App />
|
|
101
|
+
</body>
|
|
102
|
+
</html>
|
|
103
|
+
),
|
|
104
|
+
{ headers: { "Content-Type": "text/html;charset=utf-8" } }
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```css [src/styles.css]
|
|
111
|
+
.hero {
|
|
112
|
+
color: orange;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
button {
|
|
116
|
+
background-color: lightskyblue;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
</code-tree>
|
|
120
|
+
|
|
121
|
+
Set up server-side rendering (SSR) with React, Vite, and Nitro. This setup enables streaming HTML responses, automatic asset management, and client hydration.
|
|
122
|
+
|
|
123
|
+
## Overview
|
|
124
|
+
|
|
125
|
+
1. Add the Nitro Vite plugin to your Vite config
|
|
126
|
+
2. Configure client and server entry points
|
|
127
|
+
3. Create a server entry that renders your app to HTML
|
|
128
|
+
4. Create a client entry that hydrates the server-rendered HTML
|
|
129
|
+
|
|
130
|
+
## 1. Configure Vite
|
|
131
|
+
Add the Nitro and React plugins to your Vite config. Define the `client` environment with your client entry point:
|
|
132
|
+
|
|
133
|
+
```js [vite.config.mjs]
|
|
134
|
+
import { defineConfig } from "vite";
|
|
135
|
+
import { nitro } from "nitro/vite";
|
|
136
|
+
import react from "@vitejs/plugin-react";
|
|
137
|
+
|
|
138
|
+
export default defineConfig({
|
|
139
|
+
plugins: [nitro(), react()],
|
|
140
|
+
environments: {
|
|
141
|
+
client: {
|
|
142
|
+
build: { rollupOptions: { input: "./src/entry-client.tsx" } },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The `environments.client` configuration tells Vite which file to use as the browser entry point. Nitro automatically detects the server entry from files named `entry-server` or `server` in common directories.
|
|
149
|
+
|
|
150
|
+
## 2. Create the App Component
|
|
151
|
+
|
|
152
|
+
Create a shared React component that runs on both server and client:
|
|
153
|
+
|
|
154
|
+
```tsx [src/app.tsx]
|
|
155
|
+
import { useState } from "react";
|
|
156
|
+
|
|
157
|
+
export function App() {
|
|
158
|
+
const [count, setCount] = useState(0);
|
|
159
|
+
return (
|
|
160
|
+
<>
|
|
161
|
+
<h1 className="hero">Nitro + Vite + React</h1>
|
|
162
|
+
<button onClick={() => setCount((c) => c + 1)}>Count is {count}</button>
|
|
163
|
+
</>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 3. Create the Server Entry
|
|
169
|
+
|
|
170
|
+
The server entry renders your React app to a streaming HTML response. It uses `react-dom/server.edge` for edge-compatible streaming:
|
|
171
|
+
|
|
172
|
+
```tsx [src/entry-server.tsx]
|
|
173
|
+
import "./styles.css";
|
|
174
|
+
import { renderToReadableStream } from "react-dom/server.edge";
|
|
175
|
+
import { App } from "./app.tsx";
|
|
176
|
+
|
|
177
|
+
import clientAssets from "./entry-client?assets=client";
|
|
178
|
+
import serverAssets from "./entry-server?assets=ssr";
|
|
179
|
+
|
|
180
|
+
export default {
|
|
181
|
+
async fetch(_req: Request) {
|
|
182
|
+
const assets = clientAssets.merge(serverAssets);
|
|
183
|
+
return new Response(
|
|
184
|
+
await renderToReadableStream(
|
|
185
|
+
<html lang="en">
|
|
186
|
+
<head>
|
|
187
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
188
|
+
{assets.css.map((attr: any) => (
|
|
189
|
+
<link key={attr.href} rel="stylesheet" {...attr} />
|
|
190
|
+
))}
|
|
191
|
+
{assets.js.map((attr: any) => (
|
|
192
|
+
<link key={attr.href} type="modulepreload" {...attr} />
|
|
193
|
+
))}
|
|
194
|
+
<script type="module" src={assets.entry} />
|
|
195
|
+
</head>
|
|
196
|
+
<body id="app">
|
|
197
|
+
<App />
|
|
198
|
+
</body>
|
|
199
|
+
</html>
|
|
200
|
+
),
|
|
201
|
+
{ headers: { "Content-Type": "text/html;charset=utf-8" } }
|
|
202
|
+
);
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Import assets using the `?assets=client` and `?assets=ssr` query parameters. Nitro collects CSS and JS assets from each entry point, and `merge()` combines them into a single manifest. The `assets` object provides arrays of stylesheet and script attributes, plus the client entry URL. Use `renderToReadableStream` to stream HTML as React renders, improving time-to-first-byte.
|
|
208
|
+
|
|
209
|
+
## 4. Create the Client Entry
|
|
210
|
+
|
|
211
|
+
The client entry hydrates the server-rendered HTML, attaching React's event handlers:
|
|
212
|
+
|
|
213
|
+
```tsx [src/entry-client.tsx]
|
|
214
|
+
import "@vitejs/plugin-react/preamble";
|
|
215
|
+
import { hydrateRoot } from "react-dom/client";
|
|
216
|
+
import { App } from "./app.tsx";
|
|
217
|
+
|
|
218
|
+
hydrateRoot(document.querySelector("#app")!, <App />);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The `@vitejs/plugin-react/preamble` import is required for React Fast Refresh during development. The `hydrateRoot` function attaches React to the existing server-rendered DOM without re-rendering it.
|
|
222
|
+
|
|
223
|
+
## Learn More
|
|
224
|
+
|
|
225
|
+
- [Renderer](/docs/renderer)
|
|
226
|
+
- [Server Entry](/docs/server-entry)
|