-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
base: main
Are you sure you want to change the base?
docs: how-to set up SSR #4703
Conversation
Co-authored-by: tannerlinsley <[email protected]>
Co-authored-by: tannerlinsley <[email protected]>
Co-authored-by: tannerlinsley <[email protected]>
Co-authored-by: tannerlinsley <[email protected]>
Co-authored-by: tannerlinsley <[email protected]>
Co-authored-by: tannerlinsley <[email protected]>
Co-authored-by: tannerlinsley <[email protected]>
View your CI Pipeline Execution ↗ for commit 38853d0
☁️ Nx Cloud last updated this comment at |
More templates
@tanstack/arktype-adapter
@tanstack/directive-functions-plugin
@tanstack/eslint-plugin-router
@tanstack/history
@tanstack/react-router
@tanstack/react-router-devtools
@tanstack/react-router-with-query
@tanstack/react-start
@tanstack/react-start-client
@tanstack/react-start-plugin
@tanstack/react-start-server
@tanstack/router-cli
@tanstack/router-core
@tanstack/router-devtools
@tanstack/router-devtools-core
@tanstack/router-generator
@tanstack/router-plugin
@tanstack/router-utils
@tanstack/router-vite-plugin
@tanstack/server-functions-plugin
@tanstack/solid-router
@tanstack/solid-router-devtools
@tanstack/solid-start
@tanstack/solid-start-client
@tanstack/solid-start-plugin
@tanstack/solid-start-server
@tanstack/start-client-core
@tanstack/start-plugin-core
@tanstack/start-server-core
@tanstack/start-server-functions-client
@tanstack/start-server-functions-fetcher
@tanstack/start-server-functions-server
@tanstack/valibot-adapter
@tanstack/virtual-file-routes
@tanstack/zod-adapter
commit: |
we already have this guide: https://tanstack.com/router/latest/docs/framework/react/guide/ssr merge them? keep only one? |
We need to either make the guide more like a “learn” thing and reference the how-to or just get rid of the guide and redirect if it’s overlapped a lot.
…On Jul 18, 2025 at 5:47 PM -0600, Manuel Schiller ***@***.***>, wrote:
schiller-manuel left a comment (TanStack/router#4703)
we already have this guide: https://tanstack.com/router/latest/docs/framework/react/guide/ssr
merge them? keep only one?
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
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. |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 | ||
``` | ||
|
There was a problem hiding this comment.
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
} | ||
} | ||
``` | ||
|
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
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."
}, | ||
}) | ||
``` | ||
|
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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}'....
"build:client": "vite build --outDir dist/client", | ||
"build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server", |
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
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:
- During development our express server will serve the app using the vite dev server using vite middleware mode.
- Separate build processes are used for client and server bundles.
- In production, it will be served over the express server directly."
```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' | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
``` |
There was a problem hiding this comment.
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,
};
});
No description provided.