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.
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
$schemaURL in each JSON file gives you autocomplete in VS Code / Cursor / Zed. Confirm by typing"operator": "PROP_inside a rule'scriteriaarray. - AI agents:
qfg initwrites aCLAUDE.mdandAGENTS.mdinsidequonfig/. Point your agent at the workspace directory and it knows to edit JSON directly, runqfg verify, and useqfg config-schemato look up valid shapes. It will not try to call hosted-only commands likeqfg createorqfg push. - More environments: add
stagingandproductionentries toquonfig/quonfig.jsonand add per-environment overrides inside each flag'senvironmentsarray. - Targeting: use operators like
PROP_IS_ONE_OF,PROP_MATCHES,IN_SEG, orIN_INT_RANGEto roll out to specific users. Runqfg 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 pushfrom the same repo. The file format is identical.