happykit / flags

⛳️ Feature Flags for Next.js
https://happykit.dev
MIT License
1.03k stars 11 forks source link

Next.js Static Config #20

Open runia1 opened 2 years ago

runia1 commented 2 years ago

I have a side project written in nextjs, deployed on vercel. It has a handful of lambdas that serve as the backend (/pages/api/...), but no server.

A common pattern for feature flag solutions (launchdarkly, and others) is to have a heavy initialization on the server side, usually the initialization is fetching all the feature flag config and caching it somewhere.. in memory or in a cache. After that the feature decisions can be made locally in the sdk, which is much more performant than making an api call for each feature decision.

Unfortunately since my stack doesn't have persistent servers, a heavy initialization for each lambda invocation is not ideal. I also don't like the idea of needing to maintain infrastructure for caching such as redis or memcached. Even if I had redis that each of the lambdas could fetch the config from, I'd still need some mechanism for keeping it up to date with the latest feature config.

What if, each time the config was updated in the UI, it could just save that to a flat file.. json or whatever.. Then make a webhook call to tell Vercel to re-deploy. Part of the deploy pipeline would fetch the latest feature flag config file and bundle that with the deployment. Then the frontend & backend would both have the config baked in. No api calls necessary, no caching infrastructure to manage, no heavy initialization for lambdas or frontend. The only downside I can think of is if your CI CD pipeline took a while to re-deploy and Product managers were in the UI making feature flag changes a lot. But if your company was already doing CI CD I'd think it would be pretty fast.

I'm curious what you think about this idea? It looks like Vercel has a deployments api endpoint so you could create a Vercel Integration which could make this super easy to set up.

dferber90 commented 2 years ago

Thanks for your input here! Your suggestion has valid points, some of which happykit already supports and some that gave me new ideas :)

On feature flags vs waiting for CI/CD

A huge advantage of feature flags is that you don't need to redeploy your site (and wait for CI/CD) when toggling features on or off. This allows you to use feature flags as a kill switch. This functionality would be lost if we waited for CI/CD each time as you pointed out too. HappyKit currently takes a few seconds to propagate updates around the world, but this will come down to basically instant (way less than one second) soon. Waiting for CI/CD would be too slow.

On heavy initialisation

