Next.js Integration
⚡ 12 min readIntegrate Transcodes WebAuthn/Passkey authentication into your Next.js 15 application with App Router and TypeScript
Client-side rendering only. The Transcodes SDK runs exclusively in the
browser. Always add 'use client' at the top of any file that uses the SDK.
Never use it in server components or server actions.
Prerequisites
- Next.js 15 with App Router
- TypeScript
- Transcodes project ID from Transcodes Console
- HTTPS environment (or
localhostfor development)
This guide covers App Router only. Next.js 15 is the recommended version
Choose how you load the SDK
See Quick Integration — Two Ways to Integrate.
| Approach | Best for |
|---|---|
next/script + CDN | PWA, load before hydration (beforeInteractive) |
npm @bigstrider/transcodes-sdk | Auth-only apps that do not need PWA |
PWA: Add the CDN script in the root layout with beforeInteractive. npm-only setup cannot replace PWA (manifest / sw.js).
Installation
Option A: Script in root layout (default — PWA & Dashboard flow)
Add SDK Script to Root Layout
Authentication Toolkit Cluster only:
<Script
src={`https://cdn.transcodes.link/${process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID}/webworker.js`}
strategy='beforeInteractive'
/>Web App Toolkit Cluster (PWA) — also add manifest in <head> and Service Worker:
<link rel="manifest" href={`https://cdn.transcodes.link/${process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID}/manifest.json`} />
<Script
src={`https://cdn.transcodes.link/${process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID}/webworker.js`}
strategy='beforeInteractive'
/>Download sw.js from Transcodes Dashboard → Web App Cluster → Installation Guide. Place in public/sw.js so it is served at /sw.js (required for PWA installability)
Use strategy="beforeInteractive" so the SDK loads before your app hydrates
Add TypeScript Type Definitions
Download transcodes.d.ts from the Transcodes Dashboard and save as types/transcodes.d.ts
Then update your tsconfig.json:
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "types"]
}If you prefer the npm SDK (no beforeInteractive script), continue to Option B below.
Option B: npm SDK
Install the package
npm install @bigstrider/transcodes-sdkCall init from a client provider
Do not add the webworker.js <Script> if you use this path. The App Router has no main.tsx top-level await. Call init once from a small client component that wraps your app:
'use client';
import { useEffect, useState, type ReactNode } from 'react';
import { init } from '@bigstrider/transcodes-sdk';
export function TranscodesInitProvider({ children }: { children: ReactNode }) {
const [ready, setReady] = useState(false);
useEffect(() => {
void init({
projectId: process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID!,
}).then(() => setReady(true));
// Optional: init({ projectId: '...', customUserId: 'uid_xxx', debug: true })
}, []);
if (!ready) return null; // or a loading UI
return <>{children}</>;
}In app/layout.tsx, wrap children with <TranscodesInitProvider> inside <body>.
Then use named exports in your components:
'use client';
import {
openAuthLoginModal,
isAuthenticated,
getCurrentMember,
signOut,
on,
} from '@bigstrider/transcodes-sdk';TypeScript
Types ship with the npm package. Add transcodes.d.ts separately only when you use Option A (CDN script).
Skip Option A when you use npm only (and you are not building PWA via Web App Kit).
Auth Context (Client Component)
See React Integration - Auth Context for the full implementation. For Next.js, add 'use client' and use process.env:
'use client';
// Use process.env instead of import.meta.env
const projectId = process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID!;
// ... rest is identical to React versionRoot Layout with Provider
Use the same script URL as above (cdn.transcodes.link for both Authentication Toolkit Cluster only and Web App Toolkit Cluster (PWA)):
import Script from 'next/script';
import { AuthProvider } from './providers/AuthProvider';
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`}
strategy='beforeInteractive'
/>
</head>
<body>
<AuthProvider>{children}</AuthProvider>
</body>
</html>
);
}If you chose npm + init, omit the <Script> above and wrap {children} with <TranscodesInitProvider> (from Installation - Option B) outside AuthProvider so Transcodes is ready first:
<body>
<TranscodesInitProvider>
<AuthProvider>{children}</AuthProvider>
</TranscodesInitProvider>
</body>Components
For LoginButton and MemberProfile components, see React Integration - Components. In Next.js, add the 'use client' directive at the top and adjust imports:
'use client';
import { AuthContext } from '../providers/AuthProvider'; // Next.js pathProtected Route Component
See React Integration - Protected Route for the full implementation. For Next.js, use next/navigation:
'use client';
import { useRouter } from 'next/navigation'; // instead of react-router-dom
// Use router.push(redirectTo) instead of navigate(redirectTo, { replace: true })
// ... rest is identical to React versionProtected Page Example
import { ProtectedRoute } from '../components/ProtectedRoute';
import { DashboardContent } from './DashboardContent';
export default function DashboardPage() {
return (
<ProtectedRoute>
<DashboardContent />
</ProtectedRoute>
);
}'use client';
import { use } from 'react';
import { AuthContext } from '../providers/AuthProvider';
import { MemberProfile } from '../components/MemberProfile';
export function DashboardContent() {
const { signOut } = use(AuthContext);
return (
<div>
<h1>Dashboard</h1>
<MemberProfile />
<button onClick={signOut}>Sign Out</button>
</div>
);
}API Route with Token Validation
Validate Transcodes tokens in your API routes:
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const token = authHeader.split(' ')[1];
try {
// Validate token with your backend or Transcodes API
// The token is a JWT signed by Transcodes
return NextResponse.json({
message: 'Protected data',
// Return protected data
});
} catch (error) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
}Client-Side API Call
For the fetchWithAuth utility, see React Integration - API Calls with Token
SSR Considerations
The Transcodes SDK runs client-side only. It requires browser APIs (IndexedDB, WebAuthn) that are not available during server-side rendering
Hydration Handling
Use the isLoading state to prevent hydration mismatches:
'use client';
import { use } from 'react';
import { AuthContext } from '../providers/AuthProvider';
export function AuthStatus() {
const { isAuthenticated, isLoading } = use(AuthContext);
// Prevent hydration mismatch
if (isLoading) {
return <div>Loading...</div>;
}
return <div>{isAuthenticated ? 'Logged in' : 'Not logged in'}</div>;
}Dynamic Import (Alternative)
For components that should never render on server:
import dynamic from 'next/dynamic';
const AuthButton = dynamic(
() => import('./components/LoginButton').then((mod) => mod.LoginButton),
{ ssr: false }
);
export default function Page() {
return (
<div>
<h1>Welcome</h1>
<AuthButton />
</div>
);
}Custom Hook
For a simpler useAuth hook, see React Integration - Custom Hook. In Next.js, add 'use client' and use process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID:
'use client';
const projectId = process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID!;
// ... rest is identical to React versionBest Practices
Recommended patterns for using Transcodes in Next.js applications
- Use
'use client'directive: Auth-related components must be Client Components - Handle Loading State: Use
isLoadingto prevent hydration mismatches - Subscribe to Events: Use
AUTH_STATE_CHANGEDto sync React state - Always Cleanup: Unsubscribe from events in useEffect cleanup
- Async Methods: Remember
isAuthenticated()andgetAccessToken()are async - Environment Variables: Use
NEXT_PUBLIC_prefix for client-side access
Common Mistakes
See React Integration - Common Mistakes for common pitfalls including async isAuthenticated(), event name casing, and method names
Next Steps
- React Integration - Vite React SPA guide
- Vue Integration - Vue.js guide
- API Reference - Full API documentation
- Security Best Practices - Security guidelines