Website "Fleet" Management | Yext Hitchhikers Platform

Introduction

The Yext Platform makes it easy to manage thousands of websites (”fleets”) at scale. There are several key benefits to using Yext’s fleet management capabilities:

  1. Code Reusability: Instead of powering each website with its own set of code, an entire fleet can be powered by a single underlying GitHub repository.
  2. Configurability: Each website in your fleet can be customized based on inputs from the Content, to control aspects such as styling, font families, images, and the ordering of components on a page. This affords non-technical users with the ability to customize each website without touching the underlying code. If more advanced configuration is necessary, developers can easily implement customizations on a per-site basis.
  3. Programmatic Control: Yext sits on top of a robust suite of consumer-grade APIs, webhooks systems, and data monitoring tools, making it easy to deploy and monitor your fleetβ€”whether you’re working across one or multiple Yext accounts.
  4. Data Synchronization - Each website is powered by data stored in Yext Content, and is kept up-to-date with extremely high throughput . When used in combination with Yext Listings , your customers’ data is uniformly synchronized across the entire digital ecosystem.

Configuration Approaches

One of the main benefits of fleet management is code reusability, i.e. using a single code repository to power thousands of sites.

At the same time, each site in the fleet needs to be easily customizable; whether for a content editor using the Content, or a developer.

Yext recommends two approaches to facilitate customization, depending on your use case:

  1. Configuration through Environment Variables (Recommended)
    • Recommended when all sites share the same underlying structure; best for light customization
  2. Configuration through Subfolders
    • Recommended when specific sites need to deviate from the standard structure; best for heavy customization

Approach 1: Environment Variables

book
Note
Recommended when all sites share the same underlying structure; best for light customization.

This approach relies on the use of environment variables. As such, please familiarize yourself with the Environment Variables reference article before diving in.

The idea behind this approach is to parameterize parts of the template code (specifically within stream configurations) such that anything site-specific can be passed to the Pages system at build time, rather than be stored directly in the code as hard-coded values.

Configuring Streams

Environment variables can be used to pass site-specific scopes to each stream configuration in your templates (read the Templates reference article for a refresher on stream templates). For example, refer to the following code example:

export const config: TemplateConfig = {
  stream: {
    $id: "location-stream",
    filter: {
      savedFilterIds: [YEXT_PUBLIC_LOCATIONS_SAVED_FILTER],
    },
    fields: [
      "id",
      "uid",
      "meta",
      "name",
      "address",
      "mainPhone"
    ],
    localization: {
      locales: ["en"]
    }
  },
};

In this example, environment variables allow you to pass different saved filter IDs to your template code at build time. This allows you to create multiple sites based on the same underlying repo, while dynamically passing different sets of Content to the templates.

Configuring Styling

Depending on the complexity of your use case, styling data can be stored directly in the Content and made available to your Pages via the site-stream.json file (refer to our Global Data document for more details).

To summarize, a site stream can be used to β€œstream” the data from a single entity in your platform to all pages in your site; this data will be made available to each page’s JSON document under an object called _site.

An environment variable can be used to parameterize the specific entity ID that is passed to the site stream configuration. Refer to the example below:

{
  "$id": "site-entity",
  "filter": {
    "entityIds": ["YEXT_PUBLIC_SITE_ENTITY_ID"]
  },
  "fields": [
    "name",
    "logo",
    "c_styling"
  ],
  "localization": {
    "locales": ["en"]
  }
}
book
Note
Note, in site-stream.json specifically, the environment variable reference must contain double quotes.

Per the configuration above, two fields called logo and c_styling (custom field) will be streamed globally to the site. In the Content, this field could look something like this:

site entity with logo and styling fields

The returned _site object would look something like this:

"_site": {
  "name": "Turtlehead Tacos Site Entity",
  "logo": {
    "image": {
      "height": 150,
      "url": "https://a.mktgcdn.com/p-sandbox/8esDUBrhKJnkaVztLihLsC3quv_5BjLFG9L6MJ0adcs/150x150.png",
      "width": 150
    }
  },
  "c_styling": {
    "fontFamily": "\"Gill Sans Extrabold\", sans-serif;",
    "primaryColor": "#FFA31F",
    "secondaryColor": "#8946FF"
  }
},

