Quick Navigation

SectionDescription
InstallationHow to install the Next.js SDK
Basic SetupSetting up providers and middleware
Server-Side FeaturesServer components and authentication
Client-Side FeaturesClient components and hooks
Authentication FlowComplete auth implementation
Protected RoutesRoute protection strategies
Data AccessAccessing user data (server/client)
Best PracticesRecommended patterns
Type DefinitionsTypeScript interfaces
ExamplesCommon use cases

Installation

npm install @rownd/next
# or
yarn add @rownd/next

Basic Setup

1. Provider Setup

In your root layout.tsx:

import { RowndProvider } from '@rownd/next';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <RowndProvider
          appKey="<your app key>"
          apiUrl="<optional: your api url>"  // Enterprise only
          hubUrlOverride="<optional: your hub url>"  // Enterprise only
          postLoginRedirect="/dashboard"  // Optional
          postLogoutRedirect="/"  // Optional
        >
          {children}
        </RowndProvider>
      </body>
    </html>
  );
}

2. Middleware Setup

In your middleware.ts:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { withRowndMiddleware } from '@rownd/next/server';

export const middleware = withRowndMiddleware((request: NextRequest) => {
  return NextResponse.next();
});

export const config = {
  matcher: [
    // Required for Rownd token handling
    '/api/rownd-token-callback',
    // Add your protected routes
    '/protected/:path*',
    '/api/protected/:path*'
  ]
};

Server-Side Features

The @rownd/next/server package provides server-side authentication utilities:

import {
  withRowndRequireSignIn,
  getRowndUser,
  getAccessToken,
  isAuthenticated,
  getRowndUserId,
} from '@rownd/next/server';
import { cookies } from 'next/headers';

Server-Side Functions

FunctionDescriptionParametersReturn TypeUsage Example
getRowndUserRetrieves the current authenticated user’s data from Rowndcookies: ReadonlyRequestCookiesPromise<RowndServerUser | null>const user = await getRowndUser(cookies)
getRowndUserIdGets the current user’s ID from server contextcookies: ReadonlyRequestCookiesPromise<string | null>const userId = await getRowndUserId(cookies)
getRowndAccessTokenRetrieves the current access token from server contextcookies: ReadonlyRequestCookiesPromise<string | null>const token = await getRowndAccessToken(cookies)
isAuthenticatedChecks if there is an authenticated user in server contextcookies: ReadonlyRequestCookiesPromise<boolean>const isAuth = await isAuthenticated(cookies)
withRowndMiddlewareHigher-order function for Next.js middleware to handle authenticationmiddleware: NextMiddlewareNextMiddlewareSee middleware example below
withRowndRequireSignInHigher-order function to protect routes/pages/API handlershandler: Function, cookies: ReadonlyRequestCookiesFunctionSee protected routes example below

Example Usage

// Middleware setup
export const config = {
  matcher: [
    '/api/rownd-token-callback',  // Required for token handling
    '/protected/:path*'         // Your protected routes
  ]
};

// Protected API route
async function handler(req: Request) {
  const userId = await getRowndUserId(cookies);
  const token = await getRowndAccessToken(cookies);
  const user = await getRowndUser(cookies);
  
  if (!userId) {
    return Response.json({ error: 'Not authenticated' }, { status: 401 });
  }
  
  return Response.json({ userId, user });
}

export const GET = withRowndRequireSignIn(handler, cookies);

Client-Side Features

The client-side features mirror the React SDK functionality but must be used within client components:

The useRownd Hook

The useRownd hook provides comprehensive authentication and user management functionality:

State Properties

PropertyTypeDescriptionExample Usage
is_initializingbooleanWhether Rownd is still loadingif (is_initializing) return <Loading />
is_authenticatedbooleanWhether user is currently signed inif (is_authenticated) showDashboard()
access_tokenstring | nullCurrent JWT access tokenheaders: { Authorization: Bearer ${access_token} }
user.dataRecord<string, any>User’s profile dataconst { first_name, email } = user.datauser.groupsstring[]Groups the user belongs toif (user.groups.includes('admin'))

Authentication Methods

