Skip to content

Next.js

Use the JavaScript SDK in browser code with a project user session. Keep TEFILY_SERVICE_TOKEN on the server only. Turn on project auth and owner rules in Auth and owner rules before shipping writes.

NEXT_PUBLIC_* is inlined into the browser bundle. TEFILY_SERVICE_TOKEN is read only in server code.

NEXT_PUBLIC_TEFILY_API_URL=https://api.tefily.com
NEXT_PUBLIC_TEFILY_PROJECT_ID=YOUR_PROJECT_ID
TEFILY_API_URL=https://api.tefily.com
TEFILY_PROJECT_ID=YOUR_PROJECT_ID
TEFILY_SERVICE_TOKEN=kfxs_…

NEXT_PUBLIC_* is compiled into the client bundle. Never put kfxs_… or kfx_… there. The project id is not a secret. The service token is. Never prefix TEFILY_SERVICE_TOKEN with NEXT_PUBLIC_.

import { KataflaxClient } from '@kataflax/sdk';
export function createBrowserClient() {
return new KataflaxClient({
baseUrl: process.env.NEXT_PUBLIC_TEFILY_API_URL!,
projectId: process.env.NEXT_PUBLIC_TEFILY_PROJECT_ID!,
});
}

The browser client has no serviceToken. After login, client.token is the project JWT. The browser sends that JWT to your route. It never sends kfxs_.

app/api/posts/route.ts. The handler verifies the project JWT, then writes with the service token. KataflaxClient constructed with serviceToken sets that token on every call, including auth.verify, which POSTs a JSON body with token to /api/p/:projectId/auth/verify.

import { NextResponse } from 'next/server';
import { KataflaxClient } from '@kataflax/sdk';
function serverClient() {
return new KataflaxClient({
baseUrl: process.env.TEFILY_API_URL ?? 'https://api.tefily.com',
projectId: process.env.TEFILY_PROJECT_ID!,
serviceToken: process.env.TEFILY_SERVICE_TOKEN!,
});
}
export async function POST(request: Request) {
const header = request.headers.get('authorization') ?? '';
const userJwt = header.startsWith('Bearer ') ? header.slice('Bearer '.length) : '';
if (!userJwt) {
return NextResponse.json({ error: 'Missing session' }, { status: 401 });
}
const server = serverClient();
const checked = await server.auth.verify(userJwt);
if (!checked.valid || !checked.user) {
return NextResponse.json({ error: 'Invalid session' }, { status: 401 });
}
const body = (await request.json()) as { title?: string };
const created = await server.collection('posts').create({
title: body.title ?? 'Untitled',
status: 'draft',
authorId: checked.user['_id'],
});
return NextResponse.json(created);
}

The browser call does not include a service token:

await fetch('/api/posts', {
method: 'POST',
headers: {
Authorization: `Bearer ${client.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: 'Hello Tefily' }),
});

Create the token in the console: project Developers → Service tokens → Create service token. The value is shown once and starts with kfxs_. The verify contract is on Backend service token.

The data API reflects Origin for CORS, so the browser can call https://api.tefily.com directly when owner rules are in place. You do not have to proxy CRUD through Next.js unless you want to hide filters or attach a service token (which you should not expose). This route is for writes that must stay behind the service token.