Next.js
This guide installs Auth UI in a Next.js App Router app with a Convex backend. jsrepo copies the components, routes and Convex functions from the Convex-Better-Auth-UI registry into your project, and you own them from then on. At the end you have password sign-in with every non-public page behind a session.
For a new app, follow every step. For an existing app, skip step 1 and read What the install replaces before step 2.
1. Create a Next.js app
This runs without prompts and creates a TypeScript App Router app with Tailwind CSS, a src/ directory and the @/* import alias, with React Compiler off and an AGENTS.md for coding agents. An existing app needs the App Router, a src/ directory and the @/* alias, because the registry’s files assume them.
2. Install Auth UI
Run the install from the project root. During jsrepo init @auth/nextjs, press Enter to accept the default paths.
In a new app:
--all adds every registry item, --overwrite replaces existing files without asking, and --yes skips the confirmation. jsrepo installs the npm dependencies the items need.
In an existing app, commit your work first and leave out --overwrite --yes. jsrepo then asks before it changes a file, so you can keep yours and merge the parts Auth UI needs:
Finish the Next.js config
Auth UI builds its CSS with the Tailwind PostCSS plugin: the install adds postcss.config.mjs and the @tailwindcss/postcss package. It also adds next.config.mjs. Next.js loads next.config.mjs before next.config.ts, so a next.config.ts from create-next-app or your own app is ignored from now on. Move any setting you still need into next.config.mjs, then delete next.config.ts.
What the install replaces
These files already exist in most Next.js apps. With --overwrite, the install replaces them:
| File | Item | What Auth UI puts there |
|---|---|---|
next.config.mjs | base | Allows images from your Convex deployment’s host |
postcss.config.mjs | base | The @tailwindcss/postcss plugin |
components.json | base | The shadcn configuration |
src/proxy.ts | base | Route protection |
src/app/layout.tsx | base | <html data-theme="auth">, the providers, the header with OrganizationSwitcher and UserButton, the profile dialogs, and the server-side preload of the user and organizations |
src/app/page.tsx | base | An empty home page |
src/app/globals.css | base | Tailwind, Skeleton and the auth theme from src/themes/auth.css |
src/lib/utils.ts | primitives | The cn() class helper, which shadcn/ui apps already have |
convex.json | base | Sets the Convex functions folder to src/convex/ |
src/convex/schema.ts, http.ts, convex.config.ts, auth.config.ts | base | The Better Auth component, its HTTP routes and the Resend webhook |
src/convex/auth.ts, auth.constants.ts | config | The Better Auth setup and the feature flags |
If your app already has Convex functions, convex.json moves the functions folder to src/convex/, and your schema.ts, http.ts and convex.config.ts need merging with Auth UI’s by hand.
3. Start Convex
On its first run, npx convex dev logs you in, creates a project and a development deployment, and writes NEXT_PUBLIC_CONVEX_URL and NEXT_PUBLIC_CONVEX_SITE_URL to .env.local. Auth UI reads these two names; without them the server throws NEXT_PUBLIC_CONVEX_URL must be set.
Leave it running in its own terminal. It deploys your Convex functions on every change, and next dev doesn’t start it.
4. Set the Convex environment variables
In a second terminal, generate the secret Better Auth uses for encryption and hashing:
Allow your local hosts and set the fallback URL:
BETTER_AUTH_ALLOWED_HOSTS lists the hosts Better Auth accepts requests from and builds URLs for. BETTER_AUTH_FALLBACK_URL is used only when Better Auth runs without an incoming request, as in Convex queries, mutations, triggers, scheduled functions and device authorization.
Better Auth reads the protocol from the request and proxy headers. Cookies get the __Secure- prefix and the Secure attribute when BETTER_AUTH_FALLBACK_URL is an https URL, so the http URL here keeps them plain for local development. Keep it http there: Safari on older macOS doesn’t store Secure cookies from http://localhost. A deployment served over HTTPS with an http fallback URL sets BETTER_AUTH_BASE_URL_PROTOCOL="https", as the deploy guides do for previews.
5. Run the app
Open http://localhost:3000, click Sign in in the header, and create an account with an email and a password. The default auth.constants.ts turns on password sign-in only, and it works without any email setup.
What the install adds
Routes
| Route | File | Item |
|---|---|---|
/signin | src/app/signin/page.tsx | auth/routes |
/reset-password | src/app/reset-password/page.tsx | auth/routes |
/api/auth/* | src/app/api/auth/[...all]/route.ts | auth/routes |
/api/organization/accept-invitation/[invitationId] | src/app/api/organization/accept-invitation/[invitationId]/page.tsx | organizations/routes |
/active-organization/* | src/app/active-organization/[[...all]]/page.tsx | organizations/routes |
/organization/create | src/app/organization/create/page.tsx | organizations/routes |
/device | src/app/device/page.tsx | device-authorization/routes |
/admin | src/app/admin/page.tsx | admin/routes |
The components live in src/components/<area>/ui/, for example src/components/users/ui/UserButton.tsx, and the Convex functions in src/convex/.
Route protection
src/proxy.ts sends every request without a Better Auth session cookie to /signin?redirectTo=<path>. After signing in, the user lands back on that path. These paths are public:
Add your own public pages to that list. Invitation links and the device page aren’t public, so a signed-out visitor signs in first and then continues. The proxy only checks that the cookie exists, so keep authorization checks in your Convex functions.
Next steps
- Configure features: the
AUTH_CONSTANTSflags for email, social providers, organizations, API keys, device authorization and the admin dashboard. - Components: drop
<UserButton />or<OrganizationSwitcher />into your own layout. - Deploy to Vercel.