Skip to main content

Next.js + TypeScript (Fully Local)

This tutorial gets you to a working Next.js + TypeScript app with type-safe feature flags and dynamic configs, without signing up for a Quonfig account, without an API key, and without any network connection to quonfig.com. Everything runs against JSON files in a local git repo.

By the end you'll have:

  • A Next.js 15+ app with the App Router
  • A Quonfig workspace (./quonfig/) checked into the same git repo
  • A type-safe SDK client generated by qfg generate
  • A server component reading a feature flag from local files

If you'd rather see the bigger picture first, read the Open Source / Fully Local how-to.

Prerequisites

  • Node.js 20 or newer
  • A fresh terminal

1. Scaffold a Next.js app

npx create-next-app@latest quonfig-local-demo \
--typescript --app --no-tailwind --no-eslint --no-src-dir --import-alias "@/*"
cd quonfig-local-demo

2. Install the Quonfig CLI

npm install -g @quonfig/cli

You do not need to run qfg login. Skip past anything in the docs that mentions it.

3. Create a workspace inside your repo

mkdir quonfig && cd quonfig
qfg init --samples
cd ..

qfg init --samples lays down a complete workspace under ./quonfig/:

quonfig/
├── quonfig.json
├── feature-flags/
│ └── example.dark-mode.json
├── configs/
│ └── example.greeting.json
├── segments/
│ └── example.beta-users.json
├── schemas/
├── log-levels/
└── .git/ # yes, the workspace gets its own git repo

The pre-commit hook in quonfig/.git/hooks/pre-commit runs qfg verify automatically on every commit inside the workspace.

If you'd rather keep everything in a single git repo, delete quonfig/.git and let your Next.js repo track those JSON files directly.

4. Add a feature flag

For a quick demo, just use the sample dark-mode flag. Open quonfig/feature-flags/example.dark-mode.json and confirm it looks like:

{
"key": "example.dark-mode",
"type": "feature_flag",
"valueType": "bool",
"description": "Enable dark mode UI.",
"default": {
"rules": [
{ "criteria": [{ "operator": "ALWAYS_TRUE" }], "value": { "type": "bool", "value": false } }
]
},
"environments": [
{
"id": "staging",
"rules": [
{ "criteria": [{ "operator": "ALWAYS_TRUE" }], "value": { "type": "bool", "value": true } }
]
}
],
"variants": []
}

The default returns false; the staging environment override returns true. We'll run in the development environment in step 7, so we get the default — flip the inner value in default.rules[0].value to flip the result.

Validate the workspace:

cd quonfig && qfg verify && cd ..

You should see no errors. qfg verify runs entirely locally — no network.

5. Install the Node SDK

npm install @quonfig/node

6. Generate typed accessors

From the project root:

cd quonfig
qfg generate --targets node-ts -o ../generated
cd ..

Because the current directory has a quonfig.json, qfg generate auto-detects the local workspace and reads the files directly — no login, no network. You should see:

Using local workspace at /…/quonfig-local-demo/quonfig (auto-detected quonfig.json).
Generating node-ts code for configs...

That creates ./generated/quonfig-server.ts and ./generated/quonfig-server-types.d.ts. Re-run this whenever you change a flag or config.

7. Bootstrap the SDK against the local directory

Create lib/quonfig.ts:

// lib/quonfig.ts
import { Quonfig } from "@quonfig/node";
import path from "node:path";

const quonfig = new Quonfig({
// Point the SDK at the JSON files on disk.
datadir: path.join(process.cwd(), "quonfig"),
// Pick which environment overrides to apply.
environment: process.env.QUONFIG_ENVIRONMENT ?? "development",
// No server to stream from, no server to poll.
enableSSE: false,
fallbackPollEnabled: false,
});

let initPromise: Promise<void> | undefined;

export async function getQuonfig() {
if (!initPromise) initPromise = quonfig.init();
await initPromise;
return quonfig;
}

With datadir set and no sdkKey passed, the SDK runs in fully local mode: it reads from disk, evaluates in memory, and skips the telemetry reporter entirely. No POSTs are made to telemetry.quonfig.com.

The initPromise lazy-initializes once per server process — Next.js reuses the same Node module instance across requests in production.

8. Use the flag in a server component

Edit app/page.tsx:

// app/page.tsx
import { getQuonfig } from "@/lib/quonfig";

export default async function Home() {
const quonfig = await getQuonfig();
const darkMode = quonfig.getBool("example.dark-mode");
const greeting = quonfig.getString("example.greeting");

return (
<main style={{ padding: 32, background: darkMode ? "#111" : "#fff", color: darkMode ? "#eee" : "#111" }}>
<h1>{greeting}</h1>
<p>Dark mode is <strong>{darkMode ? "on" : "off"}</strong>.</p>
<p>Flip the flag value in <code>quonfig/feature-flags/example.dark-mode.json</code> and restart the dev server to see it change.</p>
</main>
);
}

The SDK exposes getBool, getString, getNumber, getJSON, getStringList, and isEnabled (plus *Details variants). If you want type-safe accessors per-flag instead of stringly-typed keys, use the QuonfigTypesafeNode wrapper generated by qfg generate in step 6 — see the Node SDK docs for the typed pattern.

9. Run it

npm run dev

Visit http://localhost:3000. The page renders using the value of example.dark-mode from your local JSON file.

Editing flags in dev

The SDK loads the workspace once per Node module instance, so editing quonfig/feature-flags/example.dark-mode.json while the dev server is running won't be reflected on reload. Restart the dev server (Ctrl-C then npm run dev) to pick up the change. The values DO re-evaluate per-request, so changes to context-based targeting take effect immediately — only the JSON file contents are cached.

If port 3000 is busy, Next.js falls back to 3001 (or higher) — watch the boot log for the actual URL.

10. Commit and ship

The whole workspace is just files. By default qfg init makes quonfig/ its own git repo so the pre-commit hook works. The cleanest pattern for this tutorial is a single repo at the project root — drop the inner .git and let the outer Next.js repo track everything:

rm -rf quonfig/.git
git add quonfig generated lib app
git commit -m "Add feature flags via local Quonfig workspace"

(If you'd rather keep quonfig/ as a separate repo — e.g. you want to share configs across multiple apps — leave its .git in place, commit inside it separately, and add quonfig as a git submodule of the outer repo.)

Push the repo wherever you want. In production, set QUONFIG_ENVIRONMENT=production and the SDK applies the production environment overrides from your JSON files. That's the entire deployment loop.

Where to go from here

  • Editor support: the $schema URL in each JSON file gives you autocomplete in VS Code / Cursor / Zed. Confirm by typing "operator": "PROP_ inside a rule's criteria array.
  • AI agents: qfg init writes a CLAUDE.md and AGENTS.md inside quonfig/. Point your agent at the workspace directory and it knows to edit JSON directly, run qfg verify, and use qfg config-schema to look up valid shapes. It will not try to call hosted-only commands like qfg create or qfg push.
  • More environments: add staging and production entries to quonfig/quonfig.json and add per-environment overrides inside each flag's environments array.
  • Targeting: use operators like PROP_IS_ONE_OF, PROP_MATCHES, IN_SEG, or IN_INT_RANGE to roll out to specific users. Run qfg config-schema | jq '.definitions.Criterion' to see the full operator list.
  • Upgrade later: if you want a UI, real-time SSE delivery, telemetry, or audit history, sign up for a Quonfig account and run qfg push from the same repo. The file format is identical.