Back to overview

Feature flags with Astro + Vercel Toolbar

Person drawing flags on the ground
| 5 min read
View count:

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 & @vercel/flags dependencies.

terminal
npm i @vercel/toolbar @vercel/flags

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

terminal
vercel link [path-to-directory]

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.

src/layouts/Layout.astro
{
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
}

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: Screenshot of the Vercel Toolbar

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:

terminal
node -e "console.log(crypto.randomBytes(32).toString('base64url'))"

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";
};

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

src/pages/.well-known/vercel/flags/index.ts
export const prerender = false;
import { verifyAccess } from "@vercel/flags";
import type { APIRoute } from "astro";
export const GET: APIRoute = async ({ request }) => {
const access = await verifyAccess(
request.headers.get("Authorization"),
import.meta.env.FLAGS_SECRET,
);
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" },
],
},
},
}),
);
};

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 pass a second argument to the verifyAccess (and the decrypt later) function, which is the FLAGS_SECRET environment variable we set earlier coming from import.meta.env, since Vite-based frameworks like Astro don’t use process.env, and therefore the FLAGS_SECRET is not read from the environment by the @vercel/flags package automatically.

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:

Screenshot of the new feature flag

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.

src/middleware.ts
import { defineMiddleware } from "astro:middleware";
import { decrypt } from "@vercel/flags";
// `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),
import.meta.env.FLAGS_SECRET) as {
newFeature: boolean;
};
if (decryptedFlags.newFeature) {
return context.redirect("/homepage-alternative-feature-flag");
}
}
return response;
});

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.

Did you like this post? Check out my latest blog posts:

Director using a clapperboard
8 Oct 2024

Building a view counter with Astro's Server Islands and Actions

4 min reading time

  • astro
  • javascript
Binoculars
26 Jun 2024

Adding search to static Astro sites

5 min reading time

  • astro
  • pagefind
Astral picture
29 Mar 2024

Astro DB: Migrating my analytics data from Vercel Postgres

5 min reading time

  • astro
  • database
  • vercel
  • postgres
  • libsql