Going further

Server rendering

Pages normally render in the browser. For pages search engines need to read, like a landing page or a blog, you can render them on the server with one decorator.

Turn it on

Add @Ssr() to a handler:

import { Ssr, View } from 'nestjs-mvc'

@Controller()
export class HomeController {
  @Get()
  @View('Home')
  @Ssr()
  home() {
    return { headline: 'Welcome' }
  }
}

The first response now has the finished HTML of your page in it, and your page takes over in the browser as usual.

A whole controller

Put it on the controller to render every handler on the server:

@Controller('blog')
@Ssr()
export class BlogController {
  @Get()
  @View('Blog/Index')
  index() { /* ... */ }

  @Get('drafts')
  @View('Blog/Drafts')
  @Ssr(false)
  drafts() { /* this one stays in the browser */ }
}

Setup

It works without a separate server or a config file. In development your pages render inside the Nest app, and vite build builds what the server needs for production too.

When to use it

Use it where it actually helps:

  • Yes: public pages that should show up in search results or link previews.
  • No: pages behind a login. Search engines can't see those anyway, and rendering them in the browser costs less.

Server rendering only changes the first page load. Moving between pages after that works the same either way.

If rendering fails

If a page fails to render on the server, nestjs-mvc logs the error and lets the browser render the page instead.

Page titles

Titles and meta tags from Head are rendered on the server too, as long as your template calls ctx.head().

Decide per request

A guard or handler can switch it off for one request, for example to skip server rendering for logged in users on a public page:

this.view.disableSsr()

How to check it

To see whether a page was rendered on the server, use View Source in your browser. The element inspector shows the page after its JavaScript has run, so it'll always look rendered there.

In detail

Which one wins

A call to disableSsr() or enableSsr() during the request wins over the decorators. After that, @Ssr() on the handler wins over the one on its controller. Without any of these, the page renders in the browser.

So enableSsr() can also switch it on for one request on a route that has no decorator at all.

What's in the rendered HTML

Only the first page load is rendered on the server. When someone clicks a link, submits a form or reloads some props, the browser gets data and renders the page itself, whatever the decorator says.

Props you load after the page shows aren't part of the first load, so they aren't in the server rendered HTML either. The browser fetches them once it has taken over.

Code that only works in the browser

On the server there's no window or document. A component that touches them while it renders fails there, so the user gets the page rendered by the browser and search engines don't get the finished HTML. Keep that code in useEffectonMounted, which only runs in the browser.

When it fails

A failed render never breaks the page. The browser renders it instead, and the status stays 200, so the only sign is a warning in your log:

SSR failed for Home at /: window is not defined (falling back to client-side rendering)

To send those to your error tracker instead, pass onError:

MvcModule.forRoot({
  vite: {},
  ssr: { onError: (error) => errorTracker.capture(error) },
})

The error has a message, and the component and url of the page that failed.

A slow page

Rendering happens inside your Nest app and has no time limit. A component that waits for something slow while it renders holds up the response for as long as it takes.

If you'd rather render in a separate server, set ssr.url. Your app then sends the page to <url>/render and gives up after ssr.timeout milliseconds (5000 by default), falling back to the browser.

Your own template

If you've written your own template, it has to call ctx.body() where the page goes and ctx.head() in the <head>, or the rendered HTML has nowhere to go. The default template already does both.

Error pages

An error page doesn't follow the decorator of the route that failed. To render it on the server, set ssr: true on the page you return for it.

Previous
Error pages