Skip to main content

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

FlagDefaultWhat it adds
partnerstruePartner/sponsor slots in the sidebar (monthly subscriptions).
badgestrueEmbeddable "Featured on …" badges for winners.
backlinkstrueDofollow/nofollow toggle on outbound project links.
socialProoftrueLive counters, "X people submitted today" widgets.
newslettertrueEmail newsletter signup (uses Resend).
blogtrue/blog section, SEObot integration for AI articles.
webhooksExternaltrueOutbound webhooks (Discord/Slack) on events.
adBannertrueTop horizontal banner above the header.
bookmarkstrueLogged-in users can save projects.
ratingstrue1–5 star ratings on project pages.
commentstrueComment threads on project pages.
promotionstruePaid ad placements (banner, catalog cards, detail cards).
analyticstrueEnhanced tracking: device, browser, country, per-project events.
aifalseAI-powered description generation & category suggestions.
i18nfalseMulti-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

  1. Set ai: true here
  2. Set AI_PROVIDER, AI_MODEL, AI_API_KEY in .env.local
  3. 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:

  1. If submissions exceed 30/week, enable socialProof to show momentum.
  2. If users start asking "what's new?", enable blog and ship 3 pillar posts.
  3. If a project owner asks "can I be at the top?", enable promotions and price at $39–69/month.
  4. If sponsorship inquiries arrive, enable partners. Don't enable proactively.
  5. Enable ratings only when you have 1,000+ MAU; below that, the signal is noise.
  6. Enable comments only when you have moderation bandwidth.
  7. Enable ai if your niche generates enough listings that hand-written descriptions become the bottleneck.
  8. Enable i18n only 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 comments and ratings on 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 analytics to "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 revalidatePath or 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 isFeatureEnabled checks. 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

AI Prompt· Configure features for my MVP

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.