Skip to main content

Feature Configuration

The template uses a feature flag system to gracefully enable or disable functionality based on system configuration. This allows the application to work without a database (serving static content only) while progressively enabling features as infrastructure becomes available.

Feature Flags Module

The feature flags are defined in lib/config/feature-flags.ts.

FeatureFlags Interface

interface FeatureFlags {
/** User ratings and reviews functionality */
ratings: boolean;
/** User comments on items */
comments: boolean;
/** User favorite items collection */
favorites: boolean;
/** Admin-managed featured items display */
featuredItems: boolean;
/** User surveys and feedback collection */
surveys: boolean;
}

How Flags Are Determined

All current features depend on database availability. A feature is enabled when DATABASE_URL is configured:

export function getFeatureFlags(): FeatureFlags {
const isDatabaseConfigured = Boolean(process.env.DATABASE_URL);

return {
ratings: isDatabaseConfigured,
comments: isDatabaseConfigured,
favorites: isDatabaseConfigured,
featuredItems: isDatabaseConfigured,
surveys: isDatabaseConfigured,
};
}

This design allows the template to serve content from the Git-based CMS without any database, while database-dependent interactive features (ratings, comments, favorites) are disabled automatically.

Utility Functions

The module provides several helper functions:

// Check a single feature
import { isFeatureEnabled } from '@/lib/config/feature-flags';

if (isFeatureEnabled('comments')) {
// Render comments component
}

// Get all enabled features
import { getEnabledFeatures } from '@/lib/config/feature-flags';
const enabled = getEnabledFeatures();
// e.g., ['ratings', 'comments', 'favorites', 'featuredItems', 'surveys']

// Get all disabled features (useful for debugging)
import { getDisabledFeatures } from '@/lib/config/feature-flags';
const disabled = getDisabledFeatures();

// Check if everything is ready
import { areAllFeaturesEnabled } from '@/lib/config/feature-flags';
if (areAllFeaturesEnabled()) {
console.log('Full platform is operational');
}

Full API Reference

FunctionReturnsDescription
getFeatureFlags()FeatureFlagsAll flags as a boolean object
isFeatureEnabled(name)booleanCheck a single feature by name
getEnabledFeatures()string[]Array of enabled feature names
getDisabledFeatures()string[]Array of disabled feature names
areAllFeaturesEnabled()booleanTrue if every feature is enabled

Feature-Dependent Rendering

In Server Components

import { isFeatureEnabled } from '@/lib/config/feature-flags';

export default function ItemDetailPage({ item }) {
const showComments = isFeatureEnabled('comments');
const showRatings = isFeatureEnabled('ratings');

return (
<div>
<ItemDetail item={item} />
{showRatings && <RatingSection itemId={item.id} />}
{showComments && <CommentsSection itemId={item.id} />}
</div>
);
}

In API Routes

import { isFeatureEnabled } from '@/lib/config/feature-flags';
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
if (!isFeatureEnabled('comments')) {
return NextResponse.json(
{ error: 'Comments feature is not available' },
{ status: 503 }
);
}
// Handle comment creation...
}

Site Configuration (siteConfig)

Beyond feature flags, the template provides a siteConfig object in lib/config.ts for branding and metadata customization. Every value can be overridden via environment variables:

export const siteConfig = {
name: process.env.NEXT_PUBLIC_SITE_NAME || 'Ever Works',
tagline: process.env.NEXT_PUBLIC_SITE_TAGLINE || 'The Open-Source, AI-Powered Directory Builder',
url: process.env.NEXT_PUBLIC_APP_URL ?? 'https://demo.ever.works',
logo: process.env.NEXT_PUBLIC_SITE_LOGO || '/logo-ever-works.svg',
brandName: process.env.NEXT_PUBLIC_BRAND_NAME || 'Ever Works',
description: process.env.NEXT_PUBLIC_SITE_DESCRIPTION || '...',
keywords: process.env.NEXT_PUBLIC_SITE_KEYWORDS?.split(',').map(k => k.trim()) || [...],
ogImage: {
gradientStart: process.env.NEXT_PUBLIC_OG_GRADIENT_START || '#667eea',
gradientEnd: process.env.NEXT_PUBLIC_OG_GRADIENT_END || '#764ba2'
},
social: {
github: process.env.NEXT_PUBLIC_SOCIAL_GITHUB || '...',
x: process.env.NEXT_PUBLIC_SOCIAL_X || '...',
linkedin: process.env.NEXT_PUBLIC_SOCIAL_LINKEDIN || '...',
// ...
},
attribution: {
url: process.env.NEXT_PUBLIC_ATTRIBUTION_URL || 'https://ever.works',
name: process.env.NEXT_PUBLIC_ATTRIBUTION_NAME || 'Ever Works'
}
} as const;

