Flags
Flags and remote config
This app uses two mechanisms:
- Vercel Web Analytics (SDK component) gated via config
- Internal, DB-backed feature flags with an admin UI and a client hook
Vercel Web Analytics (SDK)
Rendered in the root layout when enabled:
{APP_CONFIG.analytics.vercel_web_analytics_enabled && (
<Analytics />
)}
```typescript
Enable via config/env:
```typescript
analytics: {
vercel_web_analytics_enabled:
process.env.NEXT_PUBLIC_VERCEL_WEB_ANALYTICS_ENABLED || false,
}
```typescript
Set `NEXT_PUBLIC_VERCEL_WEB_ANALYTICS_ENABLED=true` to include the `<Analytics />` component.
### Internal feature flags (DB-backed)
These flags are first-class in the app, managed per-user via an admin page and exposed via a React hook.
#### Canonical flags
Add new flags to the enum:
```typescript
export enum FeatureFlagName {
SUITS_CONNECT_ACCESS = "suits_connect_access",
}
```typescript
Current flags:
- `suits_connect_access`
#### Database schema
```sql
create table if not exists public.feature_flags (
id bigint generated by default as identity not null,
created_at timestamp with time zone not null default now(),
user_id text null,
name text null,
description text null,
enabled boolean null default false,
flag text null,
feature_flag_id uuid null default gen_random_uuid(),
constraint feature_flags_pkey primary key (id),
constraint feature_flags_user_id_fkey foreign key (user_id) references users (user_id)
) tablespace pg_default;
create index if not exists feature_flags_user_id_idx on public.feature_flags (user_id);
create index if not exists feature_flags_flag_idx on public.feature_flags (flag);
```typescript
The client reads from a view `public.v_flags` that should return a single row for the current user containing a `flags` column (JSON array or stringified array). Ensure your database creates/maintains this view.
#### Client hook
Use the hook to gate UI and logic:
```typescript
export function useFeatureFlags() {
const { user } = useUserStore();
const [cachedFlags, setCachedFlags] = useState<Set<string>>(() => {
try {
if (typeof window === "undefined") return new Set();
const key = user?.user_id ? `flags:${user.user_id}` : null;
if (!key) return new Set();
const raw = window.localStorage.getItem(key);
```typescript
```typescript
const effective = enabledFlags.size > 0 ? enabledFlags : cachedFlags;
const hasFlag = useCallback((flag: string) => effective.has(flag), [effective]);
return { isLoading, hasFlag };
```typescript
Example usage:
```tsx
import { useFeatureFlags } from "@/hooks/use-feature-flags";
import { FeatureFlagName } from "@/lib/enums/feature-flags";
export function SuitsConnectButton() {
const { hasFlag } = useFeatureFlags();
if (!hasFlag(FeatureFlagName.SUITS_CONNECT_ACCESS)) return null;
return <button className="rounded-sm">Suits Connect</button>;
}
```typescript
#### Admin UI
Manage user flags at `app/admin/feature-flags`:
```tsx
"use client";
import { Plus } from "lucide-react";
import { useCallback, useState } from "react";
import { ErrorBoundary } from "react-error-boundary";
import PageHeader from "@/components/layouts/blocks/page-title";
import { Button } from "@/components/ui/button";
import AddFeatureFlagDialog from "@/features/feature-flags/components/add-feature-flag-dialog";
import FeatureFlagTable from "@/features/feature-flags/components/feature-flag-table";
export default function FeatureFlagsPage() {
```typescript
This page supports searching users, creating/updating flags, and deleting flags.
### Adding a new flag end-to-end
## File Structure
```files
lib/
└── enums/
└── feature-flags.ts
database/
└── views/
└── v_flags.sql
Steps
- Add the name to
lib/enums/feature-flags.ts - Ensure
public.v_flagsincludes it for the appropriate users - Gate UI/logic with
useFeatureFlags().hasFlag("my_flag") - Use the admin page to assign flags to users
Notes
- Use the DB-backed flags for app logic and access control.
- Keep the enum authoritative; avoid magic strings in feature checks.
Unified flags via Flags SDK (server + client)
Flags resolve from the DB (public.v_flags). Use a single API on the server and an optional client hook.
Server (RSC/route/middleware):
import { createFeatureFlag } from "@/lib/flags";
export default async function Page() {
const isOn = await createFeatureFlag("my_first_gate")(); // resolves via DB
return <div>myFeatureFlag is {isOn ? "on" : "off"}</div>;
}
```typescript
Client (hook):
```typescript
import { useUnifiedFlag } from "@/hooks/use-unified-flag";
export function MyButton() {
const { enabled } = useUnifiedFlag("my_first_gate");
if (!enabled) return null;
return <button className="rounded-sm">Enabled</button>;
}
```typescript
Notes:
- The DB adapter uses the `fetch/data` API and the `public.v_flags` view. Ensure it returns `flags` for the current user.