- Published on
Implementing Feature Flags for Subscriptions in Next.js
- Authors
- Name
- Avasdream
- @avasdream_
Implementing Feature Flags for Subscriptions in Next.js
In this tutorial, we'll walk through implementing feature flags based on subscription plans in a Next.js application. We'll use a simple JSON data structure to manage features and subscriptions, making the setup straightforward and easy to maintain. Let's get started!
Step 1: Define Your Features and Subscriptions
First, create a features.ts
file where we'll define our features and subscriptions. Each feature will have a unique key, name, description, and the plans it belongs to.
// features.ts
interface Feature {
key: string
name: string
description: string
plan: string[]
}
interface Subscription {
type: string
title: string
description: string
price: string
buttonText: string
buttonLink: string
}
const features: Feature[] = [
{
key: 'feature_one',
name: 'Feature One',
description: 'Description for feature one.',
plan: ['basic', 'professional'],
},
{
key: 'feature_two',
name: 'Feature Two',
description: 'Description for feature two.',
plan: ['professional', 'enterprise'],
},
{
key: 'feature_three',
name: 'Feature Three',
description: 'Description for feature three.',
plan: ['enterprise'],
},
]
const subscriptions: Subscription[] = [
{
type: 'basic',
title: 'Basic',
description: 'Basic subscription plan.',
price: '10 USD / month',
buttonText: 'Subscribe to Basic',
buttonLink: '/subscribe-basic',
},
{
type: 'professional',
title: 'Professional',
description: 'Professional subscription plan.',
price: '20 USD / month',
buttonText: 'Subscribe to Professional',
buttonLink: '/subscribe-professional',
},
{
type: 'enterprise',
title: 'Enterprise',
description: 'Enterprise subscription plan.',
price: '50 USD / month',
buttonText: 'Subscribe to Enterprise',
buttonLink: '/subscribe-enterprise',
},
]
export { features, subscriptions, type Feature, type Subscription }
Step 2: Create a Hook to Fetch Features
Next, create a custom hook useFeatures to fetch the features based on the subscription type.
// hooks/useFeatures.ts
import { useState, useEffect } from 'react'
import { Feature, features } from '../features'
interface UseFeaturesResult {
features: string[]
loading: boolean
}
function useFeatures(subscriptionType: string): UseFeaturesResult {
const [featuresList, setFeaturesList] = useState<string[]>([])
const [loading, setLoading] = useState<boolean>(true)
useEffect(() => {
function fetchFeatures() {
try {
const filteredFeatures = features
.filter((feature) => feature.plan.includes(subscriptionType))
.map((feature) => feature.key)
setFeaturesList(filteredFeatures)
} catch (error) {
console.error('Error fetching features:', error)
} finally {
setLoading(false)
}
}
fetchFeatures()
}, [subscriptionType])
return { features: featuresList, loading }
}
export default useFeatures
Step 3: Create a Context to Provide Features
We'll use a context to provide the features to the component tree.
// context/FeaturesContext.tsx
import { createContext, ReactNode, useContext } from 'react';
import useFeatures from '../hooks/useFeatures';
interface FeaturesContextProps {
features: string[];
loading: boolean;
}
interface FeaturesProviderProps {
subscriptionType: string;
children: ReactNode;
}
const FeaturesContext = createContext<FeaturesContextProps | undefined>(undefined);
export function FeaturesProvider({ subscriptionType, children }: FeaturesProviderProps) {
const { features, loading } = useFeatures(subscriptionType);
return (
<FeaturesContext.Provider value={{ features, loading }}>
{children}
</FeaturesContext.Provider>
);
}
export function useFeaturesContext(): FeaturesContextProps {
const context = useContext(FeaturesContext);
if (!context) {
throw new Error('useFeaturesContext must be used within a FeaturesProvider');
}
return context;
}
export { FeaturesContext };
Step 4: Create a Has Component to Check Feature Availability
Create a Has component that conditionally renders its children based on the presence of specified features.
// components/Has.tsx
import { ReactNode } from 'react';
import { useFeaturesContext } from '../context/FeaturesContext';
interface HasProps {
feature: string | string[];
fallback?: ReactNode;
children: ReactNode;
}
function Has({ feature, fallback, children }: HasProps) {
const { features, loading } = useFeaturesContext();
if (loading) return null; // Optionally handle loading state
const hasFeature = Array.isArray(feature)
? feature.every((f) => features.includes(f))
: features.includes(feature);
if (!hasFeature) {
return <>{fallback}</> || null;
}
return <>{children}</>;
}
export default Has;
Step 5: Wrap Your Application with the FeaturesProvider
Ensure your application is wrapped with the FeaturesProvider to pass the subscription type.
// pages/_app.tsx
import { AppProps } from 'next/app';
import { FeaturesProvider } from '../context/FeaturesContext';
function MyApp({ Component, pageProps }: AppProps) {
const subscriptionType = 'basic'; // Fetch from user session or context
return (
<FeaturesProvider subscriptionType={subscriptionType}>
<Component {...pageProps} />
</FeaturesProvider>
);
}
export default MyApp;
Step 6: Use the "Has" Component in Your Pages
Now you can use the "Has" component to conditionally render elements based on features.
// pages/index.tsx
import { Button, Stack } from '@chakra-ui/react';
import Has from '../components/Has';
import { subscriptions, features } from '../features'; // Adjust the path as necessary
import PricingCard from '../components/PricingCard'; // Adjust the path as necessary
function HomePage() {
return (
<Stack direction={"row"} p="12" spacing="4">
{subscriptions.map((subscription) => {
const subscriptionFeatures = features
.filter((feature) => feature.plan.includes(subscription.type))
.map((feature) => feature.name);
return (
<PricingCard
key={subscription.type}
title={subscription.title}
features={subscriptionFeatures}
description={subscription.description}
price={subscription.price}
buttonText={subscription.buttonText}
buttonLink={subscription.buttonLink}
subscriptionType={subscription.type}
/>
);
})}
<Has feature="feature_one" fallback={<Button colorScheme="red">Feature One Disabled</Button>}>
<Button colorScheme="green">Feature One Enabled</Button>
</Has>
<Has feature={["feature_two", "feature_three"]} fallback={<Button colorScheme="red">Multiple Features Disabled</Button>}>
<Button colorScheme="green">All Required Features Enabled</Button>
</Has>
</Stack>
);
}
export default HomePage;
// components/PricingCard.tsx
import { Box, Button, Heading, Text, List, ListItem } from '@chakra-ui/react';
interface PricingCardProps {
title: string;
features: string[];
description: string;
price: string;
buttonText: string;
buttonLink: string;
subscriptionType: string;
}
const PricingCard: React.FC<PricingCardProps> = ({
title,
features,
description,
price,
buttonText,
buttonLink,
subscriptionType,
}) => {
return (
<Box border="1px" borderColor="gray.200" borderRadius="md" p="6" width="full">
<Heading as="h3" size="lg" mb="4">
{title}
</Heading>
<Text mb="4">{description}</Text>
<List spacing={3} mb="4">
{features.map((feature, index) => (
<ListItem key={index}>{feature}</ListItem>
))}
</List>
<Text fontSize="2xl" fontWeight="bold" mb="4">{price}</Text>
<Button colorScheme="teal" as="a" href={buttonLink}>
{buttonText}
</Button>
</Box>
);
};
export default PricingCard;
And that's it! You've successfully implemented feature flags based on subscription plans in a Next.js application using a simple JSON data structure. This setup allows you to easily manage and extend features as your application grows.