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.


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.


Installation

Set Environment Variables

.env.local
NEXT_PUBLIC_TRANSCODES_PROJECT_ID=proj_abc123xyz

Add SDK Script to Root Layout

app/layout.tsx
import Script from 'next/script'; 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>{children}</body> </html> ); }

Use strategy="beforeInteractive" to ensure the SDK loads before your app hydrates.

Add TypeScript Type Definitions

Download the complete type definitions from the API Reference 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"] }

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> ); }

Components

For LoginButton and UserProfile 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 { UserProfile } from '../components/UserProfile'; export function DashboardContent() { const { signOut } = use(AuthContext); return ( <div> <h1>Dashboard</h1> <UserProfile /> <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