From 45c4f5f324a1f6446ace073f3a735d6b008dd383 Mon Sep 17 00:00:00 2001 From: Ankur Datta <64993082+ankur-arch@users.noreply.github.com> Date: Mon, 19 Oct 2026 12:31:40 +0200 Subject: [PATCH 1/3] docs: port the Clerk with Next.js guide to Prisma ORM 8 and Next.js 16 Rewrites /guides/authentication/clerk/nextjs for Prisma ORM 8, Next.js 16, and @clerk/nextjs 7 (proxy.ts, Show, direct webhooks link), adds the Prisma Compute deployment step, and moves the Prisma ORM 7 version to /guides/v7/authentication/clerk/nextjs. Co-Authored-By: Claude Fable 5.1 --- .../guides/authentication/clerk/nextjs.mdx | 1009 ++++++----------- apps/docs/content/docs/guides/index.mdx | 8 +- .../guides/v7/authentication/clerk/meta.json | 6 + .../guides/v7/authentication/clerk/nextjs.mdx | 836 ++++++++++++++ .../docs/guides/v7/authentication/meta.json | 6 + apps/docs/content/docs/guides/v7/index.mdx | 4 + apps/docs/content/docs/guides/v7/meta.json | 1 + apps/docs/cspell.json | 1 + 8 files changed, 1224 insertions(+), 647 deletions(-) create mode 100644 apps/docs/content/docs/guides/v7/authentication/clerk/meta.json create mode 100644 apps/docs/content/docs/guides/v7/authentication/clerk/nextjs.mdx create mode 100644 apps/docs/content/docs/guides/v7/authentication/meta.json diff --git a/apps/docs/content/docs/guides/authentication/clerk/nextjs.mdx b/apps/docs/content/docs/guides/authentication/clerk/nextjs.mdx index c391a6aa16b..927d3c75d2b 100644 --- a/apps/docs/content/docs/guides/authentication/clerk/nextjs.mdx +++ b/apps/docs/content/docs/guides/authentication/clerk/nextjs.mdx @@ -1,69 +1,134 @@ --- title: Clerk (with Next.js) -description: Learn how to use Prisma ORM in a Next.js app with Clerk Auth +description: 'Add Clerk authentication to a Next.js app, sync users to Prisma Postgres with a webhook, build a posts API, and deploy to Prisma Compute.' image: /img/guides/prisma-clerk-nextjs-cover.png url: /guides/authentication/clerk/nextjs -metaTitle: How to use Prisma ORM and Prisma Postgres with Clerk Auth and Next.js -metaDescription: Learn how to use Prisma ORM in a Next.js app with Clerk Auth +metaTitle: How to use Prisma ORM with Clerk and Next.js +metaDescription: 'Wire Clerk into a Next.js 16 app on Prisma ORM, store users in Prisma Postgres through a Clerk webhook, expose an authenticated posts API, and deploy to Prisma Compute.' --- -:::note[This guide uses Prisma ORM 7] +## Introduction -The commands and code on this page target Prisma ORM 7, which remains fully supported. To start a new Prisma ORM 8 project, see [Create a new app with PostgreSQL](/prisma-orm/quickstart/postgresql); to add Prisma ORM 8 to an existing app, see [Add Prisma ORM to an existing PostgreSQL database](/prisma-orm/add-to-existing-project/postgresql). A Prisma ORM 8 version of this guide is planned. +[Clerk](https://clerk.com/) is an auth provider: it handles sign-up, sign-in, user management, and webhooks. -::: +In this guide you wire Clerk into a new [Next.js](https://nextjs.org/) app, keep a copy of each Clerk user in a [Prisma Postgres](/postgres) database, expose a small posts API that only signed-in users can call, and deploy the app to [Prisma Compute](/compute). Every command below was run end to end against a live database. -## Introduction +:::note[Using Prisma ORM 7?] -[Clerk](https://clerk.com/) is a drop-in auth provider that handles sign-up, sign-in, user management, and webhooks so you don't have to. +Prisma ORM 8 is a [release candidate](/orm/release-status). Prisma ORM 7 remains fully supported; the Prisma ORM 7 version of this guide is at [/guides/v7/authentication/clerk/nextjs](/guides/v7/authentication/clerk/nextjs). -In this guide you'll wire Clerk into a brand-new [Next.js](https://nextjs.org/) app, persist users in a [Prisma Postgres](https://prisma.io/postgres) database, and expose a tiny posts API. You can find a complete example of this guide on [GitHub](https://github.com/prisma/prisma-examples/tree/latest/orm/clerk-nextjs). +::: ## Prerequisites -- [Node.js 20.19+ on 20.x, 22.12+ on 22.x, or 24+](https://nodejs.org) -- [Clerk account](https://clerk.com) -- [ngrok account](https://ngrok.com) +- [Node.js](https://nodejs.org) 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer (24 recommended) +- A PostgreSQL connection string, or nothing at all: `npx create-db@latest` creates a [Prisma Postgres](/postgres) database for you +- A [Clerk account](https://dashboard.clerk.com/sign-up) +- [ngrok](https://ngrok.com/docs/getting-started) installed and signed in (`ngrok config add-authtoken`), or any other tool that gives your local server a public URL, so Clerk can reach your webhook while you develop -## 1. Set up your project +## 1. Scaffold the project -Create the app: +Create a Next.js app with Prisma ORM already wired in. The `next` template generates the app, installs dependencies, and sets up the files that Prisma Compute deploys later. `--no-deploy` skips the scaffold's offer to deploy right away; you deploy in step 8: ```npm -npx create-next-app@latest clerk-nextjs-prisma +npm create prisma@latest -- clerk-nextjs-prisma --template next --provider postgres --no-deploy +cd clerk-nextjs-prisma ``` -It will prompt you to customize your setup. Choose the defaults: +The scaffold asks three questions, and this guide answers them as follows: write your models in Prisma Schema Language, the `.prisma` syntax you know from Prisma ORM 7 (Prisma ORM 8 calls the file the contract, and step 2 edits it); use npm, to match the commands on this page; and install the Prisma agent skills for the coding agents you use, or none. -:::info +Next, create a Prisma Postgres database with `create-db`. It needs no account and prints a connection string and a claim URL; the database is deleted after 24 hours unless you open the claim URL, which adds it to your Prisma Data Platform account: -- _Would you like to use TypeScript?_ `Yes` -- _Would you like to use ESLint?_ `Yes` -- _Would you like to use Tailwind CSS?_ `Yes` -- _Would you like your code inside a `src/` directory?_ `No` -- _Would you like to use App Router?_ (recommended) `Yes` -- _Would you like to use Turbopack for `next dev`?_ `Yes` -- _Would you like to customize the import alias (`@/_`by default)?*`No` +```npm +npx create-db@latest +``` -::: +Export the connection string in the shell you work in. The Prisma CLI, and the `npm run` scripts in `package.json` that call it, read the environment variable, not `.env`: -Navigate to the project directory: +```bash +export DATABASE_URL="" +``` + +## 2. Define the contract + +### 2.1. Replace the models + +The contract holds only models: the database connection lives in `prisma.config.ts`, and there are no `datasource` or `generator` blocks. The template ships demo models with seeded users. Replace the whole file with a `User` model that stores the Clerk user id next to the email, and a `Post` model that belongs to a user. Keep the `// use prisma-8` line: every Prisma ORM 8 contract file starts with it. + +In Prisma ORM 8, `DateTime` reads and writes `Temporal.Instant` objects. `TimestamptzString` is the same timestamp with time zone column read and written as a plain string, which is enough for a timestamp you only sort by. + +```prisma title="src/prisma/contract.prisma" +// use prisma-8 + +model User { + id Int @id @default(autoincrement()) + clerkId String @unique + email String @unique + name String? + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + content String? + author User @relation(fields: [authorId], references: [id]) + authorId Int + createdAt TimestamptzString @default(now()) +} +``` + +The template's seed helpers refer to fields that no longer exist, so delete them: ```bash -cd clerk-nextjs-prisma +rm src/prisma/seed.ts src/prisma/users.ts ``` -## 2. Set up Clerk +### 2.2. Emit the contract -### 2.1. Create a new Clerk application +Emitting the contract replaces `prisma generate`: it writes the types your queries are checked against into `src/prisma/`: -[Sign in](https://dashboard.clerk.com/sign-in) to Clerk and navigate to the home page. From there, press the `Create Application` button to create a new application. Enter a title, select your sign-in options, and click `Create Application`. +```npm +npm run contract:emit +``` -:::info +### 2.3. Plan the migration -For this guide, the Google, Github, and Email sign in options will be used. +The template also ships a migration for its demo models. Delete it and plan one that creates your models. `migration plan` writes the migration file without touching the database; the deploy in step 8 builds the production database by replaying `migrations/`, so it needs this file: -::: +```bash +rm -rf migrations +``` + +```npm +npx prisma migration plan --name init +``` + +```json no-copy +"dir": "migrations/app/20261019T0955_init" +``` + +### 2.4. Create the tables + +Now create the tables in the database from step 1. `db:init` applies the contract directly and records in the database which contract it matches (the output calls this signing). It does not replay the migration you just planned; that file is for the deploy: + +```npm +npm run db:init +``` + +```text no-copy +"summary": "Applied 6 operation(s) across 1 space(s), database signed" +``` + +If `db:init` stops with `Connection terminated unexpectedly`, a database you just created is still starting; wait a few seconds and run it again. The command is safe to repeat. + +## 3. Set up Clerk + +### 3.1. Create a Clerk application + +[Sign in](https://dashboard.clerk.com/sign-in) to Clerk and create an application, or open one you already have. On the application's overview page, select **Next.js** and the **Manual setup** tab: it shows the install command and your API keys. + +Under **Configure** > **User & authentication** > **Email**, turn on **Sign-up with email**, **Require email address**, and **Sign-in with email**. The sign-up form only shows the fields an application requires, so without **Require email address** the form offers social sign-in buttons only. Install the Clerk Next.js SDK: @@ -71,109 +136,60 @@ Install the Clerk Next.js SDK: npm install @clerk/nextjs ``` -Copy your Clerk keys and paste them into **.env** in the root of your project: +Create a `.env.local` file in the project root and paste your keys from the dashboard. Next.js loads this file in development and at build time; the Prisma CLI does not, which is why `DATABASE_URL` stays exported in your shell. Use `.env.local` rather than `.env`: with `output: "standalone"`, `next build` copies `.env` into the build output that step 8 uploads, and `.env.local` stays out of it: -```bash title=".env" -# Clerk # [!code ++] -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= # [!code ++] -CLERK_SECRET_KEY= # [!code ++] +```bash title=".env.local" +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= ``` -### 2.2. Protect routes with Clerk middleware +The template's `.gitignore` lists only `.env`, so add the new file to it: -The `clerkMiddleware` helper enables authentication and is where you'll configure your protected routes. +```bash title=".gitignore" +.env.local # [!code ++] +``` -Create a `middleware.ts` file in the root directory of your project: +### 3.2. Add `clerkMiddleware()` as the Next.js proxy -```tsx title="middleware.ts" showLineNumbers -import { clerkMiddleware } from "@clerk/nextjs/server"; // [!code ++] -// [!code ++] -export default clerkMiddleware(); // [!code ++] -// [!code ++] -export const config = { - // [!code ++] - matcher: [ - // [!code ++] - "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", // [!code ++] - "/(api|trpc)(.*)", // [!code ++] - ], // [!code ++] -}; // [!code ++] -``` +`clerkMiddleware()` reads the Clerk session on every request so that `auth()` and `currentUser()` work in your routes. Next.js 16 runs this kind of code from a `proxy.ts` file (the file was called `middleware.ts` in Next.js 15 and older). The template puts the app in `src/`, so create the file there: -### 2.3. Add Clerk UI to your layout +```ts title="src/proxy.ts" +import { clerkMiddleware } from "@clerk/nextjs/server"; -Next, you'll need to wrap your app in the `ClerkProvider` component to make authentication globally available. +export default clerkMiddleware(); -In your `layout.tsx` file, add the `ClerkProvider` component: +export const config = { + matcher: [ + // Skip Next.js internals and all static files, unless found in search params + "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", + // Always run for API routes + "/(api|trpc)(.*)", + // Always run for Clerk-specific frontend API routes + "/__clerk/(.*)", + ], +}; +``` -```tsx title="app/layout.tsx" showLineNumbers -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; -import { ClerkProvider } from "@clerk/nextjs"; // [!code ++] +:::note -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); +On Next.js 15 or older, name the file `middleware.ts` instead. The code is the same; only the file name changes. -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +::: -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; +`clerkMiddleware()` leaves every route public, and the routes in this guide check for a user themselves, which is where Clerk recommends the check lives. -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {" "} - // [!code ++] - - - {children} - - - // [!code ++] - ); -} -``` +### 3.3. Add the Clerk provider and sign-in buttons -Create a `Navbar` component which will be used to display the Sign In and Sign Up buttons as well as the User Button once a user is signed in: +Replace `src/app/layout.tsx` so that `ClerkProvider` wraps the app and a header shows sign-in, sign-up, and user buttons. `Show` renders its children only when the condition in `when` holds, so the header shows the sign-in and sign-up buttons to visitors and the user button to signed-in users; it replaces the `SignedIn` and `SignedOut` components from earlier Clerk versions. `mode="modal"` opens the sign-in and sign-up forms on your page instead of redirecting to Clerk's hosted pages: -```tsx title="app/layout.tsx" showLineNumbers +```tsx title="src/app/layout.tsx" import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { ClerkProvider, Show, SignInButton, SignUpButton, UserButton } from "@clerk/nextjs"; import "./globals.css"; -import { - ClerkProvider, - UserButton, // [!code ++] - SignInButton, // [!code ++] - SignUpButton, // [!code ++] - SignedOut, // [!code ++] - SignedIn, // [!code ++] -} from "@clerk/nextjs"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "clerk-nextjs-prisma", + description: "Generated by create-prisma", }; export default function RootLayout({ @@ -182,661 +198,362 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - - // [!code ++] + + + +
+ + + + + + + +
{children} - - -
+
+ + ); } - -const Navbar = () => { - // [!code ++] - return ( - // [!code ++] -
- {" "} - // [!code ++] - - {" "} - // [!code ++] - // [!code ++] - // [!code ++] - {" "} - // [!code ++] - - {" "} - // [!code ++] - // [!code ++] - {" "} - // [!code ++] -
// [!code ++] - ); // [!code ++] -}; // [!code ++] -``` - -## 3. Install and configure Prisma - -### 3.1. Install dependencies - -To get started with Prisma, you'll need to install a few dependencies: - -```npm -npm install prisma@prev tsx @types/pg --save-dev ``` -```npm -npm install @prisma/client@7 @prisma/adapter-pg dotenv pg -``` +Append styles for the header, and for the post form you build in step 5, to the template's stylesheet: -:::info +```css title="src/app/globals.css" +.navbar { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 1rem; + height: 4rem; + padding: 0 1rem; +} -If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of `@prisma/adapter-pg`. For more information, see [Database drivers](/orm/v7/core-concepts/supported-databases/database-drivers). +.post-form { + display: grid; + gap: 0.5rem; + margin-bottom: 1.5rem; +} -::: +.post-form input, +.post-form textarea, +.post-form button { + padding: 0.5rem; + border: 1px solid #e5e5e5; + border-radius: 0.375rem; + font: inherit; +} +``` -Once installed, initialize Prisma in your project: +## 4. Build a posts API -```npm -npx prisma init --output ../app/generated/prisma -``` +Create a route that stores a post for the signed-in user. `auth()` returns the Clerk user id from the session; a request without one gets `401`. The route then looks up the matching database user, so a post always points at a `User` row: -:::info +```ts title="src/app/api/posts/route.ts" +import { auth } from "@clerk/nextjs/server"; +import { db } from "@/src/prisma/db"; -`prisma init` creates the Prisma scaffolding and a local `DATABASE_URL`. In the next step, you will create a Prisma Postgres database and replace that value with a direct `postgres://...` connection string. +export async function POST(req: Request) { + const { userId: clerkId } = await auth(); + if (!clerkId) return new Response("Unauthorized", { status: 401 }); -::: -This will create: + const { title, content } = await req.json(); -- A `prisma/` directory with a `schema.prisma` file -- A local `DATABASE_URL` in `.env` + const user = await db.orm.public.User.where({ clerkId }).first(); + if (!user) return new Response("User not found", { status: 404 }); -Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output: + const post = await db.orm.public.Post.create({ + title, + content, + authorId: user.id, + }); -```npm -npx create-db + return Response.json(post, { status: 201 }); +} ``` -### 3.2. Define your Prisma Schema +`db` is the Prisma ORM client the template created in `src/prisma/db.ts`, and the template maps `@/` to the project root, so the import path is `@/src/prisma/db`. `db.orm` is the ORM query API, and on PostgreSQL every model sits under its schema, so the `User` table is `db.orm.public.User`. `.first()` returns one matching record or `null`, and `.create()` takes the fields directly and returns the inserted row. [Reading data](/orm/fundamentals/reading-data) and [Writing data](/orm/fundamentals/writing-data) cover the rest of the query API. -In the `prisma/schema.prisma` file, add the following models: - -```prisma title="prisma/schema.prisma" showLineNumbers -generator client { - provider = "prisma-client" - output = "../app/generated/prisma" -} +## 5. Add a post form -datasource db { - provider = "postgresql" -} +Create a client component with a title field, a content field, and a submit button. On submit it calls the posts API, clears the fields, and reloads the page so the new post appears in the list: -model User { // [!code ++] - id Int @id @default(autoincrement()) // [!code ++] - clerkId String @unique // [!code ++] - email String @unique // [!code ++] - name String? // [!code ++] - posts Post[] // [!code ++] -} // [!code ++] - // [!code ++] -model Post { // [!code ++] - id Int @id @default(autoincrement()) // [!code ++] - title String // [!code ++] - content String? // [!code ++] - published Boolean @default(false) // [!code ++] - authorId Int // [!code ++] - author User @relation(fields: [authorId], references: [id]) // [!code ++] - createdAt DateTime @default(now()) // [!code ++] -} // [!code ++] -``` - -This will create two models: `User` and `Post`, with a one-to-many relationship between them. - -Create a `prisma.config.ts` file to configure Prisma: - -```typescript title="prisma.config.ts" showLineNumbers -import "dotenv/config"; // [!code ++] -import { defineConfig, env } from "prisma/config"; // [!code ++] -// [!code ++] -export default defineConfig({ - // [!code ++] - schema: "prisma/schema.prisma", // [!code ++] - migrations: { - // [!code ++] - path: "prisma/migrations", // [!code ++] - }, // [!code ++] - datasource: { - // [!code ++] - url: env("DATABASE_URL"), // [!code ++] - }, // [!code ++] -}); // [!code ++] -``` +```tsx title="src/app/components/PostInputs.tsx" +"use client"; -:::note +import { useState } from "react"; -You'll need to install the `dotenv` package: +export default function PostInputs() { + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); -```npm -npm install dotenv -``` + async function createPost(e: React.FormEvent) { + e.preventDefault(); + if (!title || !content) return; -::: + await fetch("/api/posts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ title, content }), + }); -Now, run the following command to create the database tables and generate the Prisma Client: + setTitle(""); + setContent(""); + location.reload(); + } -```npm -npx prisma migrate dev --name init + return ( +
+ setTitle(e.target.value)} + /> +