Skip to content

Segmentation (beta)

Data appsEnterpriseLearn more about Enterprise

Segmentation is a feature of Observable Cloud that allows a Framework data app to build a unique version for different users. When a project is configured to build with segmentation, it will produce a copy of the site for each configured segment. When users view the resulting data app, they will be served the appropriate files based on their identity. This lets static data apps include user authentication without expensive runtime queries. Viewers get assigned to a segment by the platform, and cannot choose to view different segments.

TIP

Segmentation lets different users see different things at the same routes. If you instead want to generate many versions of a page that a user can choose between, see parameterized routes.

Preparing your project

For a project to build different versions for each segment, it will need to know which segment it is a part of. To enable this, the managed build process will provide environment variables when running data loaders and page loaders. Here is an example data loader that reads a segment variable to customize its output.

js
import { query } from "./data-warehouse.js";

const customerId = process.env["SEGMENT_CUSTOMER_ID"];
const customerLogin = process.env["SEGMENT_CUSTOMER_LOGIN"] ?? "<unknown>";
if (!customerId) throw new Error("SEGMENT_CUSTOMER_ID was not provided");

let rows = await query(`
  SELECT sales.date, sales.amount, products.name, ${customerId},
  FROM sales
  JOIN products ON sales.product_id = products.id
  WHERE sales.customer_id = ${customerId}
`);

process.stdout.write(JSON.stringify(rows));

This loader runs a query against a data warehouse (whose configuration has been elided here), and customizes its query based on the current segment. Specifically it accesses sales only for the specific customer.

TIP

The examples in this documentation use JavaScript for loaders, but segmented builders are compatible with all Framework data loaders, regardless of what language they are written in.

Enabling segmented building

In a file named observablehq.cloud.yaml in the root directory of your Framework project (also used for schedules), add the following line:

yaml
segmented: true

Commit the file and push it up to your repository. Observable parses this file from GitHub.

TIP

Data apps may use two configuration files: observablehq.config.js configures the contents and appearance of your Observable Framework app, and observablehq.cloud.yaml configures how Observable Cloud builds and serves it.

Defining segments

When building a data app, Observable Cloud will need to have a list of segments. To get this, it reads the segments key of observablehq.config.js. This configuration property is only used for segmented builds, and is not used during local development.

Here is a simple example of how that might look for a simple list of customers:

js
export default {
  title: "Customer Sales",
  pages: [{ name: "Sales", path: "/sales" }],
  segments: [
    {
      key: "79a12f9d2d229ced",
      variables: {
        SEGMENT_CUSTOMER_ID: "79a12f9d2d229ced",
        SEGMENT_CUSTOMER_LOGIN: "wile-e-coyote",
      },
    },
    {
      key: "33de0d65b1246a28",
      variables: {
        SEGMENT_CUSTOMER_ID: "33de0d65b1246a28",
        SEGMENT_CUSTOMER_LOGIN: "road-runner",
      },
    },
  ],
};

As seen above, each segment is defined as an object with two properties: a key and variables. The key is used by Observable Cloud to organize segments, report build status, and is how users are associated with segments (see the serving section below). The variables are the values provided to loaders as environment variables. By convention the names of these variables should be capitalized and begin with SEGMENT_.

Because the Framework configuration file is code, you can also compute your segments dynamically. When generating segments, you will have access to all your project’s secrets and other build-time environment variables that would be available to a data loader.

js
import { query } from "./data-warehouse.js";

export default {
  title: "Customer Sales",
  pages: [{ name: "Sales", path: "/sales" }],
  segments: async function* () {
    let rows = await query(`SELECT id, login FROM customers`);
    for (const row of rows) {
      yield {
        key: row.id,
        variables: {
          SEGMENT_CUSTOMER_ID: row.id,
          SEGMENT_CUSTOMER_LOGIN: row.login,
        },
      };
    }
  },
};

Default segment

You are free to choose almost any value for the key of a segment. However, there is one segment key that is treated specially. A segment whose key is “default” will be used as a fallback. If a viewer accesses a data app, and there is no segment specifically for that user, then the default segment will be used. If you do not provide a default segment or if the default segment does not have the requested page, then your data app’s 404 page will be shown instead.

This happens after the authentication system determines if a viewer has access to your app, so users that don’t have access won’t be able to see the default segment.

The default segment can be used as a preview of the site, as a more detailed error page, or you could leave it out entirely.

Configuring serving

When a user visits your data app, the authentication system will be used to choose a segment. The authentication key will choose a segment key for that request, and the matching segment will be served. If your data app is configured to use Observable Cloud authentication, their segment key will be their Observable user ID. If you have configured an OIDC provider to control access to the data app, then the OIDC sub field will be used as the segment key.

It is important that these configuration values match between the building and serving steps so that segments are appropriately matched to users.