The data from the _site object is then easily accessible for use at the template level.

  • For example, in the code snippet below, the primary and secondary colors from the _site.c_styling object can be be added to the :root as global CSS variables.

    import * as React from "react";
    import Header from "./Header";
    import Footer from "./Footer";
        
    type Props = {
      _site: any;
      children?: React.ReactNode;
    };
        
    // Change hex color into RGB
    export const getRGBColor = (hex, type) => {
      let color = hex.replace(/#/g, "")
      var r = parseInt(color.substr(0, 2), 16)
      var g = parseInt(color.substr(2, 2), 16)
      var b = parseInt(color.substr(4, 2), 16)
      return `--color-${type}: ${r}, ${g}, ${b};`
    }
        
    const PageLayout = ({ _site, children }: Props) => {
      const primaryColor = getRGBColor(_site.c_styling?.primaryColor ?? "#000000", "primary");
      const secondaryColor = getRGBColor(_site.c_styling?.secondaryColor ?? "#808080", "secondary");
        
      return (
        <>
          <style>:root {`{${primaryColor} ${secondaryColor}}`}</style>
          <div className="min-h-screen">
            <Header _site={_site} />
            {children}
            <Footer _site={_site} />
          </div>
        </>
      );
    };
        
    export default PageLayout;

Local Development

During local development, you can define environment variables directly in the .env file, which will be picked up and incorporated into your Stream configurations during the build.

Deploying to Production

In production, you can define environment variables in your site settings (see the Site Settings reference article for more details). These can also be added to your sites programmatically using the Configuration API .

Should you ever update any environment variables, you will need to initiate a new deployment in order to see your updated variables reflected in the new deployment. To accomplish this, you can click β€œNew Deploy” from the deploys page in the UI.

Approach 2: β€œSubfolder” per Site

book
Note
Recommended when specific sites need to deviate from the standard structure; best for heavy customization

This approach allows you to store site-specific code within subfolders. The name of each subfolder must match the hostname at which the site will be hosted.

By using subfolders, it is possible to make each site in your fleet entirely custom; while also allowing for fleet-wide reuse of common files such as components or assets. Refer to our fleet-subfolder-starter to see an example repository.

You must include a subfolder per site in the following directories of your Pages project:

  1. sites-config
  2. src/templates

Refer to the example directory structure below:

.
β”œβ”€β”€ sites-config
β”‚   β”œβ”€β”€ www.example-1.com
β”‚   β”‚   └── ci.json
β”‚   β”œβ”€β”€ www.example-2.com
β”‚   β”‚   └── ci.json
β”‚   β”œβ”€β”€ ...
β”‚   └── www.example-n.com
β”‚       └── ci.json
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ assets
β”‚   β”œβ”€β”€ components
β”‚   β”œβ”€β”€ styles
β”‚   └── templates
β”‚       β”œβ”€β”€ www.example-1.com
β”‚       β”‚   └── index.tsx
β”‚       β”œβ”€β”€ www.example-2.com
β”‚       β”‚   └── index.tsx
β”‚       β”œβ”€β”€ ...
β”‚       └── www.example-n.com
β”‚           └── index.tsx
β”œβ”€β”€ package.json
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ tsconfig.json
└── vite.config.js

Setting up the sites-config Directory

In the sites-config directory, each site in your fleet requires its own subfolder, wherein any configuration files specific to a particular website can be provided, such as ci.json. Refer to the site-config directory structure below:

.
β”œβ”€β”€ sites-config
β”‚   β”œβ”€β”€ www.example-1.com
β”‚   β”‚   β”œβ”€β”€ ci.json
β”‚   β”‚   └── site-stream.json
β”‚   └── www.example-2.com
β”‚       β”œβ”€β”€ ci.json
β”‚       └── site-stream.json
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ assets
β”‚   β”œβ”€β”€ components
β”‚   β”œβ”€β”€ styles
β”‚   └── templates
β”œβ”€β”€ package.json
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ tsconfig.json
└── vite.config.js

Each ci.json file should be configured to build and collect artifacts specific to each site.

  • Refer to the features and buildCmd properties in the example configuration file below, which tells the Pages system to run your build based on the contents of the www.example-1.com folder:

    // sites-config/www.example-1.com/ci.json
    {
      "artifactStructure": {
        "assets": [
          {
            "root": "dist",
            "pattern": "assets/**/*"
          }
        ],
        "features": "sites-config/www.example-1.com/features.json",
        "plugins": [
          {
            "pluginName": "Generator",
            "sourceFiles": [
              {
                "root": "dist/plugin",
                "pattern": "*{.ts,.json}"
              },
              {
                "root": "dist",
                "pattern": "assets/{server,static,renderer}/**/*{.js,.css}"
              }
            ],
            "event": "ON_PAGE_GENERATE",
            "functionName": "Generate"
          }
        ]
      },
      "dependencies": {
        "installDepsCmd": "npm install",
        "requiredFiles": ["package.json", "package-lock.json", ".npmrc"]
      },
      "buildArtifacts": {
        "buildCmd": "npx pages build --scope www.example-1.com"
      },
      "livePreview": {
        "serveSetupCmd": ":"
      }
    }

Setting up the src/templates Directory

Within the src/templates directory, the templates for each site must be stored within subfolders. This is necessary in order for each of the sites-config/ci.json build commands to work, each of which references a particular subfolder with the --scope flag.

Refer to the src/templates directory structure below:

.
β”œβ”€β”€ sites-config
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ assets
β”‚   β”œβ”€β”€ components
β”‚   β”œβ”€β”€ styles
β”‚   └── templates
β”‚       β”œβ”€β”€ www.example-1.com
β”‚       β”‚   β”œβ”€β”€ index.tsx
β”‚       β”‚   └── 404.tsx
β”‚       └── www.example-2.com
β”‚           β”œβ”€β”€ index.tsx
β”‚           └── 404.tsx
β”œβ”€β”€ package.json
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ tsconfig.json
└── vite.config.js

Local Development

book
Note
Ensure you are on the latest version of both @yext/pages and the Yext CLI .

Local development can be scoped to a particular site in your repo by utilizing the following commands (and by replacing REPLACE_ME with the hostname of your desired site):

npx pages build --scope REPLACE_ME
yext pages generate-test-data --hostname REPLACE_ME
npx pages dev --scope REPLACE_ME

Deploying to Production

In production, each site in your fleet must be connected to a domain. Based on the domain connected to a site, the Pages system will run the build step using the corresponding subfolders from the repo.

For example, if a site is connected to www.example-1.com, the build will run based on the configuration files specified in sites-config/www.example-1.com.

Custom Styling per Site

Styling can also be customized on a per site basis. Refer to the example src/styles directory structure below, which shows how multiple TailwindCSS configuration files can be implemented:

.
β”œβ”€β”€ sites-config
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ assets
β”‚   β”œβ”€β”€ components
β”‚   β”œβ”€β”€ styles
β”‚   β”‚   β”œβ”€β”€ www.example-1.com
β”‚   β”‚   β”‚   β”œβ”€β”€ index.css
β”‚   β”‚   β”‚   └── tailwind.config.cjs
β”‚   β”‚   └── www.example-2.com
β”‚   β”‚        β”œβ”€β”€ index.css
β”‚   β”‚        └── tailwind.config.cjs
β”‚   └── templates
β”œβ”€β”€ package.json
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ tsconfig.json
└── vite.config.js
book
Note
Note, subfolders are not required to support this use case; rather, they are used for organizational purposes. Subfolders are only required in the sites-config and src/templates directories.

In this example, each subfolder can contain its own tailwind.config.cjs file:

// src/styles/www.example-1.com/tailwind.config.cjs

module.exports = {
  content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
  theme: {
    extend: {
      colors: {
        // [INSERT SITE-SPECIFIC STYLING HERE]
      },
    },
  },
  plugins: [],
};

Each .css file can refer to its own tailwind.config.cjs file:

/* src/styles/www.example-1.com/index.css */

@config "./tailwind.config.cjs"; 

Finally, at the template level, each .css file can can be imported for site-specific styling:

// src/templates/www.example-1.com/index.tsx

import "../../styles/www.example-1.com/index.css";

Observe how the following component refers to bg-primary as a background color, but renders a different color based on the site:

Known Limitations

  • Subfolders are not supported in the functions/http directory. Any HTTP functions included in your repository will be made available to all sites in the repo.