Features Config
Feature flags for every optional module — when to ship lean, when to layer back on, and how flags interact with API routes and crons.
Why this matters
Most directories ship 3–4 features they actually need on day one and 8–10 features they'll add later if growth justifies it. Trying to launch all features at once dilutes attention, multiplies bugs, and creates a UI that feels cluttered before you've even validated the niche. The features config is the cheapest way to keep your product surface honest — flip everything to false except the essentials, ship, and let user requests drive what comes back on.
The implementation matters too: a false flag doesn't just hide UI. It returns 404 from API routes, skips associated cron jobs, and removes the feature from the admin panel entirely. That means flagged-off features can't be hit by curl, can't fire scheduled work, and can't be accidentally re-enabled by a stale UI cache.
File: config/features.config.ts
Toggle features without touching code. When a flag is false:
- Related UI components hide themselves
- API routes return 404
- Cron jobs associated with the feature are skipped
The core platform (submissions, payments, auth, admin) is always active — you can't turn those off.
All flags
| Flag | Default | What it adds |
|---|---|---|
partners | true | Partner/sponsor slots in the sidebar (monthly subscriptions). |
badges | true | Embeddable "Featured on …" badges for winners. |
backlinks | true | Dofollow/nofollow toggle on outbound project links. |
socialProof | true | Live counters, "X people submitted today" widgets. |
newsletter | true | Email newsletter signup (uses Resend). |
blog | true | /blog section, SEObot integration for AI articles. |
webhooksExternal | true | Outbound webhooks (Discord/Slack) on events. |
adBanner | true | Top horizontal banner above the header. |
bookmarks | true | Logged-in users can save projects. |
ratings | true | 1–5 star ratings on project pages. |
comments | true | Comment threads on project pages. |
promotions | true | Paid ad placements (banner, catalog cards, detail cards). |
analytics | true | Enhanced tracking: device, browser, country, per-project events. |
ai | false | AI-powered description generation & category suggestions. |
i18n | false | Multi-language via next-intl. |
Where flags are checked
In API routes
import { featureGuard } from '@/lib/features';
export async function GET() {
const guard = featureGuard('partners');
if (guard) return guard; // 404 response if disabled
// ... normal logic
}In server components
import { isFeatureEnabled } from '@/lib/features';
if (isFeatureEnabled('comments')) {
// render comments
}In client components
import { useFeatures } from '@/hooks/use-features';
function ProjectPage() {
const { isEnabled } = useFeatures();
return (
<>
<ProjectInfo />
{isEnabled('ratings') && <StarRating />}
{isEnabled('comments') && <Comments />}
</>
);
}Common recipes
"Free-only" directory (no payments)
Don't disable promotions — disable payments at the provider level instead. In config/payments.config.ts:
export const paymentsConfig = {
provider: 'none', // disables all paid flows
testMode: false,
};Launch MVP with minimum features
Set everything except the essentials to false:
export const featuresConfig: FeaturesConfig = {
partners: false,
badges: false,
backlinks: true, // keep — nice for SEO
socialProof: false,
newsletter: true, // keep — grows your list
blog: false,
webhooksExternal: false,
adBanner: false,
bookmarks: false,
ratings: false,
comments: false,
promotions: false,
analytics: true, // keep — you need usage data
ai: false,
i18n: false,
};Ship, watch what users ask for, turn flags back on one at a time.
Turning on AI features
- Set
ai: truehere - Set
AI_PROVIDER,AI_MODEL,AI_API_KEYin.env.local - See AI Config for options
A real-world walkthrough: a 14-day "minimum viable directory" feature plan
Day-one launch with the smallest sustainable surface:
- Submissions, payments, auth, admin — always on (core platform).
backlinks: true— every project has a do-follow outbound link by default. Flip to nofollow only after you grow enough that link-juice farming becomes a problem.newsletter: true— email capture is the only retention loop that survives without re-acquisition costs.analytics: true— you cannot iterate without seeing what works.- Everything else:
false.
Week two, layer back features in order of validation:
- If submissions exceed 30/week, enable
socialProofto show momentum. - If users start asking "what's new?", enable
blogand ship 3 pillar posts. - If a project owner asks "can I be at the top?", enable
promotionsand price at $39–69/month. - If sponsorship inquiries arrive, enable
partners. Don't enable proactively. - Enable
ratingsonly when you have 1,000+ MAU; below that, the signal is noise. - Enable
commentsonly when you have moderation bandwidth. - Enable
aiif your niche generates enough listings that hand-written descriptions become the bottleneck. - Enable
i18nonly when you have a real translation, not before.
This sequence keeps the directory feeling polished at every stage — features ship when they have content to be useful, not because the codebase supports them.
Common pitfalls
- Flipping
commentsandratingson a fresh directory. Empty comment sections and "(no ratings yet)" widgets make the site look abandoned. Enable both only after you have user volume. - Disabling
analyticsto "save data". The built-in tracker is GDPR-friendly by design (no PII). Disabling it means you launch blind. Keep it on. - Forgetting to clear API caches after flipping a flag. The UI hides immediately, but cached API responses (Next.js fetch cache, edge cache) might serve old data for an hour. Use
revalidatePathor wait for natural expiry. - Editing the config from the admin panel. There's no admin-panel toggle by design — the file is committed to git so flag changes are reviewable. Editing through git keeps the audit trail.
- Hardcoding
isFeatureEnabledchecks. Use the helper functions (featureGuard,isFeatureEnabled,useFeatures) — they centralise the lookup and let you migrate to env-driven flags later without rewriting every check.
FAQ
Can I have different features enabled per environment?
Yes — read flag values from env vars in the config file. Pattern: ai: process.env.AI_FEATURE_ENABLED === 'true'. Lets you ship AI to production while keeping it off in staging, for example. Not all flags benefit from this; the heavyweights (ai, i18n, analytics) are the most common candidates.
What happens to existing data when I flag a feature off?
Nothing — the data stays in the DB. Flagging comments: false hides the UI and 404s the API, but the comments table is untouched. Re-enabling restores everything. Only run a migration if you intend to permanently remove the feature.
Do flagged-off features affect bundle size?
Server components: yes (Next.js tree-shakes flagged-off code paths). Client components: usually no — the import graph still pulls in disabled feature components. If bundle size matters, dynamically import heavy client features and gate the dynamic import on the flag.
With AI
Edit config/features.config.ts for my directory about {your niche}. I'm launching an MVP — enable only the features that are critical for day one and disable the rest to keep the UI focused.
Must-have for me: {list what matters most — e.g. "submissions, pricing, analytics"}. Nice-to-have (disable for now): {list what to defer — e.g. "ratings, comments, promotions"}.
For each flag, briefly tell me why you enabled or disabled it. Preserve the FeaturesConfig type.