Customization via Environment Variables

VariableDefaultPurpose
NEXT_PUBLIC_SITE_NAME'Ever Works'Site name in metadata and OG images
NEXT_PUBLIC_SITE_TAGLINETemplate defaultHomepage tagline
NEXT_PUBLIC_APP_URL'https://demo.ever.works'Full site URL (no trailing slash)
NEXT_PUBLIC_SITE_LOGO'/logo-ever-works.svg'Logo path relative to /public
NEXT_PUBLIC_BRAND_NAME'Ever Works'Schema.org Organization name
NEXT_PUBLIC_SITE_DESCRIPTIONTemplate defaultSEO meta description
NEXT_PUBLIC_SITE_KEYWORDSTemplate defaultsComma-separated SEO keywords
NEXT_PUBLIC_OG_GRADIENT_START'#667eea'OG image gradient start color
NEXT_PUBLIC_OG_GRADIENT_END'#764ba2'OG image gradient end color
NEXT_PUBLIC_SOCIAL_GITHUBEver Works URLGitHub profile link
NEXT_PUBLIC_SOCIAL_XEver Works URLX (Twitter) profile link
NEXT_PUBLIC_ATTRIBUTION_URL'https://ever.works'"Built with" footer link

Validation

The validateSiteConfig() function checks for missing production-critical variables:

import { validateSiteConfig } from '@/lib/config';

// Returns true if all required vars are set, false with warnings otherwise
const isValid = validateSiteConfig();

Warnings are logged for missing NEXT_PUBLIC_APP_URL, NEXT_PUBLIC_SITE_URL, and NEXT_PUBLIC_SITE_NAME.

ConfigManager (YAML Configuration)

The ConfigManager class at lib/config-manager.ts manages the works.yml file from the Git-based CMS repository. It reads and writes canonical works.yml and commits configuration changes.

Reading Configuration

import { configManager } from '@/lib/config-manager';

// Get entire config
const config = configManager.getConfig();

// Get a specific key
const pagination = configManager.getPaginationConfig();
// Returns: { type: 'standard' | 'infinite', itemsPerPage: 12 }

// Get nested value
const value = configManager.getNestedValue('pagination.type');

Writing Configuration

All writes are automatically committed and pushed to the Git repository:

// Update pagination
await configManager.updatePagination('infinite', 24);

// Update any top-level key
await configManager.updateKey('pagination', { type: 'standard', itemsPerPage: 20 });

// Update nested key
await configManager.updateNestedKey('headerSettings.sticky', true);

Git Integration

The ConfigManager automatically:

  1. Writes the YAML file to the content directory
  2. Queues a Git commit with a descriptive message
  3. Pushes to the configured GitHub repository
  4. Serializes Git operations to prevent concurrent write conflicts

Commit messages are context-aware:

// For pagination changes:
"Update pagination configuration (type: infinite, itemsPerPage: 24) - 2024-01-20T..."

// For header navigation:
"Update custom header navigation (5 items) - 2024-01-20T..."

// For generic keys:
"Update works.yml: myKey - 2024-01-20T..."

Security

The ConfigManager includes prototype pollution protection:

private isPrototypePollutingKey(key: string): boolean {
return key === '__proto__' || key === 'constructor' || key === 'prototype';
}

Attempts to update __proto__, constructor, or prototype keys are silently rejected.

PathDescription
lib/config/feature-flags.tsFeature flag definitions and utility functions
lib/config.tsClient-safe siteConfig and type re-exports
lib/config-manager.tsYAML config reader/writer with Git integration
lib/config/index.tsBarrel export for the config module
lib/config/config-service.tsServer-side ConfigService singleton
lib/config/types.tsTypeScript type definitions for config
.env.exampleFull list of environment variable options