MethodDescriptionParametersReturn Type
requestSignIn()Triggers sign-in flow{ auto_sign_in?: boolean, identifier?: string, method?: string, post_login_redirect?: string }void
signOut()Signs out current userNonevoid
getAccessToken()Gets current access token{ waitForToken?: boolean, timeoutMs?: number }Promise<string>

User Data Methods

MethodDescriptionParametersReturn Type
setUser()Updates multiple user fieldsRecord<string, any>Promise<void>
setUserValue()Updates a single user field(field: string, value: any)Promise<void>
manageAccount()Opens account management UINonevoid

Additional Features

FeatureDescriptionParametersReturn Type
getFirebaseIdToken()Gets Firebase ID tokenNonePromise<string>
getAppConfig()Gets app configurationNonePromise<AppConfig>
passkeysPasskey authentication methods{ register: () => Promise<void>, authenticate: () => Promise<void> }Object
eventsEvent system for state changesaddEventListener(event: string, callback: Function)EventEmitter

Example Usage

'use client';

function ProfileManager() {
  const {
    is_authenticated,
    is_initializing,
    user,
    setUser,
    manageAccount,
    getAccessToken
  } = useRownd();

  if (is_initializing) return <div>Loading...</div>;
  if (!is_authenticated) return <div>Please sign in</div>;

  const updateProfile = async () => {
    await setUser({
      first_name: 'John',
      last_name: 'Doe'
    });
  };

  const fetchProtectedData = async () => {
    const token = await getAccessToken({ waitForToken: true });
    // Use token for API calls
  };

  return (
    <div>
      <h1>Welcome {user.data.first_name}!</h1>
      <button onClick={updateProfile}>Update Profile</button>
      <button onClick={manageAccount}>Manage Account</button>
    </div>
  );
}

Authentication Flow

Combined Server/Client Authentication

// app/profile/page.tsx
import { getRowndUser } from '@rownd/next/server';
import { cookies } from 'next/headers';
import { ClientProfile } from './client-profile';

// Server Component
export default async function ProfilePage() {
  const serverUser = await getRowndUser(cookies);
  
  if (!serverUser) {
    return <div>Please sign in</div>;
  }
  
  // Initial data from server
  return (
    <div>
      <h1>Server-rendered Profile</h1>
      <pre>{JSON.stringify(serverUser.data, null, 2)}</pre>
      
      {/* Client component for real-time updates */}
      <ClientProfile initialData={serverUser} />
    </div>
  );
}

// client-profile.tsx
'use client';

export function ClientProfile({ initialData }) {
  const { user, is_initializing } = useRownd();
  
  if (is_initializing) {
    return <div>Syncing...</div>;
  }
  
  return (
    <div>
      <h2>Real-time Profile Updates</h2>
      <pre>{JSON.stringify(user.data, null, 2)}</pre>
    </div>
  );
}

Protected Routes

Server-Side Protection

// middleware.ts
import { withRowndMiddleware } from '@rownd/next/server';

export const middleware = withRowndMiddleware((request: NextRequest) => {
  // Protect specific paths
  if (request.nextUrl.pathname.startsWith('/protected')) {
    // Additional custom logic
  }
  return NextResponse.next();
});

Component-Level Protection

// Protected Server Component
import { withRowndRequireSignIn } from '@rownd/next/server';
import { cookies } from 'next/headers';

async function ProtectedPage() {
  const user = await getRowndUser(cookies);
  return <div>Protected Content for {user.data.email}</div>;
}

export default withRowndRequireSignIn(ProtectedPage, cookies);

// Protected Client Component
'use client';

function ProtectedClientComponent() {
  const { is_authenticated, is_initializing } = useRownd();
  
  if (is_initializing) return <div>Loading...</div>;
  if (!is_authenticated) return <div>Please sign in</div>;
  
  return <div>Protected Client Content</div>;
}

Data Access

Server-Side Data Access

async function ServerComponent() {
  const user = await getRowndUser(cookies);
  const token = await getAccessToken(cookies);
  
  // Make authenticated API calls
  const response = await fetch('https://api.example.com/data', {
    headers: {
      Authorization: `Bearer ${token}`
    }
  });
  
  return <div>User: {user.data.email}</div>;
}

Client-Side Data Access

'use client';

function ClientComponent() {
  const { user, getAccessToken } = useRownd();
  
  const fetchData = async () => {
    const token = await getAccessToken();
    // Make authenticated API calls
  };
  
  return <div>User: {user.data.email}</div>;
}

Best Practices

  1. Server/Client Separation

    // Server Component
    export default async function Page() {
      const serverUser = await getRowndUser(cookies);
      return <ClientComponent initialData={serverUser} />;
    }
    
    // Client Component
    'use client';
    export function ClientComponent({ initialData }) {
      const { user } = useRownd();
      // Use initialData for SSR, user for updates
    }
    
  2. Efficient Token Handling

    // Server-side
    const token = await getAccessToken(cookies);
    // Token includes app_id and user_id
    
    // Client-side
    const { getAccessToken } = useRownd();
    const token = await getAccessToken();
    
  3. Error Boundaries

    'use client';
    
    class RowndErrorBoundary extends React.Component {
      // Implementation
    }
    

Type Definitions

Core Types

TypeDescriptionInterface
RowndServerUserServer-side user type{ data: Record<string, any>; access_token: string; app_id: string; user_id: string; }
RowndUserClient-side user type{ data: Record<string, any>; groups: string[]; redacted_fields: string[]; verified_data: Record<string, any>; meta: UserMeta; }
UserMetaUser metadata{ created_at: string; updated_at: string; last_sign_in: string; }
SignInOptionsSign-in configuration{ auto_sign_in?: boolean; identifier?: string; method?: AuthMethod; post_login_redirect?: string; }
TokenOptionsToken retrieval options{ waitForToken?: boolean; timeoutMs?: number; }
AuthMethodAuthentication methods'email' | 'phone' | 'google' | 'apple' | 'passkey' | 'anonymous'

Server-Side Types

// Server-side interfaces
interface RowndServerUser {
  data: Record<string, any>;
  access_token: string;
  app_id: string;
  user_id: string;
}

interface RowndServerConfig {
  app_key: string;
  api_url?: string;
  hub_url_override?: string;
}

type RowndMiddlewareConfig = {
  matcher: string[];
};

// Server utility types
type GetUserFn = (cookies: ReadonlyRequestCookies) => Promise<RowndServerUser | null>;
type GetTokenFn = (cookies: ReadonlyRequestCookies) => Promise<string | null>;
type IsAuthenticatedFn = (cookies: ReadonlyRequestCookies) => Promise<boolean>;

Client-Side Types

// Client-side interfaces
interface RowndUser {
  data: Record<string, any>;
  groups: string[];
  redacted_fields: string[];
  verified_data: Record<string, any>;
  meta: {
    created_at: string;
    updated_at: string;
    last_sign_in: string;
  };
  is_loading: boolean;
}

interface UseRowndReturn {
  is_initializing: boolean;
  is_authenticated: boolean;
  user: RowndUser;
  requestSignIn: (options?: SignInOptions) => void;
  signOut: () => void;
  getAccessToken: (options?: TokenOptions) => Promise<string>;
  setUser: (data: Record<string, any>) => Promise<void>;
  setUserValue: <T>(field: string, value: T) => Promise<void>;
  manageAccount: () => void;
  getFirebaseIdToken: () => Promise<string>;
  getAppConfig: () => Promise<AppConfig>;
  passkeys: PasskeyMethods;
  events: EventEmitter;
}

TypeScript Examples

Server-Side Example

// app/api/user/route.ts
import { 
  getRowndUser, 
  getRowndAccessToken,
  withRowndRequireSignIn 
} from '@rownd/next/server';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';

interface UserPreferences {
  theme: 'light' | 'dark';
  notifications: boolean;
}

async function handler(req: Request) {
  try {
    const user = await getRowndUser(cookies);
    const token = await getRowndAccessToken(cookies);
    
    if (!user) {
      return NextResponse.json(
        { error: 'Not authenticated' },
        { status: 401 }
      );
    }
    
    // Type-safe user data access
    const preferences: UserPreferences = {
      theme: user.data.theme || 'light',
      notifications: user.data.notifications ?? true
    };
    
    return NextResponse.json({
      user_id: user.user_id,
      preferences,
      token
    });
  } catch (error) {
    return NextResponse.json(
      { error: 'Server error' },
      { status: 500 }
    );
  }
}

export const GET = withRowndRequireSignIn(handler, cookies);

Client-Side Example

// components/user-profile.tsx
'use client';

import { useRownd } from '@rownd/next';
import { useState } from 'react';

interface ProfileFormData {
  first_name: string;
  last_name: string;
  email: string;
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
}

interface ProfileProps {
  initialData?: Partial<ProfileFormData>;
}

export function UserProfile({ initialData }: ProfileProps) {
  const { 
    user,
    is_authenticated,
    is_initializing,
    setUser,
    getAccessToken
  } = useRownd();
  
  const [formData, setFormData] = useState<ProfileFormData>({
    first_name: initialData?.first_name || '',
    last_name: initialData?.last_name || '',
    email: initialData?.email || '',
    preferences: initialData?.preferences || {
      theme: 'light',
      notifications: true
    }
  });

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await setUser(formData);
      const token = await getAccessToken({ waitForToken: true });
      
      // Make authenticated API call
      await fetch('/api/user/preferences', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(formData.preferences)
      });
    } catch (error) {
      console.error('Failed to update profile:', error);
    }
  };

  if (is_initializing) {
    return <div>Loading profile...</div>;
  }

  if (!is_authenticated) {
    return <div>Please sign in to view your profile</div>;
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="first_name">First Name:</label>
        <input
          id="first_name"
          type="text"
          value={formData.first_name}
          onChange={e => setFormData(prev => ({
            ...prev,
            first_name: e.target.value
          }))}
        />
      </div>

      <div>
        <label htmlFor="email">Email:</label>
        <input
          id="email"
          type="email"
          value={formData.email}
          onChange={e => setFormData(prev => ({
            ...prev,
            email: e.target.value
          }))}
        />
        {user.verified_data.email === formData.email && (
          <span>✓ Verified</span>
        )}
      </div>

      <div>
        <label>
          <input
            type="checkbox"
            checked={formData.preferences.notifications}
            onChange={e => setFormData(prev => ({
              ...prev,
              preferences: {
                ...prev.preferences,
                notifications: e.target.checked
              }
            }))}
          />
          Enable Notifications
        </label>
      </div>

      <button type="submit">Save Changes</button>
    </form>
  );
}

Combined Server/Client Example

// app/profile/page.tsx
import { getRowndUser } from '@rownd/next/server';
import { cookies } from 'next/headers';
import { UserProfile } from '@/components/user-profile';

interface PageProps {
  params: Record<string, string>;
  searchParams: Record<string, string>;
}

export default async function ProfilePage({ params, searchParams }: PageProps) {
  const user = await getRowndUser(cookies);
  
  if (!user) {
    return <div>Please sign in to view your profile</div>;
  }
  
  // Pass server-fetched data to client component
  return (
    <div>
      <h1>User Profile</h1>
      <UserProfile 
        initialData={{
          first_name: user.data.first_name,
          last_name: user.data.last_name,
          email: user.data.email,
          preferences: user.data.preferences
        }} 
      />
    </div>
  );
}

Examples

Full Authentication Flow

// app/auth/page.tsx
import { getRowndUser } from '@rownd/next/server';
import { cookies } from 'next/headers';
import { AuthClient } from './auth-client';

export default async function AuthPage() {
  const serverUser = await getRowndUser(cookies);
  return <AuthClient initialUser={serverUser} />;
}

// auth-client.tsx
'use client';

export function AuthClient({ initialUser }) {
  const { 
    is_initializing,
    is_authenticated,
    user,
    requestSignIn,
    signOut
  } = useRownd();

  if (is_initializing) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      {is_authenticated ? (
        <div>
          <h1>Welcome {user.data.first_name}!</h1>
          <button onClick={signOut}>Sign Out</button>
        </div>
      ) : (
        <button onClick={() => requestSignIn()}>Sign In</button>
      )}
    </div>
  );
}

For more examples and detailed client-side features, refer to the React SDK documentation.