Every year or two, someone on a mature Ember team asks whether the app should be server-rendered. The trigger is usually one of three things: a marketing team that wants pages indexed, a slow first paint on mid-range hardware, or a new engineer who worked somewhere with Next.js and found it normal. FastBoot is the answer Ember has had for this since 2015, and it still works. Whether you want it is a separate question, and for a lot of long-lived apps the honest answer is no.
Here is how we reason about it during an assessment.
What FastBoot actually gives you
FastBoot runs your app in Node, renders the route's HTML, and sends that to the browser; the same app then boots client-side and takes over. You get server-rendered markup for crawlers and a faster first contentful paint. You do not get a smaller bundle, a faster time-to-interactive, or partial hydration. The JavaScript still ships and still boots. If your complaint is "the app takes nine seconds to become usable," FastBoot moves pixels earlier and leaves the nine seconds roughly intact.
That distinction decides most cases. SSR is a fix for blank screen and crawlability. It is not a fix for bundle size, boot cost, or an over-fetching data layer.
Three questions before you touch the code
Who needs the HTML? If the app sits behind a login — most of the apps we work on do — there is no crawler to serve and no first-impression bounce to win. The user is an employee with a warm cache. Index-time rendering buys you very little, and you have just added a Node tier to operate.
What is actually slow? Measure before deciding. Our post on initial load performance walks through the breakdown; the short version is that in mature Ember apps the dominant costs are usually bundle size, a long serial chain of model hooks, and render-blocking third-party scripts. FastBoot addresses none of those, and can make the second one worse, because now the server waits on those same requests before it can respond.
Can the codebase survive Node? This is the one that sinks projects. Any module that touches window, document, localStorage, or jQuery at import time or in a constructor will throw during server render. In an eight-year-old app that is rarely a handful of files. It is a long tail: analytics wrappers, a date-picker addon, a resize-observer service, two components that measure DOM in init. Budget for the tail, not the first three fixes.
If the answer is "we need indexable pages"
Consider the cheaper paths first, in this order.
- Split the marketing surface off. If the pages that need indexing are pricing, docs, and a blog, they do not belong in the Ember app at all. Serving them from a static site generator behind the same domain is less work than making a ten-year-old SPA server-renderable, and it removes the requirement permanently.
- Prerender a fixed set of routes. If there are twenty public routes and their content changes daily, not per-request, you can run FastBoot at build time and publish static HTML. You get crawlable pages without running and monitoring a render tier in production.
- Then, and only then, per-request FastBoot. Choose this when the public content is genuinely dynamic and genuinely needs to be indexed.
Teams skip to step three because it is the documented path. Steps one and two retire the requirement for a fraction of the cost.
If you do adopt it, adopt it on one route
The failure mode is switching FastBoot on for the whole app, watching the error rate in Node spike, and reverting a week later with nothing learned. Do it incrementally instead.
- Install
ember-cli-fastbootand getember fastboot --serve-assetsrunning locally. Expect the first run to fail. Read the stack trace, fix the first browser-global reference, repeat. This loop is the bulk of the work. - Wrap genuinely browser-only work so it only runs after hydration — guard on
fastboot.isFastBoot, or move the work intodidInsertElement-era lifecycle equivalents such as a modifier, which never runs on the server. - Pick one route to serve from FastBoot. A public, read-only, low-traffic one. Put it behind a flag or a CDN rule so you can route a percentage of traffic and roll back without a deploy.
- Watch two numbers for a couple of weeks: server render time at p95, and the rate of render failures falling back to client boot. If the fallback path is not wired up and tested, you do not have a safe deployment; you have a new single point of failure.
- Only then extend the route list.
The operational bill
Be clear with whoever is approving this. FastBoot adds a Node process per app instance, with its own memory profile and its own deploy. Memory leaks that were harmless in a browser tab — a service that caches per-session data in a module-level variable, say — now accumulate across every request the server handles, and sandbox isolation is not free. Cold starts are real if you deploy to a serverless runtime. Your error budget now has two surfaces, and a crawler hitting a route that throws on the server gets a different experience from a user who hits the same route in a browser.
None of this is prohibitive. It is just a standing cost, paid monthly, against a benefit you should be able to name in one sentence.
Where this sits next to a migration
If a move off Ember is already on the roadmap, think twice. Making a mature Ember app server-renderable is a substantial investment in the app you plan to retire, and almost none of that work transfers — the SSR story in React or another target framework is a different implementation entirely. If SSR is being argued for because the team wants Next.js, the real conversation is about the migration, not about FastBoot, and it is better to have it openly.
The case where it does line up: you are strangling routes out of Ember one at a time, the new public routes are server-rendered in the target framework, and the Ember app keeps serving the logged-in surface with no SSR at all. That is a coherent plan. "Add FastBoot everywhere, then migrate" usually is not.
The short version
Server rendering a long-lived Ember app is possible, documented, and occasionally correct. Reach for it when public, dynamic content must be indexed and prerendering cannot cover it. Otherwise measure first, split the marketing surface out, and spend the same hours on bundle size and the data layer, where the user-visible win is larger and the thing you build does not need to be operated forever.
If you are weighing this for a specific app, we are happy to look at it — version, route count, and what is actually slow is usually enough to give you a straight answer in a short call.