Video Summary and Transcription
Hey, everybody, I'm Graham, and I'm really excited to give you a talk today on feature flagging with React server components. We're going to give examples of how you can deploy feature flags in your application, and how feature flagging interacts with React's rendering strategies. Feature flags are conditional logic that allows you to control the state of a feature independent from the code deploy. This separation of code deployments from feature releases is critical for large product organizations. It enables frequent commits without exposing changes to users, conditional release of features, and easy rollback in case of unexpected bugs. Using FeatureFlag gives you a kill switch where you can turn off your feature. It allows you to do A-B testing and measure the impact of new features. You conditionally release the feature to different sets of users, allowing you to control for externalities and changes to your product. A FeatureFlag is used to get the state and conditionally show a component. The flag state can be stored in a file or using environment variables. However, environment variables have limitations in terms of complexity. A more complex example uses growth book syntax, allowing for advanced control over feature rollout. Building your own system can be tricky, but feature flagging platforms offer fully-featured solutions with two main advantages. Feature flagging applications have a nice UI for controlling feature releases and an SDK that integrates with the code. React poses complications in using feature flags due to its rendering strategies. Static site generators like Next.js have limitations in user targeting and require redeploy for updates. Client components are not async, requiring the use of React primitives. These approaches have their own challenges in implementing feature flags. To initialize the SDK, instantiate the SDK outside of the app and use a user effect hook to download the feature flag payload and update the user data on the SDK. The growth book provider builds context for evaluating feature flags, allowing components to easily access feature flag states. However, there may be a slight flicker in the UX due to the time it takes to initialize the SDK and download the feature flag state. Network optimization can help reduce flickering, but the client's network connection is beyond your control. There are some workarounds for flickering, like showing a loading spinner while rendering in the background. Using feature flags for SEO is not ideal as the initial HTML payload doesn't include all the contents. Server components in React 19 provide an async solution without the need for complicated useEffect and state. react.cache allows caching expensive operations scoped to the current request. The getSdk function call retrieves the value of the feature flag from the cache, providing faster subsequent calls. Our SDK has its own in-memory cache. Dynamic rendering can be expensive in terms of requests, rendering, network calls, and running React on the service side. It can also be slow, and tracking events may be challenging. The server-client hybrid approach combines the advantages of the previous strategies without the downsides. By making the outer app a server component and caching the feature flag payload, we can achieve no-flicker client-side feature flagging. The client component can use the UseMemo hook and the initSync method with the payload already in memory. Passing the SDK instance and wrapping the main app in a provider allows us to have the best of both worlds. Although it may be slightly more complex to set up, using Next.js with React server components offers a cool and modern approach to feature flagging with high performance.
1. Introduction to Feature Flagging with React
Hey, everybody, I'm Graham, and I'm really excited to give you a talk today on feature flagging with React server components. We're going to give examples of how you can deploy feature flags in your application, and how feature flagging interacts with React's rendering strategies. Feature flags are conditional logic that allows you to control the state of a feature independent from the code deploy. This separation of code deployments from feature releases is critical for large product organizations. It enables frequent commits without exposing changes to users, conditional release of features, and easy rollback in case of unexpected bugs.
Hey, everybody, I'm Graham, and I'm really excited to give you a talk today on feature flagging with React server components, and so let's jump right in. I'm Graham McNichol. I'm the co-founder of Growthbook. Growthbook is the most popular open-source feature flagging and A-B testing platform. I went through Y Combinator a couple of years ago and was previously the CTO of my last startup.
So the goal of today's talk is to, if you're not already familiar, kind of give you a quick introduction to what feature flagging is all about. We're going to give some examples of how you can kind of deploy feature flags in your application and then some more complex examples. And then really the meat of this talk is to talk about how feature flagging interacts with React and particularly with some of the different rendering strategies that React has. So we're going to take a look at four different examples of different rendering strategies that React and how you can use feature flagging within each of those.
All right, so with that in mind, we're going to take a look at what is feature flagging. So feature flags are conditional logic that you place around a block of code that allows you to control the state of that feature independent from the code deploy. So it really helps you separate code deployments from feature release, right? And that's really critical when you're working with large product organizations. You have a lot of commits happening at the same time.
And so let's take a look at some of those advantages. So from a development point of view, it allows you to commit code, even though it's a work in progress because it will never be seen by a user. So you can commit more frequently without exposing those changes to your users. And also enables trunk-based development, which feel free to look it up. I don't have time to go into that, but that's also a cool way to kind of do development if you find yourself getting a lot of merge conflicts constantly. And so it also allows you to do conditional release of features. So you can do release features to a subset of your users, or you can do live QA in production by, say, targeting for just beta users, or doing canary releases, or just releasing to like a small subset of your users. And you really can't do that without other systems or hard coding that in. And FeatureFlag makes that so trivially easy.
From a product perspective, as engineers, one of the things that happens is we can release a feature and cause an unexpected bug. And in which case, historically, you've had to quickly roll back your code, figure out the bug and redeploy, or roll it back to a previous version and then deploy that version. And now you're waiting for your CICD pipeline to finish. And it's sort of nerve-wracking a couple of minutes while everything goes. Hopefully, it's a couple of minutes. Sometimes, it could be much, much longer. And then there's all kinds of interaction effects. If your deploy went out with other features, now you're rolling back other people's works.
2. Advantages of Using Feature Flags and A-B Testing
Using FeatureFlag gives you a kill switch where you can turn off your feature. It allows you to do A-B testing and measure the impact of new features. You conditionally release the feature to different sets of users, allowing you to control for externalities and changes to your product.
And it just gets really messy. So using FeatureFlag gives you a kill switch where you can just turn off just your feature. Yeah. So that's really powerful. And the other cool thing it does is it allows you to do A-B testing. So we feel that FeatureFlags is the best way to release new features. And A-B test is the best way to measure the impact of those features. So what you do is you kind of conditionally release that feature to a random set of users that get the control version, a random set that get the new variant, and then you kind of measure the impact. So it allows you to sort of control for all the different externalities that can happen and sort of changes to your product that can happen if you just looked at before and after testing.
3. Working with Feature Flags in Practice
A FeatureFlag is used to get the state and conditionally show a component. The flag state can be stored in a file or using environment variables. However, environment variables have limitations in terms of complexity. A more complex example uses growth book syntax, allowing for advanced control over feature rollout. Building your own system can be tricky, but feature flagging platforms offer fully-featured solutions with two main advantages.
So in a really simple example, a FeatureFlag is just kind of getting the state. So here, we are evaluating a feature called MyFeature. And then we have that stored in the variable. And then we can use that to conditionally show a component or not. And so where you store that value of that flag can be as simple as just hard-coding that into your file or a different file somewhere. A lot of companies we've seen have a Features.json file that is included. So in this example, I just showed you where the flag state is stored in the file.
Changing that state requires a code deploy, which is one of the major advantages of using FeatureFlags in the first place. And now you have to wait for your full CICD thing to finish before you can deploy. And so if you did have a bug in that new feature that you just deployed, well, you turn it off really quickly. You're kind of out of luck because now you've got to change that flag state in that file and then do a full deploy and wait for your CICD pipeline to finish completely. You could also use environment variables. It's a little bit better. But it limits in terms of the amount of complexity that you can use in your FeatureFlags state. It's pretty good for simple flags. But if you want more advanced things than environment variables, it doesn't have the rich context and the rich expressions to allow you to control for that. So if you want, say, to control the rollout to a certain percentage of your users or to target just particular types of users, these states don't really work.
Here's a more complex example. This is using the kind of growth book syntax, but they're all similar from other feature flagging syntax. So what this does is it has a feature called MyFeature that is off by default. And then you can see a set of rules there that are evaluated. So the first one will turn the feature on for all admins. So if you're an admin, you will see this MyFeature as true. And so it will show you that feature. And then if you're in the USA, in Canada, there's a 20% chance that you'll be included and shown in this feature as well. So it's a good way to kind of roll it out. But this kind of expressive language, and you can get much more complicated as we can include ands and ors, and it gets really complicated there. So it's very tricky to build this yourself and not really needed because there's plenty of platforms like GrowthBugger or others that are out there that will do this for you. So what we see is eventually people start by building their own, and then eventually they graduate to using a system that is much more fully featured. And using a feature flagging platform has two main advantages.
4. Different Rendering Strategies and Challenges
Feature flagging applications have a nice UI for controlling feature releases and an SDK that integrates with the code. React poses complications in using feature flags due to its rendering strategies. Static site generators like Next.js have limitations in user targeting and require redeploy for updates. Client components are not async, requiring the use of React primitives. These approaches have their own challenges in implementing feature flags.
One is that gives you a really nice UI, so you can let your product managers or other stakeholders take control over how they release those features. And those UIs conditionally control what rules are shown. And then the other side of a feature flagging application is the SDK that integrates with your code and does the evaluation figure out the state for that particular user or that particular render.
Okay, so now you understand what feature flags are all about, and hopefully understand why you should probably be using them. But when using them with React, React has different rendering strategies that can cause some complications with how feature flags are used. So we're going to take a look at these four different rendering strategies, and we're going to start off with static site generators. And we're going to use Next.js as an example here. Every framework is a little bit different, but hopefully you should get the gist of it. And there should be a way to get static props in your application that are passed in at runtime in any framework that you use.
So the static props are evaluated at build time that are used to generate your static application. So in this example, we're getting static props that we're fetching the payload of our feature flagging. So in this case, it's from our growth book CDN. But wherever that feature flag definitions are held, they're downloaded into your application and then saved as a prop and then pass that into your application. So now the app is able to initialize SDK and then can use that to evaluate features. So the is on is doing evaluation that will return true or false to show feature and that's used to conditionally render that component my feature.
The biggest problems with this approach is because this is done at build time before we deployed our app, we can't really do user targeting, right? So in that example before where we targeted feature flags for admins, it doesn't really work because at build time, we don't know the user state. We don't know if they're an admin or not. So really limited to the type of targeting that you can do with a static site generator. Now, if you want something that's always on or always off for everybody, it's an okay strategy, but it's not great for some of the advanced usage. And one of the problems is that it also requires redeploy of your feature flags to get the new value in there. And that gets rid of one of the main advantages of feature flagging, which is to be able to immediately turn something off as it breaks. And some frameworks kind of muddy the water a little bit with incremental static site generation, which allows you to statically build a page and then rebuild it every minute or something like that. But yeah, it gets around some of the cons, but not completely.
The next strategy we're going to take a look at is client components. And the biggest problem with client components is that they're not async. We have to use user effect, and state and context are kind of required. And so downloading the list of features from a server is definitionally an asynchronous task. So we have to use some of these React primitives to get around those limitations. So here's a code example of using client components. So on the left here we have our wrapper around our application and then the right we have an example component using the feature flag.
5. Handling SDK Initialization and Flickering
To initialize the SDK, instantiate the SDK outside of the app and use a user effect hook to download the feature flag payload and update the user data on the SDK. The growth book provider builds context for evaluating feature flags, allowing components to easily access feature flag states. However, there may be a slight flicker in the UX due to the time it takes to initialize the SDK and download the feature flag state. Network optimization can help reduce flickering, but the client's network connection is beyond your control.
So if we look at the left code, first we're going to instantiate our SDK. And that's outside of our app. This is a singleton. But because it's client side, that's fine. It's perfectly fine to do that. And so inside the application, we're going to fire a user effect hook to initialize the SDK. And so as soon as your app loads, it's going to run this user effect hook. And it's going to start downloading the feature flag payload. And then, yeah, so that kicks off an asynchronous task to get that payload. The second user effect hook we have there, unlike site generation, because it's a client side, we do have access to all the user information. So we can start doing user targeting based on those user attributes. So we have here we have an example, like a user object, and then we return the country, but it could be an ID and other attributes you know about user. So the second user effect hook gets the user data and then updates that on your SDK. And then, we'll actually reevaluate the SD sorry, the feature flag states based on the attributes changing at a point. And so the last bit on the left there, we have the growth book provider. And that builds context that any component underneath it can use to evaluate the feature flag. So inside the components, we don't have to worry about any of this, you know, instantiation of the feature flags, we can just strictly use a hook to get the states for those features. So it's literally like one line to evaluate the feature flag inside of component once you're using a provider like this. So here we use the user effect is on which uses the context to evaluate the state of my feature, and then uses that to conditionally render that component there my feature.
The biggest problem is that we have a slight problem with flickering. So basically the first time the app renders, every feature flag is off or whatever the default state is. And then it takes a little time for the feature for the SDK to be initialized and to download the feature flag state, and then to kind of rerender those parts of the application. And at that point, some of those feature flags might be on. So for example, if you're an admin user, some of those admin features will suddenly be on as it as it loads. And so this can cause a slight flicker of the UX or problem where like it shows the default state and then flickers to the state that's valid for you. You may be familiar with this with a lot of single page applications online right now. And there are ways to kind of speed this up. Like you can work on making sure your network request for the feature flag payload is really fast. And you know, we certainly at GrowthWheek, we use a very fast CDN to help with that. But you can't control the client's network connection completely.
6. Server Components and Caching
There are some workarounds for flickering, like showing a loading spinner while rendering in the background. Using feature flags for SEO is not ideal as the initial HTML payload doesn't include all the contents. Server components in React 19 provide an async solution without the need for complicated useEffect and state. react.cache allows caching expensive operations scoped to the current request. The getSdk function call retrieves the value of the feature flag from the cache, providing faster subsequent calls.
So there are some ways like you can't really help that. There are some workarounds here where it's pretty common to just show a big loading spinner for the first second or so while everything's rendering in the background. But it's still not ideal. And lastly, if you are using feature flags for SEO, it's not ideal because the feature flag, the initial HTML payload that's served to you or to the request is not including all the full HTML of the contents of the feature flag. So if you're trying to SEO rank for that content that's in a feature flag, that can cause some problems there. Yeah, that's not usually a very common use case. Most times feature flags are used for like per user basis, not for SEO.
Okay, so let's take a look at server components. So server components are new for React 19. And you have to be using a more modern React framework like Next.js to access this. One of the nice things about React server components is that they are async. So all the complicated useEffect and state that we had to deal with before, we don't need to deal with that anymore. So it does require a completely different approach than we saw before. So let's take a look at some example code. So on the left here, we set up our SDK. And then we have it here loading inside of our component as before. But let's take a look at the left side here. So we are using react.cache here on the left. And that's really powerful if you're not familiar with that. It allows you to cache an expensive operation like a stanchion in SDK. And it gets reused during a render. But the nice thing about react.cache is that it's scoped to the current request. So you can see inside of our react.cache call, we're actually using user attribute stuff. So we are accessing the cookies and we can set values based on that. And because the servers are processing thousands of these requests, as soon as this request finishes, the next one comes in, the cache is going to be clear and it's going to be re-evaluated again. And then on the right side, I get this value of that feature flag with the getSdk function call, which is awaiting for that cache function to return. And the first time you call it, it's going to be a little bit slow, but any subsequent time should be much, much faster. And then we use that to evaluate it. And I should also mention that growth book inside of SDK and probably a lot of other SDKs too have their own internal caching. So it's not as bad as you might think in terms of like, you know, getting clear this cache for every request.
7. Hybrid Approach for Rendering
Our SDK has its own in-memory cache. Dynamic rendering can be expensive in terms of requests, rendering, network calls, and running React on the service side. It can also be slow, and tracking events may be challenging. The server-client hybrid approach combines the advantages of the previous strategies without the downsides. By making the outer app a server component and caching the feature flag payload, we can achieve no-flicker client-side feature flagging. The client component can use the UseMemo hook and the initSync method with the payload already in memory.
It's not that bad because our SDK will have its own in-memory cache for that. Cool. And the rest of the code is pretty similar. We just use that, you know, the isOn to evaluate the feature and then conditionally render that component.
Okay, so this strategy does have some downsides. And the biggest one is that dynamic rendering can be kind of expensive. So basically all those requests that come in needs to hit node, it needs to render, it needs to do a network call, it needs to run React on the service side. And so that could get really expensive at scale. So depending on how much traffic you have, and particularly if you're using a platform like Bristle, where you're charged based on compute requests, it can really start adding up for large, large sites. And it can also be a little bit slow, particularly for that first time. Now, edge functions are generally pretty fast, but it's not as fast as just a cached HTML being served statically from a CDN. The other complication with this rendering strategy is that tracking events, like tracking which features your users are using, and you want to pass that to, say, DataDog or Sentry, then that's gonna be pretty hard because you're gonna have to move data between service side and client side. And most of those tools don't need to be tracked directly from service side, so it does add some complexity there if you want some more advanced use cases.
So the last rendering strategy we're gonna take a look at is the server-client hybrid approach. So it's basically a hybrid of the last two approaches that I spoke about. And the idea here is that we want to get the best of both worlds without any of those downsides. So you really want to get no-flicker client-side feature flagging without having to be really expensive for the dynamic rendering. Yeah, pretty cool if we can do this, right? So let's take a look at how we can. So the first step here is to make your outer app into a server component. So the key here is that we can't look at cookies or headers inside the server component here. So even though we have a server component with dynamic rendering, because we're not looking at any of the header stuff, we're able to cache this heavily. So in this example here, you can see we're fetching the CDN, the feature flag payload, and we're caching it for five minutes. So you can see it's only gonna hit the Node.js server once every five minutes, and everything else is gonna be completely cached. So it's much cheaper and much faster at scale. So you're downloading the payload of the feature flags, and then we're passing that down to the client app. So the outer one is the server component, and the inner component is the client component. So let's take a look at the client component. So because we have everything already synchronously available to us, we don't really need to use a UseEffect hook. We can actually use a UseMemo hook. And then we're using the initSync method, because we already have that payload in memory.
8. Using SDK Instance and Provider
Passing the SDK instance and wrapping the main app in a provider allows us to have the best of both worlds. Although it may be slightly more complex to set up, using Next.js with React server components offers a cool and modern approach to feature flagging with high performance.
So we just pass it right in there. And then we can immediately get that SDK instance available to us. And then, so the second step, we wrap our main app in a provider, which again, gives that context available to all the components under it. On the right, it's the same as before, where we look at the evaluating of that feature. So this really does get us the best of both worlds. However, there are slight downsides, which is that it's slightly more complex to set up. But if you are using something like Next.js with React server components, this is a really cool way to build your application with really modern feature flagging and high performance as well.
Comments