dende
v0.1.4
Published
Next Server UI adapters
Downloads
2
Readme
Dende - Next Server Toolbox
Overview
Dende is a toolbox for fullstack Next solutions that make it easier to translate your backend to your frontend. Dende includes solutions for feature flagging, and Server Component prop translation. Dende is a typescript package.
Install
Yarn
yarn install dende
pnpm
pnpm add dende
Feature Flags - Setup
Defining Flags - Environment Variables
You can define your feature flags in your app's env files or your app host's environment variable definitions (if applicable). Use a "DENDE" prefix in your environment variable in order for to be properly calculated. For example:
# env.local
DENDE_SECRET_FEATURE=true
#env.staging
DENDE_SECRET_FEATURE=true
#env
DENDE_SECRET_FEATURE=false
In this example, we can use DENDE_SECRET_FEATURE
to hide a component from production.
Defining Flags - dende.config.ts
You can define a config file at the root of your project called dende.config.ts
. You can use it to define feature flag states using the following strategies:
Static Flags
Similar to environment variables, you can also create a static flags using a custom feature state map with dende.config.ts
file. For example:
// dende.config.ts
import { createFeatureConfig } from "dende/config";
export default createFeatureConfig({
secretFeature: false,
publicFeature: true,
});
Async Flags - dende.config.ts
If you want more dynamic feature flags or you want to retrieve them from a 3rd party vendor, you can use our cachedResolver
in dende.config.ts
file. Our cachedResolver
utilizes Next fetch to call user defined API's and cache the responses. Here's how to use it:
// dende.config.ts
import { createFeatureConfig, cachedResolver } from "dende/config";
export default createFeatureConfig({
asyncSecretFeature: () => cachedResolver("/api/hello", { callback: () => true }),
asyncPublicFeature: () => cachedResolver("/api/hello",
{ callback: () => true, tags: ['holidayFeature'] }),
});
Busting Resolver Cache
Since our cachedResolver
use Next fetch, you're able to take control of the cache management. In the second paramater, tags
are allowed to be used (part of the Next fetch API), which enable you to bust the cache for groups of feature states as needed. Here's how you can do this with Next API routes:
Create a route dedicated to clear the cache for a set up features using a certain tag
:
// app/api/features/revalidate/route.ts
import { updateFeature } from 'dende/config';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { searchParams } = new URL(request.url);
const secret = searchParams.get('secret');
const tag = searchParams.get('tag');
// Define an env var secret to protect this route from the public
if (secret !== process.env.FEATURES_SECRET) {
return NextResponse.error();
}
updateFeature(tag);
return NextResponse.json({}, { status: 200 });
}
A call to /api/features/revalidate?tag=holidayFeature
will now bust the cached response made for calls the API endpoints defined in the cachedResolver
URL param. So the subsequent call to that URL will request, receive and cache the latest feature state data from the third party.
Client Setup - with Next Server Components and React Context
In an async page or layout component, use calculateFeatures
from dende/config
to calculate the feature flag states.
Layout component (Recommended)
Calculating feature states makes the most sense at the layout level because feature states are typically meant to be global. Here's how it works with layout:
layout.tsx
import { PropsWithChildren } from 'react';
import { calculateFeatures } from 'dende/config';
export default async function Layout({ children }: PropsWithChildren) {
const features = await calculateFeatures();
return (
<AppShell features={features}>
<h1>Hello World</h1>
</AppShell>
)
}
AppShell.tsx (example layout wrapper)
'use client'
import { FeatureMap } from 'dende/config';
type Props = PropsWithChildren & { features: FeatureMap }
export default function AppShell({ features, children }: Props) {
return (
<FeatureProvider map={features}>
{chidlren}
</FeatureProvider>
)
}
Page component
page.tsx
import { calculateFeatures } from 'dende/config';
export default async function Page() {
const features = await calculateFeatures();
return (
<PageWrapper>
<h1>Hello World</h1>
</PageWrapper>
)
}
PageWrapper.tsx
'use client'
import { FeatureMap } from 'dende/config';
type Props = PropsWithChildren & { features: FeatureMap }
export default function PageWrapper({ features, children }: Props) {
return (
<FeatureProvider map={features}>
{children}
</FeatureProvider>
)
}
Components
You can use our FeatureToggle
and FeatureSetToggle
component to gate certain client components based on feature flag states.
FeatureToggle
<FeatureToggle feature="publicFeature">
<div>Hello World</div>
</FeatureToggle>
The component will show bsaed on the feature flag state.
FeatureSetToggle
<FeatureSetToggle features={["publicFeature", "secretFeature"]}>
<div>Hello World</div>
</FeatureSetToggle>
Hooks
You can use our useFeature
and useFeatureSet
hooks to gate more intricate component logic based on feature flag state.
useFeature
const Component = () => {
const activateFeature = useFeature('publicFeature');
return (
<div>
<h1>Hello World</h1>
{activateFeature ? (
<h2>There's a new feature ready!</h2>
) : (
<h2>There's old feature</h2>
)}
</div>
)
}
useFeatureSet
const Component = () => {
const activateFeature = useFeatureSet(['secretFeature', 'publicFeature']);
return (
<div>
<h1>Hello World</h1>
{activateFeature ? (
<h2>Secret feature is ready!</h2>
) : (
<h2>Secret feature is not ready yet</h2>
)}
</div>
)
}