惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Schneier on Security
N
News and Events Feed by Topic
量子位
S
Secure Thoughts
V2EX - 技术
V2EX - 技术
Hugging Face - Blog
Hugging Face - Blog
S
Security Affairs
J
Java Code Geeks
Schneier on Security
Schneier on Security
Google Online Security Blog
Google Online Security Blog
TaoSecurity Blog
TaoSecurity Blog
小众软件
小众软件
S
SegmentFault 最新的问题
www.infosecurity-magazine.com
www.infosecurity-magazine.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Security Archives - TechRepublic
Security Archives - TechRepublic
P
Privacy International News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
博客园 - 聂微东
T
Tor Project blog
博客园 - Franky
C
CERT Recently Published Vulnerability Notes
Cyberwarzone
Cyberwarzone
罗磊的独立博客
博客园_首页
The Cloudflare Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
大猫的无限游戏
大猫的无限游戏
Forbes - Security
Forbes - Security
V
Vulnerabilities – Threatpost
Security Latest
Security Latest
腾讯CDC
Simon Willison's Weblog
Simon Willison's Weblog
S
Securelist
博客园 - 【当耐特】
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
博客园 - 司徒正美
AWS News Blog
AWS News Blog
WordPress大学
WordPress大学
Jina AI
Jina AI
G
GRAHAM CLULEY
V
V2EX
L
LINUX DO - 最新话题
H
Heimdal Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Migrating from NextAuth to Better Auth in Next.js (and What the Boring Parts Actually Are)
Ship Kit · 2026-06-22 · via DEV Community

If you've shipped a Next.js app on NextAuth (now Auth.js), you know it works. The reason people move to Better Auth usually isn't that NextAuth is bad — it's that Better Auth gives you typed, first-class access to sessions, organizations, and database-backed concepts without bolting adapters and callbacks together by hand. The session calls are typed end to end, the schema is generated for you, and features like multi-tenancy or admin tooling are first-party rather than community glue.

This post is the honest version of that migration: the mechanical parts you can rewrite almost blindly, and the parts you still have to do by hand. I'll show real before/after code for each.

Coming from Clerk instead? See the companion guide: Moving off Clerk to Better Auth.

The mental model: NextAuth is a library, so a lot of the rewrite is mechanical

Much of a NextAuth integration is call sitesgetServerSession here, useSession there, a signIn button. Those follow patterns, which means they're transformable. The part that isn't mechanical is your configuration: providers, the database adapter, and callbacks. That's the logic only you understand, and no tool should silently rewrite it.

Keep that split in your head for the whole migration:

  • Call sites → mechanical, fast, low-risk.
  • Config + schema + data → manual, deliberate, where the real work lives.

Step 1: Imports

NextAuth spreads its surface across several entry points. Better Auth centralizes server access in your auth instance and client access in an authClient.

// before
import { getServerSession } from "next-auth";
import { useSession, signIn, signOut } from "next-auth/react";

// after
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { authClient } from "@/lib/auth-client";

