DEV Community

Cover image for Feature flags with Astro + Vercel Toolbar
Thomas Ledoux
Thomas Ledoux

Posted on • Originally published at thomasledoux.be

Feature flags with Astro + Vercel Toolbar

I've been using Vercel for a few years now to host my website(s), but I never really looked into the Vercel Toolbar until now.
I though it was mainly used for leaving comments, but apparently it has evolved a lot, and Vercel added a lot of new capabilities.
One of those caught my eye: the feature flags.
Feature flags is a concept where you can toggle on or off certain functionalities, allowing for controlled release and testing of new features.

Now we know what feature flags are, let's see how to implement them in an Astro website.

Adding the Vercel Toolbar locally

We start by adding the @vercel/toolbar dependency.

npm i @vercel/toolbar
Enter fullscreen mode Exit fullscreen mode

The use the vercel CLI to link your local project to your Vercel project

vercel link [path-to-directory]
Enter fullscreen mode Exit fullscreen mode

Vercel injects the Toolbar into your deployment previews automatically, but while developing locally of course this is not available.
That's why I added the check on the NODE_ENV variable, to only add the script when we're developing locally.
So let's add the Toolbar to the Layout, which is shared by all pages on my website.
We just need to add a <script> tag to our Astro code.

{
  import.meta.env.NODE_ENV === "development" ? (
    <script
      is:inline
      src="https://vercel.live/_next-live/feedback/feedback.js"
      data-explicit-opt-in="true"
      data-owner-id="team_PK64Z6zzqG3h5W3BmmCZX9aS"
      data-project-id="prj_UlaZ1TGeEMBpy8kKmj3wnD7F3M9a"
      data-branch="main"
    />
  ) : null
}
Enter fullscreen mode Exit fullscreen mode

To know what to fill in on the data-owner-id and data-project-id, it's best to follow Vercels guide.

Once this is set up, you should see the toolbar appear at the bottom of the page.

Setting up Feature Flags

First we need to set an environment variable called FLAGS_SECRET inside the Vercel settings for our website, and then pull the environment variable using vercel env pull.
The FLAGS_SECRET value must have a specific length (32 random bytes encoded in base64) to work as an encryption key.
You can generate this value using nodejs:

node -e "console.log(crypto.randomBytes(32).toString('base64url'))"
Enter fullscreen mode Exit fullscreen mode

The Vercel Toolbar will send GET requests to an endpoint with the URL .well-known/vercel/flags on the current domain to retrieve the configuration for the feature flags.
So we should make sure we have an API route which listens on this URL for GET requests, and returns a configuration.
The configuration should have the following shape:

type ApiData = {
  definitions: Record<
    string,
    {
      description?: string;
      origin?: string;
      options?: { value: any; label?: string }[];
    }
  >;
  hints?: { key: string; text: string }[];
  overrideEncryptionMode?: "plaintext" | "encrypted";
};
Enter fullscreen mode Exit fullscreen mode

Let's implement this into an API route in our Astro codebase:

export const prerender = false;

import { verifyAccess } from "../../../../lib/vercel-flags-port.mjs";
import type { APIRoute } from "astro";

export const GET: APIRoute = async ({ request }) => {
  const access = await verifyAccess(request.headers.get("Authorization"));
  if (!access) {
    return new Response(null, { status: 401 });
  }

  return new Response(
    JSON.stringify({
      definitions: {
        newFeature: {
          description: "Controls whether the new feature is visible",
          options: [
            { value: false, label: "Off" },
            { value: true, label: "On" },
          ],
        },
      },
    }),
  );
};
Enter fullscreen mode Exit fullscreen mode

Note that I added export const prerender = false at the top of the file, because I'm using hybrid rendering, it's necessary to flag non-static routes.
You can also see I import verifyAccess from a local file, this is done because I could not get the @vercel/flags package to work with Astro, because the @vercel/flags package checks for an environment variable on process.env, but Astro (actually Vite) uses environment variables on the import.meta.env property.
Because of this, the package thinks the environment variable is not defined, and throws errors.
Therefore, I ported the file over to my own codebase, and replace process.env mentions with import.meta.env, and it works!

The verifyAccess token will make sure the request to our API route is actually coming from the Vercel Toolbar.

When we click on the feature flags button inside of the toolbar we'll see our newly created feature flag pop up.

Once we select one of the overrides, a cookie with the key vercel-flag-overrides will be created, containing the encypted value of the flag.
You'll also see the Vercel Toolbar turns purple when you activate a flag.

Now we want to actually read the flag and do something with it.
I decided to use the flag as an indication that the homepage should be redirected to another version of the homepage.
Because I want to redirect as early as possible, I decided to put this logic into a middleware.
In the middleware, we can read the cookies of the incoming request, and execute logic based on that.

import { defineMiddleware } from "astro:middleware";
import { decrypt } from "./lib/vercel-flags-port.mjs";

// `context` and `next` are automatically typed
export const onRequest = defineMiddleware(async (context, next) => {
  const response = await next();
  const featureFlagOverrideCookie = context.cookies.get(
    "vercel-flag-overrides",
  )?.value;
  if (featureFlagOverrideCookie && context.url.pathname === "/") {
    const decryptedFlags = (await decrypt(featureFlagOverrideCookie)) as {
      newFeature: boolean;
    };
    if (decryptedFlags.newFeature) {
      return context.redirect("/homepage-alternative-feature-flag");
    }
  }
  return response;
});
Enter fullscreen mode Exit fullscreen mode

So we get the cookies from the context, check if the requested page is the homepage and read the vercel-flag-overrides cookie's value.
If there's a cookie and the request is for the homepage, we decrypt the cookie's value and check if the newFeature flag is set to true.
If this is the case, we'll redirect the user (using context.redirect()) to an alternative homepage.

On more thing that needed to be changed was that I had to change my homepage to also be server rendered (so adding export const prerender = false; to the top of the file) instead of statically rendered, otherwise it was not possible to read the cookies for the request for that page.

The source code can be found on Github.

Thanks for reading! Hope it helps someone further.

Top comments (0)