Next.js Integration
⚡ 12 min readNext.js App Router with redirect-based Transcodes auth — native <script defer> in <head>, client providers, Route Handlers for JWT verification.
Transcodes Console only configures your project. Your app loads the CDN SDK and owns sign-in UX.
Canonical walkthrough: Signin · API: Redirect API
Client-side only. The SDK needs the browser (IndexedDB, WebAuthn). Mark every file that touches transcodes with 'use client'. Never call SDK APIs from Server Components or middleware.
Before you start
| Requirement | Where |
|---|---|
| Project ID | Console → Authentication Kit → Installation Guide |
| CDN script | Installation Guide snippet |
transcodes.d.ts | Console → Project Setup |
| SDK Redirect Origins | Step 1 — Domains when callback returns to your site |
| Members | Console → RBAC → Users |
Load the SDK (CDN)
Use a native <script defer> in <head> — not next/script for webworker.js:
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<script
src={`https://cdn.transcodes.link/${process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID}/webworker.js`}
defer
/>
</head>
<body>{children}</body>
</html>
);
}NEXT_PUBLIC_TRANSCODES_PROJECT_ID=proj_abc123xyzPlace transcodes.d.ts under e.g. types/ and include in tsconfig.json — Step 2.
Wrap the tree with a client bootstrap component (next section).
Client bootstrap
AuthBootstrap — callback + layout wrapper
'use client';
import { useEffect, type ReactNode } from 'react';
export function AuthBootstrap({ children }: { children: ReactNode }) {
useEffect(() => {
void transcodes.handleSignInCallback().then((result) => {
if (result?.success) {
// JWT stored — AUTH_STATE_CHANGED fires
}
});
}, []);
return <>{children}</>;
}import { AuthBootstrap } from './providers/AuthBootstrap';
// inside <body>, after <Script … />
<AuthBootstrap>{children}</AuthBootstrap>Sign-in button
'use client';
export function SignInButton() {
return (
<button type="button" onClick={() => transcodes.redirectToSignIn()}>
Sign in
</button>
);
}Auth state
'use client';
useEffect(() => {
const off = transcodes.on('AUTH_STATE_CHANGED', ({ isAuthenticated, member }) => {
// update React state
});
return () => off();
}, []);Step 3: Redirect · Step 4: Events
Protected pages
There is no Transcodes JWT in Next.js middleware today — gate on the client after the SDK restores the session:
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
export default function DashboardPage() {
const router = useRouter();
const [ready, setReady] = useState(false);
useEffect(() => {
void transcodes.token.isAuthenticated().then((authed) => {
if (!authed) router.replace('/');
else setReady(true);
});
}, [router]);
if (!ready) return null;
return <div>Dashboard</div>;
}Use a loading shell until isAuthenticated() resolves to avoid hydration mismatch.
Route Handlers (member JWT)
Forward the SDK access token and verify with ES256 + Console JWK:
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const token = request.headers.get('authorization')?.replace(/^Bearer /, '');
if (!token) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// jwtVerify — see Signin Step 5
return NextResponse.json({ ok: true });
}Step-up and Console
'use client';
const res = await transcodes.redirectToStepUp({
resource: 'billing',
action: 'delete',
});
const gate = res.payload[0];
const ok =
res.success &&
(gate?.decision === 'allow' ||
(gate?.decision === 'stepup' && gate?.status === 'verified'));
transcodes.redirectToConsole();Requires signed-in member. Step-up Auth · Console
SSR checklist
- SDK APIs only inside
'use client'or afteruseEffectmount. - Do not read
transcodesduring Server Component render. - Optional:
dynamic(() => import('./SignInButton'), { ssr: false })for immediate click handlers.
Common mistakes
| Mistake | Fix |
|---|---|
Missing AuthBootstrap / callback handler | Call handleSignInCallback() once on load |
isAuthenticated() without await | Always async |
| Unregistered callback origin | SDK Redirect Origins in Console |
| Step-up with no session | Sign in first |