The next-auth and next-auth/react imports collapse into three: your server auth instance, next/headers (you'll need it for session reads), and the authClient.

Step 2: Server-side session reads

This is the most common change in a real codebase. NextAuth's getServerSession(authOptions) becomes a call on the Better Auth instance that takes the request headers explicitly.

// before
const session = await getServerSession(authOptions);

// after
const session = await auth.api.getSession({
  headers: await headers(),
});

Passing headers() is not boilerplate you can skip — Better Auth reads the session cookie from those headers, so every server-side session read needs them.

Step 3: Client-side session reads

// before
const { data: session } = useSession();

// after
const { data: session } = authClient.useSession();

Same shape, different origin — the hook now comes off authClient instead of a top-level next-auth/react import.

Step 4: signIn / signOut (watch the arguments)

This is the first place where a find-and-replace is not enough, and it's worth slowing down. The import source changes cleanly:

// before
import { signIn, signOut } from "next-auth/react";
signIn("credentials", { email, password });
signIn("github");

// after
import { authClient } from "@/lib/auth-client";
// the call shape is different — see below

Better Auth doesn't use a single signIn(provider, options) function. Credentials and social are distinct methods with their own argument shapes:

// email + password
await authClient.signIn.email({ email, password });

// social provider
await authClient.signIn.social({ provider: "github" });

The signIn/signOut reference can be rewritten automatically, but the arguments cannot("credentials", { ... }) and .email({ ... }) are genuinely different APIs. Expect to revisit every sign-in call by hand.

Step 5: Environment variable renames

The names change, and there are enough of them to be error-prone. Here's the full mapping:

NextAuth Better Auth
NEXTAUTH_SECRET BETTER_AUTH_SECRET
NEXTAUTH_URL BETTER_AUTH_URL
GITHUB_ID GITHUB_CLIENT_ID
GITHUB_SECRET GITHUB_CLIENT_SECRET
GOOGLE_ID GOOGLE_CLIENT_ID
GOOGLE_SECRET GOOGLE_CLIENT_SECRET

One thing to be precise about: there are two places these names live — in code (process.env.NEXTAUTH_SECRET) and in your .env file. Renaming the code references is mechanical. Renaming the actual .env entries is something you do yourself, because nothing should be reaching into your secrets file and editing it.

Step 6: The API route

NextAuth mounts its handler at [...nextauth]. Better Auth uses a catch-all [...all] route wired to its handler:

// before — app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import { authOptions } from "@/lib/auth";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

// after — app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);

This is a file move plus a rewrite, not an in-place edit — the directory name itself changes from [...nextauth] to [...all].

Step 7: Middleware → cookie check + server requireSession

NextAuth's withAuth middleware has no direct equivalent in Better Auth, and that's deliberate. The recommended pattern is two layers:

  1. A lightweight cookie check in middleware/proxy for cheap edge-level redirects.
  2. An authoritative server-side requireSession() in the protected route or layout, which actually validates the session.
// before — middleware.ts
export { default } from "next-auth/middleware";

// after — a cookie presence check at the edge, plus:
const session = await requireSession(); // authoritative, server-side

The edge check is fast but only checks for a cookie's presence; the server check is the one you trust. Don't collapse them into one — the whole point is that the cheap check doesn't have authority and the authoritative check isn't on the hot edge path.

The config, the schema, the data — what you still do by hand

Everything above is the boring, transformable part. Here's the part that isn't — and it's the part that actually decides whether the migration goes well.

The auth config itself. Your authOptions / NextAuth({}) body doesn't translate one-to-one. You port it deliberately:

  • providers: [...]socialProviders: { ... }
  • your adapter → the matching Better Auth adapter (e.g. drizzleAdapter)
  • callbacks: { ... }databaseHooks or plugin options
  • email/password, verification, and reset are explicit features you enable, not implicit defaults
// the shape you're porting *to* (illustrative)
export const auth = betterAuth({
  database: drizzleAdapter(db, { /* ... */ }),
  emailAndPassword: { enabled: true },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
});

The database schema. NextAuth's tables are not Better Auth's tables. You generate the Better Auth schema and migrate:

pnpm auth:generate   # produce the Better Auth schema
pnpm db:migrate      # apply it

Existing user rows. This is the awkward one. Better Auth won't move your users for you — you map columns from the old tables to the new ones yourself. Password hashes only carry over if the hashing scheme matches; if it doesn't, those users need a password reset. Plan a real data-migration task here, not a script you run once and hope.

A sane order to do this in

  1. git commit or stash first — you want a clean diff to review.
  2. Rewrite the call sites (imports, getServerSession, useSession, sign-in references).
  3. Port the config, generate the schema, run the migration.
  4. Fix the sign-in/sign-out arguments by hand.
  5. Move the API route to [...all].
  6. Rebuild middleware as cookie-check + requireSession.
  7. Rename .env entries.
  8. Verify: run your typecheck, then pnpm auth:generate && pnpm db:migrate, then pnpm dev and click through the actual flows.

One thing to expect: even though the call-site rewrites are the most repetitive change, most of your time will go into the config, schema, and data — that's normal, and that's where to be careful.

Disclosure: I build a starter that automates the mechanical part

I make Ship Kit, a commercial Better Auth starter for Next.js 16 + TypeScript. (It's an independent, unofficial project — not affiliated with or endorsed by Better Auth.) I sell it, so treat this section as the ad it is.

Those migration codemods are free and open sourceauthlayerdev/ship-kit-migrate (MIT), deterministic and non-AI. Clone the repo and run the NextAuth transform against your project, dry-run first:

# preview every change — writes nothing
node ./migrate/cli.mjs --transform nextauth --dry ./src
# apply it (rewrites files in place)
node ./migrate/cli.mjs --transform nextauth ./src

It's idempotent (running it twice is safe), but commit or stash first regardless.

What it does: rewrites the imports, turns getServerSession(authOptions) into auth.api.getSession({ headers: await headers() }), swaps useSession() for authClient.useSession(), repoints signIn/signOut references (leaving a // TODO(ship-kit): marker because the arguments differ), renames the in-code env references, and annotates your config block with a TODO.

What it explicitly does not do — and leaves marked so you can find the remainder with one grep — is the genuinely manual work:

grep -rn "TODO(ship-kit)" .   # the manual checklist the codemod left you

…that remainder being: porting the config body, the sign-in argument shapes, the DB schema, the user-row migration, the API route move, the .env file itself, and middleware. In other words, exactly the "by hand" section above. It's an assist for the mechanical part, not a one-click migrator — the parts that need judgment stay yours.

For full context: Ship Kit is one-time ($179 solo / $499 agency, lifetime updates, 14-day refund, crypto −5%), built by one developer in Berlin. It's brand new, so there are no buyer testimonials yet. More established starters like Makerkit (from $349) and supastarter (from €349) have more templates and maturity; Ship Kit's narrower distinctions are the in-product migration codemods (most rivals are docs-only) and crypto checkout. It also runs a nightly canary CI job that bumps Better Auth to the latest version and re-runs the test suite — that's a signal it tracks upstream, not a guarantee that any given upgrade will be painless.

If you'd rather not hand-write the repetitive call-site rewrites, the codemod handles that part. The config, schema, and data migration are still yours to do.


Disclosure: I'm the author of Ship Kit, a commercial product mentioned above, and I earn revenue from its sales.