Feature Flags System
The Ever Works template uses a feature flags system to gracefully handle missing dependencies, particularly database availability. Features that depend on the database are automatically disabled when DATABASE_URL is not configured, allowing the template to operate in a static-content mode.
Configuration
Located at lib/config/feature-flags.ts, the feature flags module provides server-side flag resolution.
Flag Definitions
interface FeatureFlags {
ratings: boolean; // User ratings and reviews
comments: boolean; // User comments on items
favorites: boolean; // User favorite items collection
featuredItems: boolean; // Admin-managed featured items
surveys: boolean; // User surveys and feedback
}
Resolution Logic
All current flags depend on database availability:
function getFeatureFlags(): FeatureFlags {
const isDatabaseConfigured = Boolean(process.env.DATABASE_URL);
return {
ratings: isDatabaseConfigured,
comments: isDatabaseConfigured,
favorites: isDatabaseConfigured,
featuredItems: isDatabaseConfigured,
surveys: isDatabaseConfigured,
};
}
Server-Side API
getFeatureFlags
Returns all flags as an object:
import { getFeatureFlags } from '@/lib/config/feature-flags';
const flags = getFeatureFlags();
if (flags.comments) {
// Render comments section
}
isFeatureEnabled
Check a single flag:
import { isFeatureEnabled } from '@/lib/config/feature-flags';
if (isFeatureEnabled('comments')) {
// Show comments
}
getDisabledFeatures
Returns an array of disabled feature names, useful for debugging:
import { getDisabledFeatures } from '@/lib/config/feature-flags';
const disabled = getDisabledFeatures();
// e.g., ['ratings', 'comments', 'favorites', 'featuredItems', 'surveys']
getEnabledFeatures
Returns an array of enabled feature names:
import { getEnabledFeatures } from '@/lib/config/feature-flags';
const enabled = getEnabledFeatures();
areAllFeaturesEnabled
Quick check if all features are available:
import { areAllFeaturesEnabled } from '@/lib/config/feature-flags';
if (areAllFeaturesEnabled()) {
// Full feature set available
}
Client-Side Hooks
useFeatureFlag
Check a single feature flag on the client:
import { useFeatureFlag } from '@/hooks/use-feature-flag';
const isEnabled = useFeatureFlag('comments');
useFeatureFlags
Get all feature flags:
import { useFeatureFlags } from '@/hooks/use-feature-flags';
const { flags, isLoading } = useFeatureFlags();
useFeatureFlagsWithSimulation
Extended hook that supports admin simulation mode for testing features:
import { useFeatureFlagsWithSimulation } from '@/hooks/use-feature-flags-with-simulation';
const {
features, // FeatureFlags (actual or simulated)
isSimulating, // boolean
toggleSimulation, // (feature: keyof FeatureFlags) => void
} = useFeatureFlagsWithSimulation();
This hook is used by the favorites system to conditionally enable/disable features in development.
Integration Examples
Conditional Component Rendering
function ItemDetailPage({ item }) {
const flags = getFeatureFlags();
return (
<div>
<ItemDetails item={item} />
{flags.comments && <CommentsSection itemId={item.id} />}
{flags.ratings && <RatingWidget itemId={item.id} />}
{flags.favorites && <FavoriteButton item={item} />}
</div>
);
}
Hook-Level Feature Gating
Many hooks check feature flags internally. For example, useFavorites only fetches when the favorites feature is enabled:
// Inside useFavorites:
const { data: favorites = [] } = useQuery({
queryKey: ['favorites'],
queryFn: fetchFavorites,
enabled: !!user?.id && features.favorites, // Feature flag check
});
Conditional API Routes
Feature flags can also be checked in API routes to return appropriate responses:
import { isFeatureEnabled } from '@/lib/config/feature-flags';
export async function GET() {
if (!isFeatureEnabled('comments')) {
return NextResponse.json(
{ error: 'Comments feature is not available' },
{ status: 503 }
);
}
// ... handle request
}
Adding a New Feature Flag
- Add the flag to the interface in
lib/config/feature-flags.ts:
interface FeatureFlags {
// ... existing flags
newFeature: boolean;
}
- Set the resolution logic in
getFeatureFlags():
return {
// ... existing flags
newFeature: isDatabaseConfigured && Boolean(process.env.NEW_FEATURE_ENABLED),
};
- Use in components and hooks via
isFeatureEnabled('newFeature')or the client-side hooks.
Design Philosophy
The feature flag system is intentionally simple:
- No external service dependency -- Flags are resolved from environment variables
- No runtime overhead -- Flags are computed once per request/render
- Graceful degradation -- Missing database disables DB-dependent features without errors
- Developer-friendly -- Clear naming, TypeScript types, and helper functions