Skip to content

TanStack Start SSR

Copy it into a new project:

Terminal window
pnpm dlx degit antstanley/blogwright/examples/tanstack-start-ssr my-site

TanStack Start (React) built through Nitro’s node-server preset, running on a Lambda Web Function. Fingerprinted /assets/ still serve from S3.

Framework Mode Blogwright config Source
TanStack Start (React) + Nitro Server-rendered ssr examples/tanstack-start-ssr
  1. Copy the example and start a repository:

    Terminal window
    pnpm dlx degit antstanley/blogwright/examples/tanstack-start-ssr my-site
    cd my-site && git init
  2. Set siteName and region in config/production.jsonc. For SSR, region must be us-east-1, us-east-2, us-west-2, ap-northeast-1 or eu-west-1.

  3. Add Blogwright, then commit everything, including pnpm-lock.yaml - the builder installs with --frozen-lockfile:

    Terminal window
    pnpm add -D blogwright
    git add -A && git commit -m "Start from the blogwright example"
  4. Create the infrastructure: pnpm exec blogwright bootstrap

  5. Build and publish: pnpm exec blogwright deploy

  • vite.config.ts - the tanstackStart() plugin, then nitro({ preset: 'node-server' }), which writes a standalone server to .output/server and the browser assets to .output/public, then React’s plugin.
  • src/routes/index.tsx - a server function supplies the render timestamp, so the page proves it ran on the server.
  • lambda.mjs - the entry shim. It maps AWS_LAMBDA_HTTP_ENDPOINT onto NITRO_HOST and NITRO_PORT, then imports index.mjs. The build copies it into .output/server/.
  • pnpm-workspace.yaml - allowBuilds approves esbuild’s install script, which pnpm 11 otherwise fails the install on.
  • config/production.jsonc - packages .output/server. staticDir and staticPaths publish the fingerprinted /assets/ to S3.
config/production.jsonc
// Blogwright config for this example. Paths are relative to the repository
// root, so they assume this directory IS the repository - see README.md.
{
"region": "us-east-1",
"siteName": "tanstack-start-ssr",
"ssr": {
"packageDir": ".output/server",
"entryPoint": "lambda.mjs",
"staticDir": ".output/public",
"staticPaths": ["/assets/*", "/favicon.svg"],
},
}
vite.config.ts
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import viteReact from '@vitejs/plugin-react';
import { nitro } from 'nitro/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
tanstackStart(),
// Nitro's node-server preset writes a standalone Node server to
// .output/server and the browser assets to .output/public.
nitro({ preset: 'node-server' }),
// React's plugin must come after Start's.
viteReact(),
],
});
lambda.mjs
// Entry shim for Lambda Web Functions. Lambda passes the listen address in
// AWS_LAMBDA_HTTP_ENDPOINT; Nitro's node-server reads NITRO_HOST and NITRO_PORT.
const endpoint = process.env.AWS_LAMBDA_HTTP_ENDPOINT;
const colon = endpoint.lastIndexOf(':');
process.env.NITRO_HOST = endpoint.slice(0, colon);
process.env.NITRO_PORT = endpoint.slice(colon + 1);
await import('./index.mjs');
pnpm-workspace.yaml
# A workspace of its own, so pnpm treats this directory as the project root
# even inside the blogwright repository.
packages:
- .
allowBuilds:
# esbuild's postinstall installs its platform binary.
esbuild: true
package.json
{
"name": "blogwright-example-tanstack-start-ssr",
"private": true,
"type": "module",
"packageManager": "pnpm@11.25.0",
"scripts": {
"dev": "vite dev",
"build": "vite build && cp lambda.mjs .output/server/"
},
"dependencies": {
"@tanstack/react-router": "^1.170.44",
"@tanstack/react-start": "^1.169.1",
"react": "^19.3.0",
"react-dom": "^19.3.0"
},
"devDependencies": {
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.1.2",
"nitro": "3.0.260903-beta",
"vite": "^8.3.4"
}
}

Run it the way Lambda does - from a copy outside the project, so nothing resolves from its own node_modules:

Terminal window
pnpm build
cp -R .output/server /tmp/pkg && cd /tmp/pkg
AWS_LAMBDA_HTTP_ENDPOINT=127.0.0.1:8080 node lambda.mjs
curl -i http://127.0.0.1:8080/

The server must start listening within 10 seconds, and find .output/server -type l must print nothing: the build agent rejects a package that contains a symbolic link.