Skip to Content

Next.js Integration

⚡ 12 min read

Integrate 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 localhost for 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.

ApproachBest for
next/script + CDNload before hydration (beforeInteractive)
npm @bigstrider/transcodes-sdkAuth-only apps

Installation

Set Environment Variables

.env.local
NEXT_PUBLIC_TRANSCODES_PROJECT_ID=proj_abc123xyz

Option A: Script in root layout (default — Dashboard flow)

Add SDK Script to Root Layout

Authentication Toolkit Cluster only:

app/layout.tsx
<Script src={`https://cdn.transcodes.link/${process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID}/webworker.js`} strategy='beforeInteractive' />

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:

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-sdk

Call 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:

app/providers/TranscodesInitProvider.tsx
'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).


Auth Context (Client Component)

See React Integration - Auth Context for the full implementation. For Next.js, add 'use client' and use process.env:

app/providers/AuthProvider.tsx
'use client'; // Use process.env instead of import.meta.env const projectId = process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID!; // ... rest is identical to React version

Root Layout with Provider

app/layout.tsx
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:

app/layout.tsx (npm path, sketch)
<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 path

Protected Route Component

See React Integration - Protected Route for the full implementation. For Next.js, use next/navigation:

app/components/ProtectedRoute.tsx
'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 version

Protected Page Example

app/dashboard/page.tsx
import { ProtectedRoute } from '../components/ProtectedRoute'; import { DashboardContent } from './DashboardContent'; export default function DashboardPage() { return ( <ProtectedRoute> <DashboardContent /> </ProtectedRoute> ); }
app/dashboard/DashboardContent.tsx
'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:

app/api/protected/route.ts
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:

app/components/AuthStatus.tsx
'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:

app/page.tsx
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:

app/hooks/useAuth.ts
'use client'; const projectId = process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID!; // ... rest is identical to React version

Best Practices

Recommended patterns for using Transcodes in Next.js applications

  1. Use 'use client' directive: Auth-related components must be Client Components
  2. Handle Loading State: Use isLoading to prevent hydration mismatches
  3. Subscribe to Events: Use AUTH_STATE_CHANGED to sync React state
  4. Always Cleanup: Unsubscribe from events in useEffect cleanup
  5. Async Methods: Remember isAuthenticated() and getAccessToken() are async
  6. 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

Last updated on