HappyKit evaluates feature flags in roughly 50ms total (including network) and can take millions of requests (as it's built on Cloudflare Workers).

It is true that during builds, it would save requests and build time if the client was a smarter and temporarily downloaded definitions once and evaluated locally. But this is an edge case IMHO which is only relevant for sites wich generate thousands of pages on each deployment.

For that case on the server, we could offer an alternative to getFlags like getFlagsFromDefinitions or extend getFlags to realise when it's used during a build and behave differently.

I don't think shipping feature flag definitions to the client is a good idea for the reasons listed under the .json heading below.

On usage for static sites

HappyKit supports usage in getStaticProps (fits your use-case). HappyKit will evaluate your feature flags during the build and generate your page with those flags. So the initial render will show with those flags. When the page loads in a visitors browser, the @happykit/flags client refreshes the flags and rerenders based on the latest flags. This gives you the speed of static websites but still lets you toggle features on/off without waiting for your build to finish.

HappyKit also supports Vercel's Deploy Hooks which allow you to regenerate your site on feature flag changes so that the static site matches the reevaluated flags even on the first render. More about static rendering is documented in Static Site Generation (Pure) and Static Site Generation (Hybrid).

This has to be configured manually for now, but building an integration is something I've thought about too!

On the .json endpoint

I've been thinking about offering a .json endpoint too. This would need to also come with an evaluation library (pass your flag config, the inputs and get the evaluated flags).

I don't think a .json is generally the right solution. It reaches its limits pretty soon:


Thanks a lot for opening this issue! It gave me the inspiration for offering an alternative to getFlags which will evaluate from a local .json to save those requests during build time and even during lambdas if the alternative is used there, so the developer can decide what's right.

runia1 commented 2 years ago

How I use Next.js & Vercel

I'm not using SSR. I'm also not using getStaticProps for anything currently (I just display skeletons in the UI for initial renders). My site isn't a marketing site, it's a saas solution so I don't need to fetch blog posts from a CMS.

I'm primarily using Nextjs because of it's ability to do SSG for fast page loads, and I like that Nextjs sets up a router for you. I also like that Vercel will deploy my backend as lambdas and I don't have to mess with devops stuff.


How I will be using a feature flag solution

Primarily I'll be using feature flags to gate features I'm working on that aren't ready for use yet.. or to give early access to certain customers that are helping me test things. That means I don't necessarily need to toggle the feature on in the UI and have it affect the app immediately... waiting for the CICD to complete is acceptable for my use case. (even if it were 10 minutes before the change took effect, that would be acceptable).

A huge advantage of feature flags is that you don't need to redeploy your site (and wait for CI/CD) when toggling features on or off. This allows you to use feature flags as a kill switch.

To me this is a non-issue / non-feature. At intuit our product use to define all our feature flags and experiments in code and required a deploy to take effect. Even after we started using Intuit Experimentation Platform (IXP), we still never used feature flags as escape hatches when thing went south. We did have a mechanism for turning off api endpoints to shed load but that was based on kubernetes configs.


Local decision making

Intuit's IXP did definition evaluation locally in the sdks, I believe that's how other popular tools do it as well like LaunchDarkly, etc,.. I don't even believe IXP offered an api other than for fetching & caching definitions.

It is true that during builds, it would save requests and build time if the client was a smarter and temporarily downloaded definitions once and evaluated locally. But this is an edge case IMHO which is only relevant for sites which generate thousands of pages on each deployment.

In my mind the benefit here isn't saving build time when generating pages, it's saving execution time for users requests in production. 50ms is great latency, but imagine the amount of feature decisions that need to be made on saas products with large traffic. Plus what happens in the event that the feature service is having issues? Does the external service's issues then effect your own backend? There are some big benefits to making feature decisions locally based on a cached version of the definitions.


All that said, I think SSG - Hybrid is a fine solution. I don't expect you to scrap your backlog and change direction just to make me happy, but if you add some of these features at some point down the road, that would be super cool.

dferber90 commented 2 years ago

Thanks for all that context, that's great! I think what you want out of happykit is definitely possible without too much effort! It would require me exposing the flag definitions first, and extracting and open-sourcing the flag evaluation logic currently embedded into the Cloudflare Worker.


Just to not leave it unanswered:

imagine the amount of feature decisions that need to be made on saas products with large traffic

HappyKit evaluates all flags at once, so the number of flags doesn't play a role. The number of requests made is also almost irrelevant as Cloudflare will handle that easily. The 50ms are basically the price paid for always having the latest flags. I understand that this is not relevant for your case, so another solution fits better.

Plus what happens in the event that the feature service is having issues? Does the external service's issues then effect your own backend?

HappyKit allows you to set timeouts depending on where the flag is evaluted. If an error occurs or a timeout is reached, the happykit client will use configurable default flags. I get your point though. Only mentioned this for clarity.

There are some big benefits to making feature decisions locally based on a cached version of the definitions.

I agree. It's tradeoff between having fast evaluations and having the most recent feature flag definitions.


One question comes to mind: If you want fully-local definitions, which advantage do you see in using a feature flagging service at all? Couldn't the evaluations and definitions live in an npm package without using any external service? Are you only interested in the nice UI to update flags, or is there more?

runia1 commented 2 years ago

Are you only interested in the nice UI to update flags, or is there more?

That's pretty much it. In the future I'd like to hire a product owner and have a UI for them to set up features / experiments, as well as be able to toggle flags once they're wired up in code. I want the UI, but don't want to have to spin up redis (and/or other infrastructure) just to support a solution like LaunchDarkly. I think a solution using only lambdas as their backend needs:

The only way to achieve that is by bundling the definitions into each release, and making decisions in the sdk. If the goal is to have UI changes propagate to production somewhat real-time, it also requires the feature flag system be able to trigger re-deploys, and those re-deploys be fairly quick, like an hour for each deploy would be too long.

HappyKit evaluates all flags at once, so the number of flags doesn't play a role. The number of requests made is also almost irrelevant as Cloudflare will handle that easily. The 50ms are basically the price paid for always having the latest flags.

Thanks for the additional clarification. Given that, I'd like to give HappyKit a try. I think it's going work just fine for us, the 50ms penalty on each request isn't something I'm going to cry about. I'm working on getting our MVP out the door but likely will try to integrate it after we onboard our first customer, hopefully at the end of the month.

dferber90 commented 2 years ago

Awesome! I've added the json endpoint and open-sourcing the evaluation client to my list, so you will eventually be able to have your ideal setup on happykit.

One important tip to keep request times short: Add <link rel="preconnect" href="https://happykit.dev" /> to your <head> so you save the DNS penalty on the client.


Side note: If you don't care about anything user/visitor/traits-related (no A/B testing, no rollouts, no custom rules) and only need on/off-switches for features, then you can already use getStaticProps and the Deploy Hooks to achieve what you're after.