Skip to content

docs: how-to set up SSR #4703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

tannerlinsley
Copy link
Collaborator

No description provided.

@github-actions github-actions bot added the documentation Everything documentation related label Jul 18, 2025
Copy link

nx-cloud bot commented Jul 18, 2025

View your CI Pipeline Execution ↗ for commit 38853d0

Command Status Duration Result
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 3s View ↗
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2025-07-20 18:09:14 UTC

Copy link

pkg-pr-new bot commented Jul 18, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@4703

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@4703

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@4703

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@4703

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@4703

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@4703

@tanstack/react-router-with-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-with-query@4703

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@4703

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@4703

@tanstack/react-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-plugin@4703

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@4703

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@4703

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@4703

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@4703

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@4703

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@4703

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@4703

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@4703

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@4703

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@4703

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@4703

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@4703

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@4703

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@4703

@tanstack/solid-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-plugin@4703

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@4703

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@4703

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@4703

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@4703

@tanstack/start-server-functions-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-client@4703

@tanstack/start-server-functions-fetcher

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-fetcher@4703

@tanstack/start-server-functions-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-server@4703

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@4703

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@4703

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@4703

commit: 38853d0

@tannerlinsley tannerlinsley changed the title Work on next how-to guide docs: how-to set up SSR Jul 18, 2025
@schiller-manuel
Copy link
Contributor

we already have this guide: https://tanstack.com/router/latest/docs/framework/react/guide/ssr

merge them? keep only one?

@schiller-manuel schiller-manuel requested a review from nlynzaad July 18, 2025 23:47
@tannerlinsley
Copy link
Collaborator Author

tannerlinsley commented Jul 18, 2025 via email

@nlynzaad
Copy link
Contributor

Had a quick look through. A lot of the examples were copied into this, which is great as it results in a "what you read is what you see" situation.

The original guide was very much a guide on the tools that are provided to implement SSR without making any assumption on server and/or bundle configurations.

This is definitely a lot more of a how-to guide for an express/vite implementation.

The biggest concern I have is the fact that the how-to is built with non streaming ssr in mind, the vite config also reflects this and does not really touch on the streaming options. The renderToStream function is mentioned but as a "performance enhancement" but then lacks the explanation about changes that are required to the vite config for this. There are other minor differences in the implementations.

Will go through it in a bit more depth a bit later and see where I can tweak it.

Copy link
Contributor

@nlynzaad nlynzaad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple of points flagged mainly to align the how-to with the examples. I have all proposed changes in a commit on my local, just having an issue pushing the updates.

## Quick Start with TanStack Start

```bash
npx create-tsrouter-app@latest my-app --add-ons=start
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't need start anymore. a better starting point would probably be:

npx create-tsrouter-app@latest my-app --template file-router

cd my-app
npm run dev
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably a good idea to add section for express dependencies:

Install dependencies

To server render the content, we will require a web server instance. In this guide we will be using express as our server, let us install these dependencies so long.

npm i express compression
npm i --save-dev @types/express

}
}
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add code entry for routerContext.tsx

// src/routerContext.tsx
export type RouterContext = {
	head: string
}

```

### 2. Set Up Server Entry Point

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably helpfull to add some explanation regarding difference between server and client entry points:

"When a new request is received, the server entry point will be responsible for rendering the content on the first render."

```

### 3. Set Up Client Entry Point

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

following on from comment above on R43.

"After the initial server rendering has completed, subsequent renders will be done on the client using the client entry point."

},
})
```

Copy link
Contributor

@nlynzaad nlynzaad Jul 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the above point, adding a section for updates to the root route to align the document to the examples:

5. Update our root route

Since the initial HTML document will be rendered on the server before being sent to the client, we can use the root route to provide all our HTML needs that we would usually include in our index.html.

//src/routes/__root.tsx
import type { RouterContext } from "@/routerContext";
import {
	HeadContent,
	Outlet,
	createRootRouteWithContext,
} from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import appCss from "../App.css?url";

export const Route = createRootRouteWithContext<RouterContext>()({
	head: () => ({
		links: [
			{ rel: "icon", href: "/favicon.ico" },
			{ rel: "apple-touch-icon", href: "/logo192.png" },
			{ rel: "manifest", href: "/manifest.json" },
			{ rel: "stylesheet", href: appCss },
		],
		meta: [
			{
				name: "theme-color",
				content: "#000000",
			},
			{
				title: "TanStack Router SSR File Based",
			},
			{
				charSet: "UTF-8",
			},
			{
				name: "viewport",
				content: "width=device-width, initial-scale=1.0",
			},
		],
		scripts: [
			...(!import.meta.env.PROD
				? [
					{
						type: "module",
						children: `import RefreshRuntime from "/@react-refresh"
  							RefreshRuntime.injectIntoGlobalHook(window)
  							window.$RefreshReg$ = () => {}
 							window.$RefreshSig$ = () => (type) => type
  							window.__vite_plugin_react_preamble_installed__ = true`,
					},
					{
						type: "module",
						src: "/@vite/client",
					},
				]
				: []),
			{
				type: "module",
				src: import.meta.env.PROD
					? "/entry-client.js"
					: "/src/entry-client.tsx",
			},
		],
	}),
	component: RootComponent,
});

function RootComponent() {
	return (
		<html lang="en">
			<head>
				<HeadContent />
			</head>
			<body>
				<Outlet />
				<TanStackRouterDevtools />
			</body>
		</html>
	);
}

Now we can remove safely index.html and main.tsx

app.use(express.static('./dist/client'))
}

app.use('*', async (req, res) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

express 5 has a breaking change in this case this should now read:

app.use('/{*splat}'....

Comment on lines 246 to 247
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the change in the vite.config.ts this can be updated to:

	"build:client": "vite build",
	"build:server": "vite build --ssr",

```

### 6. Update Package Scripts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can add a description here of the various scripts' purpose:

"The below update ensures that:

  1. During development our express server will serve the app using the vite dev server using vite middleware mode.
  2. Separate build processes are used for client and server bundles.
  3. In production, it will be served over the express server directly."

Comment on lines 352 to 371
```ts
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
input: {
main: './index.html',
server: './src/entry-server.tsx',
},
output: {
entryFileNames: (chunkInfo) => {
return chunkInfo.name === 'server'
? '[name].js'
: 'assets/[name]-[hash].js'
},
},
},
},
})
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given above vite.config.ts change:

// vite.config.ts

// SSR configuration
const ssrBuildConfig: BuildEnvironmentOptions = {
    // server specific config is specified here 
	rollupOptions: {
		input: path.resolve(__dirname, "src/entry-server.tsx"),
		output: {
			entryFileNames: "[name].js",
			chunkFileNames: "assets/[name]-[hash].js",
			assetFileNames: "assets/[name]-[hash][extname]",
		},
	},
};

// Client-specific configuration
const clientBuildConfig: BuildEnvironmentOptions = {
    // client specific config is specified here
	rollupOptions: {
		input: path.resolve(__dirname, "src/entry-client.tsx"),
		output: {
			entryFileNames: "[name].js",
			chunkFileNames: "assets/[name]-[hash].js",
			assetFileNames: "assets/[name]-[hash][extname]",
		},
	},
};

export default defineConfig((configEnv) => {
	return {
		// global config is specified here
		build: configEnv.isSsrBuild ? ssrBuildConfig : clientBuildConfig,
	};
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Everything documentation related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants