Sdks
Next.js SDK
Add authentication to Next.js App Router and Pages Router apps with @hellojohn/nextjs — middleware, server components, and client hooks.
Next.js SDK
@hellojohn/nextjs provides first-class support for Next.js, including App Router Server Components, middleware-based route protection, and server actions.
Installation
npm install @hellojohn/nextjsEnvironment variables
# .env.local
NEXT_PUBLIC_HJ_PUBLISHABLE_KEY=pk_live_...
HJ_SECRET_KEY=sk_live_...
NEXT_PUBLIC_HJ_API_URL=https://auth.yourdomain.com # self-hosted onlyMiddleware (route protection)
Create middleware.ts at the root of your project:
// middleware.ts
import { hellojohnMiddleware } from '@hellojohn/nextjs/server';
export default hellojohnMiddleware({
publicRoutes: ['/', '/sign-in(.*)', '/sign-up(.*)', '/about'],
afterSignInUrl: '/dashboard',
afterSignOutUrl: '/',
});
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\..*|api/public).*)',
],
};Any route not in publicRoutes redirects unauthenticated visitors to the sign-in page.
App Router — Server Components
Read auth state in Server Components and Server Actions:
// app/dashboard/page.tsx
import { auth, currentUser } from '@hellojohn/nextjs/server';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const { userId, tenantId } = auth();
if (!userId) redirect('/sign-in');
const user = await currentUser();
return (
<div>
<h1>Welcome, {user?.displayName}</h1>
<p>Tenant: {tenantId}</p>
</div>
);
}Server action with auth
// app/actions.ts
'use server';
import { auth } from '@hellojohn/nextjs/server';
import { createHelloJohnAdminClient } from '@hellojohn/node';
export async function updateProfile(formData: FormData) {
const { userId } = auth();
if (!userId) throw new Error('Unauthorized');
const hj = createHelloJohnAdminClient({ secretKey: process.env.HJ_SECRET_KEY! });
await hj.users.update(userId, {
displayName: formData.get('displayName') as string,
});
}Route handler with auth
// app/api/me/route.ts
import { auth } from '@hellojohn/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId, tenantId, role } = auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return NextResponse.json({ userId, tenantId, role });
}App Router — Client Components
// app/dashboard/profile.tsx
'use client';
import { useUser, useSession } from '@hellojohn/nextjs';
export function Profile() {
const { user, isLoaded } = useUser();
const { session, getToken } = useSession();
if (!isLoaded) return <div>Loading...</div>;
return (
<div>
<p>{user?.email}</p>
<button onClick={async () => {
const token = await getToken();
console.log('Token:', token);
}}>
Get token
</button>
</div>
);
}Layout setup (App Router)
// app/layout.tsx
import { HelloJohnProvider } from '@hellojohn/nextjs';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<HelloJohnProvider>
{children}
</HelloJohnProvider>
</body>
</html>
);
}Sign-in page
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@hellojohn/nextjs';
export default function SignInPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<SignIn
afterSignInUrl="/dashboard"
signUpUrl="/sign-up"
/>
</div>
);
}// app/sign-up/[[...sign-up]]/page.tsx
import { SignUp } from '@hellojohn/nextjs';
export default function SignUpPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<SignUp
afterSignUpUrl="/onboarding"
signInUrl="/sign-in"
/>
</div>
);
}Pages Router
// pages/_app.tsx
import { HelloJohnProvider } from '@hellojohn/nextjs';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<HelloJohnProvider {...pageProps}>
<Component {...pageProps} />
</HelloJohnProvider>
);
}// pages/api/me.ts
import { withAuth } from '@hellojohn/nextjs/api';
import type { NextApiRequest, NextApiResponse } from 'next';
export default withAuth(function handler(req: NextApiRequest, res: NextApiResponse) {
res.json({ user: req.auth.user });
});// pages/dashboard.tsx
import { GetServerSideProps } from 'next';
import { getAuth } from '@hellojohn/nextjs/ssr';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const { userId } = getAuth(ctx.req);
if (!userId) {
return { redirect: { destination: '/sign-in', permanent: false } };
}
return { props: {} };
};Passing the token to your API
When calling your own API routes from client components:
'use client';
import { useSession } from '@hellojohn/nextjs';
export function DataFetcher() {
const { getToken } = useSession();
const fetchData = async () => {
const token = await getToken();
const res = await fetch('/api/data', {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
};
}And verify it in the API route:
// app/api/data/route.ts
import { auth } from '@hellojohn/nextjs/server';
import { NextResponse } from 'next/server';
export async function GET() {
const { userId } = auth();
if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
// Fetch user-specific data
const data = await db.query('SELECT * FROM items WHERE user_id = $1', [userId]);
return NextResponse.json({ data });
}