Honestly, I just wanted a nice rainbow gradient banner at the top.
Fullstack Authentication Snippets/Cloudflare better auth/Client

Next.js Client

Connect your Next.js app to the Cloudflare Workers authentication API

Prerequisites

This guide assumes you have deployed the Hono server to Cloudflare Workers.

Installation

npx shadcn@latest add https://snippets.thedevdavid.com/r/hono-cloudflare-better-auth-client-nextjs.json

Configuration

1. Environment Variables

.env.local
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
# Or for development
NEXT_PUBLIC_API_URL=http://localhost:8787

2. Middleware Setup

The middleware protects routes and refreshes sessions:

middleware.ts
export default authMiddleware({
  protectedRoutes: ["/dashboard", "/settings"],
  redirectTo: "/login",
});

Usage

Client Components

components/auth-button.tsx
"use client";

import { useSession, signOut } from "@/lib/auth-client";
import { useRouter } from "next/navigation";

export function AuthButton() {
  const { data: session, loading } = useSession();
  const router = useRouter();

  if (loading) return <div>Loading...</div>;

  if (!session) {
    return <button onClick={() => router.push("/login")}>Sign In</button>;
  }

  return (
    <div>
      <span>{session.user.email}</span>
      <button onClick={() => signOut()}>Sign Out</button>
    </div>
  );
}

Server Components

app/dashboard/page.tsx
import { getBetterAuthSession } from "@/lib/get-session";
import { redirect } from "next/navigation";

export default async function Dashboard() {
  const session = await getBetterAuthSession();

  if (!session) {
    redirect("/login");
  }

  return <h1>Welcome {session.user.email}!</h1>;
}

Authentication Forms

app/login/page.tsx
"use client";

import { signIn } from "@/lib/auth-client";
import { useRouter } from "next/navigation";
import { useState } from "react";

export default function LoginPage() {
  const [error, setError] = useState("");
  const router = useRouter();

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setError("");

    const formData = new FormData(e.currentTarget);

    try {
      await signIn.email({
        email: formData.get("email") as string,
        password: formData.get("password") as string,
      });
      router.push("/dashboard");
    } catch (err) {
      setError("Invalid credentials");
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      {error && <p>{error}</p>}
      <button type="submit">Sign In</button>
    </form>
  );
}

API Routes

Protected API routes example:

app/api/user/route.ts
import { getBetterAuthSession } from "@/lib/get-session";
import { NextResponse } from "next/server";

export async function GET() {
  const session = await getBetterAuthSession();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  return NextResponse.json({ user: session.user });
}

Common Patterns

Protected Pages

app/settings/page.tsx
import { getBetterAuthSession } from "@/lib/get-session";
import { redirect } from "next/navigation";

export default async function Settings() {
  const session = await getBetterAuthSession();
  if (!session) redirect("/login");

  // Your protected content
  return <div>Settings for {session.user.email}</div>;
}

Custom Hook

hooks/use-auth.ts
import { useSession, signOut } from "@/lib/auth-client";
import { useRouter } from "next/navigation";

export function useAuth() {
  const session = useSession();
  const router = useRouter();

  const logout = async () => {
    await signOut();
    router.push("/");
  };

  return {
    user: session.data?.user,
    loading: session.loading,
    logout,
  };
}

Resources

How is this guide?

Last updated: 6/2/2025