Server-side rendering means a Node process runs your component tree on every single request. That is the whole idea, and it is also the entire hosting consequence.
The documentation explains SSR in terms of what it does for the user: HTML arrives filled in, so the first paint has content and crawlers see a real page. That part is well covered. What gets skipped is the other side of the deal, which is that your deployment is now a long-lived server rather than a folder of files, and everything about hosting changes with it.
Table of contents
- What SSR actually does on each request
- getServerSideProps and the App Router equivalent
- SSR, SSG, and ISR side by side
- The hosting consequence: a process, not a bucket
- Where SSR gets slow, and what to do
- Deploying a Next.js app that renders on the server
- How this fits the rest of the stack
- FAQ
What SSR actually does on each request
A request arrives. Your server runs the route’s data fetching, waits for whatever it calls, renders the React tree to an HTML string, serialises the props alongside it, and sends the result. The browser paints that HTML immediately, then downloads the JavaScript bundle and hydrates, attaching event handlers to markup that is already on screen.
Two things follow from this that people underestimate. The first is that your render is only as fast as the slowest thing it awaits, because the user sees nothing until the whole render completes. A 400ms database query is 400ms of blank screen, every time, for every visitor.
The second is that this work is not cached by default. A static page is rendered once at build; an SSR page is rendered once per request. Ten thousand visitors means ten thousand renders, ten thousand sets of data fetches, and CPU time that scales linearly with traffic.
getServerSideProps and the App Router equivalent
In the Pages Router, SSR is opt-in per page by exporting an async getServerSideProps. It runs on the server on every request and its return value becomes the page’s props.
export default function Page({ data }) {
return <main>{data.title}</main>
}
export async function getServerSideProps(context) {
const res = await fetch('https://api.example.com/item/' + context.params.id)
if (!res.ok) return { notFound: true }
const data = await res.json()
return { props: { data } }
}
In the App Router the mechanism is different and the mental model is cleaner. Components are server components by default; you fetch directly inside them, and the route becomes dynamic when you use something request-specific such as cookies, headers, or search params, or when you opt out of caching explicitly.
export const dynamic = 'force-dynamic'
export default async function Page({ params }) {
const res = await fetch('https://api.example.com/item/' + params.id, {
cache: 'no-store',
})
const data = await res.json()
return <main>{data.title}</main>
}
There is no getServerSideProps in the App Router and there will not be one. If you are porting a codebase, the translation is: move the fetch into the component and decide, deliberately, what the caching behaviour should be.
SSR, SSG, and ISR side by side
Next.js gives you three rendering strategies and they are not interchangeable. Picking wrongly is the source of most Next.js hosting complaints.
- Static generation. HTML built once at build time. Serves from a CDN, costs nothing per request, and cannot show anything that varies per user. Correct for marketing pages, docs, and blogs.
- Incremental static regeneration. Static, but the server rebuilds a page in the background after a revalidation window. Fresh-enough content at near-static cost. Correct for content that changes hourly rather than per request.
- Server-side rendering. Rendered per request. The only option when the output genuinely depends on who is asking. Correct for dashboards, authenticated views, and anything reading a session.
The strong opinion here: most pages built as SSR should be ISR. Teams reach for the dynamic escape hatch because it always works and never surprises them, and then pay for a render on every request to show content that changes twice a day. If the page would look identical to two anonymous visitors thirty seconds apart, it does not need SSR.
The hosting consequence: a process, not a bucket
A fully static Next.js export is a directory. Any static host serves it, bandwidth is the only variable, and there is nothing to restart. The moment one route renders on the server, that stops being true and you need somewhere that runs Node continuously.
- A running process, not a build artifact.
next buildthennext start, or a container doing the same. Something has to keep it alive and restart it when it dies. - Memory that scales with concurrency. Each in-flight render holds a component tree and its fetched data. Concurrency times payload size is your memory floor, and Node gets killed by the OOM reaper if you guess low.
- CPU that scales with traffic. React rendering to a string is real CPU work. It is the reason SSR apps hit capacity on CPU long before they run out of bandwidth.
- A health check and a restart policy. A static bucket cannot crash. A Node process can, and you want it back without a human involved.
None of this is exotic; it is ordinary application hosting. The mistake is assuming a Next.js app is a frontend deployment when it is a backend service that happens to emit HTML.
Where SSR gets slow, and what to do
Four causes account for nearly all slow SSR pages, in roughly this order.
- Waterfalled fetches. Awaiting one request, then using its result to start the next. Two 200ms calls in series are 400ms of blank page. Run independent fetches together and the same page costs 200ms.
- Uncached upstream calls. Every render hitting the same API for the same rarely-changing data. Set a revalidation window on the fetch and the second visitor gets it free.
- Database round trips from the render path. Each query adds its latency directly to time-to-first-byte. Keep the render’s query count small and the connection pool warm.
- Rendering the whole page before sending anything. Streaming with Suspense boundaries lets the shell go out immediately while slow sections fill in, which changes perceived speed enormously even when total time is unchanged.
Measure before optimising. Time-to-first-byte on an SSR route is almost entirely your data fetching; if TTFB is fine and the page still feels slow, the problem is bundle size and hydration, which is a different fix in a different place.
Deploying a Next.js app that renders on the server
The deployment itself is unremarkable once you accept it is a Node service. Build, then run, with the port taken from the environment.
{
"scripts": {
"build": "next build",
"start": "next start -p 3000"
}
}
For a container, setting the standalone output mode in next.config.js is worth turning on. It emits a minimal server bundle with only the dependencies actually reached, which cuts image size substantially and speeds up cold starts.
- Bind to all interfaces and read the port from the environment. Binding to localhost inside a container is the classic reason a healthy app looks dead from outside.
- Set
NODE_ENV=productionso React skips development warnings and Next serves the optimised build. - Give the platform a health check path that does not hit your database, so a slow query does not get your app restarted.
- Keep build-time and runtime environment variables straight. Anything inlined at build time with the public prefix is baked into the bundle and cannot be changed by restarting.
With those in place an SSR deployment is as boring as any other web service, which is exactly the goal.
How this fits the rest of the stack
The practical decision an SSR app forces is a sizing one: how much CPU and memory does a per-request render actually need at your traffic, and what does that cost next to the near-zero cost of the static pages sitting beside it. Splitting a project that way is usually the win, with marketing pages built as static output and only the dynamic routes running as a service. The RunxBuild hosting calculator prices those as separate line items, so you can see what the server-rendered part costs on its own rather than as one blended figure. On the deployment side a Next.js app builds from a GitHub repository into a Node service with build logs, environment variables, a custom domain, and rollback to the previous deploy when a render regression ships.
Useful related references:
- Can Cloudways Host Next.js Server-Side Rendering: A Managed VPS Can Host Anything If You Operate It, but the Real Question Is Whether You Should
- Linux Virtual Machine Software: A Side-by-Side for Real Workloads
- React SEO: What Google Actually Indexes and Where Client Rendering Costs You
- Next.js services on RunxBuild
FAQ
What is server-side rendering in Next.js?
It is rendering your React components to HTML on the server for each incoming request, then sending that filled-in HTML to the browser. The browser paints it immediately and then hydrates it with JavaScript. In the Pages Router you opt in with getServerSideProps; in the App Router a route becomes server-rendered when it uses request-specific data or opts out of caching.
Is SSR slower than static generation?
Per request, yes, always. A static page is already built and served from a CDN, while an SSR page runs your data fetching and a React render before a single byte goes out. SSR buys you per-request freshness and personalisation, and you pay for it in time-to-first-byte and in server CPU.
Does the App Router still use getServerSideProps?
No. getServerSideProps is a Pages Router API and has no App Router equivalent. You fetch data directly inside async server components instead, and control the rendering mode with the fetch cache option or the route segment config such as dynamic and revalidate.
Can I host a server-rendered Next.js app on static hosting?
No. Static hosting serves files; SSR needs a Node process running continuously to handle each request. You can host the static parts of a hybrid app that way, but any route that renders per request needs an application host with CPU, memory, and a restart policy.
When should I use ISR instead of SSR?
Whenever the page would look the same to two anonymous visitors a short time apart. ISR serves cached HTML and regenerates it in the background on a revalidation interval, giving you nearly static performance with fresh-enough content. Reserve SSR for output that genuinely depends on the individual request, such as authenticated dashboards.