Building with the SDK
Next.js
Components, server helpers and route protection.
npm install @zevauth/nextjs
The package has two entry points, and the split matters: everything that renders is a client component, and everything that verifies is server-only.
import { SignIn, useUser } from '@zevauth/nextjs'; // client
import { getAuth } from '@zevauth/nextjs/server'; // server
The client entry is marked 'use client', so importing <SignIn> into a
server component works instead of failing with a hooks error.
Provider
// app/layout.tsx
import { ZevAuthProvider } from '@zevauth/nextjs';
export default function RootLayout({ children }) {
return (
<html>
<body>
<ZevAuthProvider publishableKey={process.env.NEXT_PUBLIC_ZEVAUTH_KEY!}>
{children}
</ZevAuthProvider>
</body>
</html>
);
}
Reading auth on the server
import { getAuth } from '@zevauth/nextjs/server';
export async function GET(request: Request) {
const auth = await getAuth(request, {
environmentId: process.env.ZEVAUTH_ENVIRONMENT_ID!,
});
if (!auth.isSignedIn) return new Response('Unauthorized', { status: 401 });
return Response.json({ userId: auth.userId });
}
This verifies offline, against your environment’s published keys. It does not call ZevAuth on every request, so our availability is not in the path of every page you serve.
An expired, tampered or missing token all return isSignedIn: false rather
than throwing. To refuse instead, use requireAuth, which throws a Response:
const auth = await requireAuth(request, { environmentId });
Checking permissions
if (!auth.has({ permission: 'org:members:manage' })) {
return new Response('Forbidden', { status: 403 });
}
Middleware
// middleware.ts
import { createZevAuthMiddleware } from '@zevauth/nextjs/server';
export default createZevAuthMiddleware({
environmentId: process.env.ZEVAUTH_ENVIRONMENT_ID!,
protected: ['/dashboard', '/settings'],
});
export const config = { matcher: ['/((?!_next|.*\\..*).*)'] };
Protection is opt-in, path by path. A signed-out visitor to a protected
path is redirected to /sign-in with redirect_url set to where they were
going.
A string rule covers everything beneath it, so /dashboard also protects
/dashboard/billing. Regular expressions work too, and public wins over
protected:
createZevAuthMiddleware({
environmentId,
protected: [/^\/app/],
public: ['/app/health'],
}); Updated at, Friday, August 28, 2026