Skip to main content

Server-side and edge-side personalization modes

Server-side vs. edge-side modes

There are different execution modes of Uniform Context, which prescribe where to run the personalization and A/B testing.

In addition to the client-side mode, which is needed to support Single Page Application architecture, Uniform supports both server-side and edge-side modes.

Each mode has pros and cons, and depending on the CDN capabilities you have at hand, as well as application architecture and business requirements, you typically go for one mode or another per application.

Server-side rendering mode (SSR)

This is a traditional server-side rendering mode (SSR), where the application renders on the server with all the data and state it has access to at the request time. In Next.js land, this requires the usage of the getServerSideProps data fetching technique for your pages where you want personalization to take place. Learn more about it here. During this mode, Uniform will execute personalization during the server-side rendering.

Edge-side mode

This mode is quite different from SSR. It requires static site generation to take place (SSG), which pre-builds the pages along with special Uniform instructions the CDN-level logic layer will translate when the static page is requested from the CDN network. This typically requires a JavaScript-based edge worker/function/handler to be deployed, which contains Uniform's special edge-side SDK to facilitate the translation of those instructions. If you are using Next.js, this mode requires you to use the getStaticProps data fetching technique for your pages where you want personalization to take place. Learn more about it here.

Where can you run it?

The ability to run this logic edge-side requires your CDN to have a programmable edge compute layer. At the moment, Uniform supports the following edge computing platforms:

Pros and cons

Server-side mode is more ubiquitous, as all you need to execute is to run a node server. Unless you execute your rendering on the CDN nodes (this is slowly becoming a reality), you typically incur a higher Time to First Byte (TTFB) tax due to latency and extra server processing.

If your content API is fast and don't have any bottlenecks fetching data during SSR, this is often a good default option.

If your content API layer is slow, and you have a global audience to serve, so you want to minimize latency, and not care about the server scaling, then the edge-side mode is the one to consider. If your CDN provider allows execution of JavaScript code to transform static pages on the fly, this mode combines the benefits of static site generation without the drawbacks.

Depending on your solution specifics, content volume (number of pages) may also be an important factor to consider.

What needs to be done

To accomplish server-side or edge-side personalization, we must share the personalization state from the client with the server (or edge), so it can preemptively send the page with personalization already applied.

There are two aspects of this journey:

  1. Sending client-side score and state data to the server (i.e. on page refresh), so that the server is aware of the client's score state. This is done with a Transition Data Store, which tells the server/edge how to receive the data from the client - generally in a cookie.
  2. Communicating the score state and any data changes (i.e. quirks) from the server/edge up to the client so that the client uses the same initial state as the server/edge. This is done by injecting a JSON script tag into the response with the server-to-client data.

Configuring your app for server-side and edge-side modes

Framework support

This walkthrough leverages the Next.js framework as an example. If you are using another React-based framework, the core concepts are identical. This support is not specific to Next.js, so any SSR capable React frameworks support Uniform SDK in these two modes.

And for the Vue.js developers, stay tuned for the upcoming guide for Nuxt 3.

Step 1: Configure UniformContext

Allow passing a server side context to createUniformContext() where it can receive the client side state:

/uniformContext.tsx
import { Context } from '@uniformdev/context';
import { NextCookieTransitionDataStore } from '@uniformdev/context-next';
import { type NextPageContext } from 'next';

export function createUniformContext(serverContext?: NextPageContext) {
const context = new Context({
// ...your existing config here
transitionStore: new NextCookieTransitionDataStore({
serverContext,
}),
});

return context;
}
pages/_document.tsx
import Document, { DocumentContext, DocumentInitialProps, Head, Html, Main, NextScript } from 'next/document';
import { enableNextSsr } from '@uniformdev/context-next';
import { createUniformContext } from '../uniformContext';

class MyDocument extends Document {
// IMPORTANT: needed to enable the SSR elements
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
const serverTracker = createUniformContext(ctx);
enableNextSsr(ctx, serverTracker);
return await Document.getInitialProps(ctx);
}

render(): React.ReactElement {
return (
...
);
}
}

export default MyDocument;

Step 3: Switch to using the server-side context

  • instantiate clientContext
  • Change the props type of your App to UniformAppProps
  • Pass the serverUniformContext prop to your UniformContext, if it exists
pages/_app.tsx
import { UniformContext, type UniformAppProps } from '@uniformdev/context-react';
import { createUniformContext } from '../uniformContext';
const clientContext = createUniformContext();

function MyApp({ Component, pageProps, serverUniformContext }: UniformAppProps) {
return (
<UniformContext context={serverUniformContext ?? clientContext}>
<Component {...pageProps} />
</UniformContext>
);
}

export default MyApp;
caution

If a Next page does not declare a getServerSideProps function, it will be subject to Automatic Static Optimization and will not run the SSR scoring features.

Step 4: Enable outputType=edge

IMPORTANT!

This step is only required if you are enabling the edge-side mode, and will break server-side rendering mode if you do it.

In order for the edge-side mode to issue special instructions the Uniform SDK-instrumented edge workers will process, you need to set outputType="edge" on <UniformContext /> on _app.tsx:

pages/_app.tsx
<UniformContext outputType="edge" context={serverUniformContext ?? clientContext}>
...
</UniformContext>

What else can you do?

Setting quirks with external data

When server-side or edge rendering is set up, you can set quirks when executing on the server/edge. This lets you communicate specific data from backend APIs to the client, such as from a CDP or internal API.

To do this, simply set quirks on the server-side Context instance before rendering the app. Uniform Context will automatically reflect the set quirks up to the client, where they will be replayed after hydration.

For example in a next.js app,

pages/_document.tsx
import Document, { DocumentContext, DocumentInitialProps, Head, Html, Main, NextScript } from 'next/document';
import { enableNextSsr } from '@uniformdev/context-next';
import { createUniformContext } from '../uniformContext';

class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
const serverTracker = createUniformContext(ctx);

const myCDPResponse = {
country: 'CA',
industry: 'Maple Syrup',
size: '11-50',
};

// set some quirks from the server-side
// this is useful for i.e. geolocation provided by a CDN
serverTracker.update({
quirks: {
...myCDPResponse,
somethingelse: 'a value',
},
});

enableNextSsr(ctx, serverTracker);

return await Document.getInitialProps(ctx);
}

render(): React.ReactElement {
return (
...
);
}
}

export default MyDocument;