Skip to main content

Why server-side rendering with Convex?

For AI agents: see llms.txt for the complete documentation index. Markdown versions are available by adding .md to a page URL or requesting Accept: text/markdown.

The browser opens a WebSocket to Convex and gets live updates either way, so is server-side rendering (SSR) worth it? On the first page load, SSR shows data sooner in almost every setup. This page explains why. For the setup, see SvelteKit Server Rendering.

The client-side waterfall​

Without SSR, every first page load hits a sequential waterfall:

1. Client → Framework server: request page
2. Framework server → Client: HTML shell (empty) ← skeleton visible
3. Browser parses HTML, discovers <script> tags
4. Browser downloads JavaScript bundle(s)
5. Browser parses + executes JavaScript ← 50-200ms on mobile
6. Framework boots, component mounts, useQuery() fires
7. Client → Convex: subscribe to query
8. Convex → Client: data ← content visible

During steps 3–6 the user sees a skeleton: the browser has to download and run the JavaScript before it can start talking to Convex.

With SSR​

With SSR, your framework server fetches data from Convex while rendering the page. The client receives complete HTML with data in a single response:

1. Client → Framework server: request page
2. Server → Convex: fetch data ← ~1-5ms if co-located
3. Server renders HTML with data
4. Framework server → Client: complete HTML + data ← content visible
5. Browser hydrates (attaches event listeners)
6. Client → Convex: WebSocket for live updates ← background, non-blocking

The server queries Convex while the browser waits for the HTML anyway, so steps 2 and 3 are part of the server's response time instead of coming after it.

How much faster?​

The difference depends on three factors:

Device speed varies the most. On a mid-range phone, parsing and running the JavaScript (steps 3–6 of the waterfall) takes 100–300 ms, on a desktop 30–80 ms. SSR doesn't wait for it.

Server-to-Convex distance: if your framework server runs in the same cloud region as Convex, a query from the server takes about 1–5 ms. See Regions for the regions Convex offers.

Client-to-server distance: both approaches need at least one round trip to the framework server. SSR sends the data in that response; client-side rendering adds a second round trip to Convex after the JavaScript runs.

A realistic example (user in Germany, framework server in EU, Convex in Ireland):

SSRClient-side
Skeleton/shell visiblewith the content~20ms
Content with data visible~70ms~200–400ms
Live updates active~150ms (background)~200–400ms

Co-locate your server with Convex​

Deploying your framework server in the same region as your Convex deployment has the largest effect on SSR speed.

PlatformHow to co-locate with Convex
VercelSet the function region closest to your Convex region in the project settings, for example iad1 (US East) or dub1 (Ireland). The default is iad1
CloudflareEnable Smart Placement or set explicit placement to match your Convex region
NetlifyUse region selection to match your Convex region

In the same region, a query from the server takes about 1–5 ms, and SSR shows data sooner than client-side rendering.

SSR with convexLoad​

With convexLoad(), SSR takes little code: query in the load function and use the result in the template, without initialData. It needs the hooks from Setup.

src/routes/+page.ts
export const load = async ({ fetch }) => ({
tasks: await convexLoad(api.tasks.get, {}, { fetch }),
});

The result is rendered on the server and updates live after hydration, without useQuery() in the component.

When is client-side rendering acceptable?​

Client-side rendering shows a skeleton sooner, but the data later. Skipping SSR can still make sense for:

  • App-like UIs behind a login, such as dashboards and admin panels. Sessions are long, so the slower first load matters less, and search engines don't index them.
  • Prototypes, where you add SSR later.

SSR would still show the first page sooner in these cases. What you save is development effort.

After the first page load, SvelteKit navigates in the browser either way. Universal loads (+page.ts) and useQuery() then use the open WebSocket without a round trip to your server. Server loads (+page.server.ts) still request your server.

Recommendation

Default to SSR. It shows data sooner in almost every deployment, and with convexLoad() it takes little code. Skip it only for a specific reason.