mcpField
N
neon-auth-nextjs

neon-auth-nextjs

github.com/neondatabase開発1956 回インストール2 日前に更新
リポジトリ

neon-auth-nextjs について

neon-auth-nextjsは、データベースを使わずにNext.js App Routerへ@neondatabase/authを導入する開発者向けセットアップスキルです。サーバー・クライアント設定、ルート保護、認証UIに加え、インポート方法とセッション更新時の注意点を扱います。

主な機能

  1. 01サーバーとクライアントの認証設定
  2. 02ルートハンドラーとミドルウェア
  3. 03認証UIコンポーネント
  4. 04Server Componentsでのセッション処理
  5. 05CSSとテーマの設定

活用シーン

  1. 01App RouterアプリへのNeon Auth導入
  2. 02ダッシュボードとアカウントのルート保護
  3. 03サインインとアカウントページの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/neondatabase/neon-js --skill neon-auth-nextjs

インストール後、Claude Code で/neon-auth-nextjsを入力して読み込まれたか確認します。

詳細

Next.js 向け Neon Auth

開発者が Next.js App Router アプリケーションに @neondatabase/auth をセットアップできるよう支援します(認証のみ、データベースは不要です)。

使用するタイミング

次の場合にこのスキルを使用します:

  • Next.js(App Router)で Neon Auth をセットアップするとき
  • ユーザーが Neon Auth とあわせて「next.js」、「next」、「app router」に言及したとき
  • 認証のみのセットアップ(データベースが不要な場合)

重要なルール

  1. サーバーとクライアントのインポート:正しいインポートパスを使用してください
  2. 'use client' ディレクティブ:フックを使用するクライアントコンポーネントでは必須です
  3. CSS のインポート:/ui/css または /ui/tailwind のどちらか一方を選び、両方は使用しないでください
  4. onSessionChange:サーバーコンポーネントを更新するため、必ず router.refresh() を呼び出してください

重要なインポート

用途インポート元
統合サーバー(createNeonAuth)@neondatabase/auth/next/server
クライアント認証@neondatabase/auth/next
UI コンポーネント@neondatabase/auth/react/ui
ビューパス(静的パラメーター)@neondatabase/auth/react/ui/server

注:@neondatabase/auth/next/server の createNeonAuth() を使って、次の機能を提供する統合 auth インスタンスを取得します:

  • .handler() - API ルートハンドラー
  • .middleware() - ルート保護ミドルウェア
  • Better Auth のすべてのサーバーメソッド(.signIn、.signUp、.getSession など)

セットアップ

1. インストール
npm install @neondatabase/auth
2. 環境変数(.env.local)
NEON_AUTH_BASE_URL=https://your-auth.neon.tech
NEON_AUTH_COOKIE_SECRET=your-secret-at-least-32-characters-long

重要:本番環境用に、安全なシークレット(32 文字以上)を生成してください:

openssl rand -base64 32
3. サーバーのセットアップ(lib/auth/server.ts)

ハンドラー、ミドルウェア、サーバーメソッドを提供する auth インスタンスを作成します:

import { createNeonAuth } from '@neondatabase/auth/next/server';

export const auth = createNeonAuth({
  baseUrl: process.env.NEON_AUTH_BASE_URL!,
  cookies: {
    secret: process.env.NEON_AUTH_COOKIE_SECRET!,
    sessionDataTtl: 300,          // Optional: session data cache TTL in seconds (default: 300 = 5 min)
    domain: '.example.com',       // Optional: for cross-subdomain cookies
  },
});
4. API ルート(app/api/auth/[...path]/route.ts)
import { auth } from '@/lib/auth/server';

export const { GET, POST } = auth.handler();
5. ミドルウェア(middleware.ts)
import { auth } from '@/lib/auth/server';

export default auth.middleware({
  loginUrl: '/auth/sign-in',
});

export const config = {
  matcher: ['/dashboard/:path*', '/account/:path*'],
};
6. クライアント(lib/auth/client.ts)
'use client';
import { createAuthClient } from '@neondatabase/auth/next';

export const authClient = createAuthClient();
7. プロバイダー(app/providers.tsx)
'use client';
import { NeonAuthUIProvider } from '@neondatabase/auth/react/ui';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { authClient } from '@/lib/auth/client';

export function Providers({ children }: { children: React.ReactNode }) {
  const router = useRouter();

  return (
    <NeonAuthUIProvider
      authClient={authClient}
      navigate={router.push}
      replace={router.replace}
      onSessionChange={() => router.refresh()}
      redirectTo="/dashboard"
      Link={({href, children}) => <Link to={href}>{children}</Link>}
    >
      {children}
    </NeonAuthUIProvider>
  );
}
8. レイアウト(app/layout.tsx)
import { Providers } from './providers';
import '@neondatabase/auth/ui/css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
9. 認証ページ(app/auth/[path]/page.tsx)
import { AuthView } from '@neondatabase/auth/react/ui';
import { authViewPaths } from '@neondatabase/auth/react/ui/server';

export function generateStaticParams() {
  return Object.values(authViewPaths).map((path) => ({ path }));
}

export default async function AuthPage({ params }: { params: Promise<{ path: string }> }) {
  const { path } = await params;
  return <AuthView pathname={path} />;
}

CSS とスタイル設定

インポートオプション

Tailwind なし(ビルド済み CSS バンドル:約 47KB):

// app/layout.tsx
import '@neondatabase/auth/ui/css';

Tailwind CSS v4 を使用(app/globals.css):

@import 'tailwindcss';
@import '@neondatabase/auth/ui/tailwind';

重要:両方をインポートしないでください。スタイルが重複します。

ダークモード

プロバイダーには next-themes が含まれています。defaultTheme プロパティで制御します:

<NeonAuthUIProvider
  defaultTheme="system" // 'light' | 'dark' | 'system'
  // ...
>
カスタムテーマ設定

globals.css で CSS 変数を上書きします:

:root {
  --primary: hsl(221.2 83.2% 53.3%);
  --primary-foreground: hsl(210 40% 98%);
  --background: hsl(0 0% 100%);
  --foreground: hsl(222.2 84% 4.9%);
  --card: hsl(0 0% 100%);
  --card-foreground: hsl(222.2 84% 4.9%);
  --border: hsl(214.3 31.8% 91.4%);
  --input: hsl(214.3 31.8% 91.4%);
  --ring: hsl(221.2 83.2% 53.3%);
  --radius: 0.5rem;
}

.dark {
  --background: hsl(222.2 84% 4.9%);
  --foreground: hsl(210 40% 98%);
  /* ... dark mode overrides */
}

NeonAuthUIProvider のプロパティ

設定オプションの全一覧:

<NeonAuthUIProvider
  // Required
  authClient={authClient}

  // Navigation (Next.js specific)
  navigate={router.push}        // router.push for navigation
  replace={router.replace}      // router.replace for redirects
  onSessionChange={() => router.refresh()} // Refresh Server Components!
  redirectTo="/dashboard"       // Where to redirect after auth
  Link={({href, children}) => <Link to={href}>{children}</Link>}                   // Next.js Link component

  // Social/OAuth Providers
  social={{
    providers: ['google'],
  }}

  // Feature Flags
  emailOTP={true}               // Enable email OTP sign-in
  emailVerification={true}      // Require email verification
  magicLink={false}             // Magic link (disabled by default)
  multiSession={false}          // Multiple sessions (disabled)

  // Credentials Configuration
  credentials={{
    forgotPassword: true,       // Show forgot password link
  }}

  // Sign Up Fields
  signUp={{
    fields: ['name'],           // Additional fields: 'name', 'username', etc.
  }}

  // Account Settings Fields
  account={{
    fields: ['image', 'name', 'company', 'age', 'newsletter'],
  }}

  // Organization Features
  organization={{}}             // Enable org features

  // Dark Mode
  defaultTheme="system"         // 'light' | 'dark' | 'system'

  // Custom Labels
  localization={{
    SIGN_IN: 'Welcome Back',
    SIGN_UP: 'Create Account',
    FORGOT_PASSWORD: 'Forgot Password?',
    OR_CONTINUE_WITH: 'or continue with',
  }}
>
  {children}
</NeonAuthUIProvider>

サーバーコンポーネント(RSC)

サーバーコンポーネントでセッションを取得する
// NO 'use client' - this is a Server Component
import { auth } from '@/lib/auth/server';

// Server components using `auth` methods must be rendered dynamically
export const dynamic = 'force-dynamic'

export async function Profile() {
  const { data: session } = await auth.getSession();

  if (!session?.user) return <div>Not signed in</div>;

  return (
    <div>
      <p>Hello, {session.user.name}</p>
      <p>Email: {session.user.email}</p>
    </div>
  );
}
認証付きルートハンドラー
// app/api/user/route.ts
import { auth } from '@/lib/auth/server';
import { NextResponse } from 'next/server';

export async function GET() {
  const { data: session } = await auth.getSession();

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

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

サーバーアクション

サーバーアクションでは、lib/auth/server.ts の同じ auth インスタンスを使用します:

サインインアクション
// app/actions/auth.ts
'use server';
import { auth } from '@/lib/auth/server';
import { redirect } from 'next/navigation';

export async function signIn(formData: FormData) {
  const { error } = await auth.signIn.email({
    email: formData.get('email') as string,
    password: formData.get('password') as string,
  });

  if (error) {
    return { error: error.message };
  }

  redirect('/dashboard');
}

export async function signUp(formData: FormData) {
  const { error } = await auth.signUp.email({
    email: formData.get('email') as string,
    password: formData.get('password') as string,
    name: formData.get('name') as string,
  });

  if (error) {
    return { error: error.message };
  }

  redirect('/dashboard');
}

export async function signOut() {
  await auth.signOut();
  redirect('/');
}
利用可能なサーバーメソッド

createNeonAuth() の auth インスタンスは、Better Auth のすべてのサーバーメソッドを提供します:

// Authentication
auth.signIn.email({ email, password })
auth.signUp.email({ email, password, name })
auth.signOut()
auth.getSession()

// User Management
auth.updateUser({ name, image })

// Organizations
auth.organization.create({ name, slug })
auth.organization.list()

// Admin (if enabled)
auth.admin.listUsers()
auth.admin.banUser({ userId })

クライアントコンポーネント

セッションフック
'use client';
import { authClient } from '@/lib/auth/client';

export function Dashboard() {
  const { data: session, isPending, error } = authClient.useSession();

  if (isPending) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!session) return <div>Not signed in</div>;

  return <div>Hello, {session.user.name}</div>;
}
クライアント側の認証メソッド
'use client';
import { authClient } from '@/lib/auth/client';

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

// Sign up
await authClient.signUp.email({ email, password, name });

// OAuth
await authClient.signIn.social({
  provider: 'google',
  callbackURL: '/dashboard',
});

// Sign out
await authClient.signOut();

// Get session
const session = await authClient.getSession();

UI コンポーネント

AuthView - メインの認証インターフェース
import { AuthView } from '@neondatabase/auth/react/ui';

// Handles: sign-in, sign-up, forgot-password, reset-password, callback, sign-out
<AuthView pathname={path} />
条件付きレンダリング
import {
  SignedIn,
  SignedOut,
  AuthLoading,
  RedirectToSignIn,
} from '@neondatabase/auth/react/ui';

function MyPage() {
  return (
    <>
      <AuthLoading>
        <LoadingSpinner />
      </AuthLoading>

      <SignedIn>
        <Dashboard />
      </SignedIn>

      <SignedOut>
        <LandingPage />
      </SignedOut>

      {/* Auto-redirect if not signed in */}
      <RedirectToSignIn />
    </>
  );
}
UserButton
import { UserButton } from '@neondatabase/auth/react/ui';

function Header() {
  return (
    <header>
      <nav>...</nav>
      <UserButton />
    </header>
  );
}
アカウント管理
import {
  AccountSettingsCards,
  SecuritySettingsCards,
  SessionsCard,
  ChangePasswordCard,
  ChangeEmailCard,
  DeleteAccountCard,
  ProvidersCard,
} from '@neondatabase/auth/react/ui';
組織コンポーネント
import {
  OrganizationSwitcher,
  OrganizationSettingsCards,
  OrganizationMembersCard,
  AcceptInvitationCard,
} from '@neondatabase/auth/react/ui';

ソーシャル/OAuth プロバイダー

設定
<NeonAuthUIProvider
  social={{
    providers: ['google'],
  }}
>
プログラムによる OAuth
// Client-side
await authClient.signIn.social({
  provider: 'google',
  callbackURL: '/dashboard',
});
対応プロバイダー

google、github、twitter、discord、apple、microsoft、facebook、linkedin、spotify、twitch、gitlab、bitbucket


ミドルウェアの設定

基本的な保護ルート
import { auth } from '@/lib/auth/server';

export default auth.middleware({
  loginUrl: '/auth/sign-in',
});

export const config = {
  matcher: ['/dashboard/:path*', '/account/:path*', '/settings/:path*'],
};
カスタムロジック
import { auth } from '@/lib/auth/server';
import { NextResponse } from 'next/server';

export default auth.middleware({
  loginUrl: '/auth/sign-in',
});

アカウントページのセットアップ

アカウントレイアウト(app/account/[path]/page.tsx)
import {
  SignedIn,
  RedirectToSignIn,
  AccountSettingsCards,
  SecuritySettingsCards,
  SessionsCard,
  ChangePasswordCard,
} from '@neondatabase/auth/react/ui';

export default async function AccountPage({ params }: { params: Promise<{ path: string }> }) {
  const { path = 'settings' } = await params;

  return (
    <>
      <RedirectToSignIn />
      <SignedIn>
        {path === 'settings' && <AccountSettingsCards />}
        {path === 'security' && (
          <>
            <ChangePasswordCard />
            <SecuritySettingsCards />
          </>
        )}
        {path === 'sessions' && <SessionsCard />}
      </SignedIn>
    </>
  );
}

高度な機能

匿名アクセス

未認証ユーザー向けに、RLS ベースのデータアクセスを有効にします:

// lib/auth/client.ts
'use client';
import { createAuthClient } from '@neondatabase/auth/next';

export const authClient = createAuthClient({
  allowAnonymous: true,
});
JWT トークンの取得
const token = await authClient.getJWTToken();

// Use in API calls
const response = await fetch('/api/data', {
  headers: { Authorization: `Bearer ${token}` },
});
タブ間の同期

BroadcastChannel により自動で同期されます。1 つのタブでサインアウトすると、すべてのタブでサインアウトします。

サーバーコンポーネントでのセッション更新

Next.js では onSessionChange コールバックが重要です:

<NeonAuthUIProvider
  onSessionChange={() => router.refresh()} // Refreshes Server Components!
  // ...
>

これがないと、サインイン/サインアウト後にサーバーコンポーネントが更新されません。


エラー処理

サーバーアクション
'use server';

export async function signIn(formData: FormData) {
  const { error } = await authServer.signIn.email({
    email: formData.get('email') as string,
    password: formData.get('password') as string,
  });

  if (error) {
    // Return error to client
    return { error: error.message };
  }

  redirect('/dashboard');
}
クライアントコンポーネント
'use client';

const { error } = await authClient.signIn.email({ email, password });

if (error) {
  toast.error(error.message);
}
よくあるエラー
エラー原因
Invalid credentialsメールアドレスまたはパスワードが間違っています
User already existsそのメールアドレスはすでに登録されています
Email not verifiedメールアドレスの確認が必要です
Session not foundセッションの有効期限が切れているか、無効です

FAQ/トラブルシューティング

サインイン後にサーバーコンポーネントが更新されない場合

プロバイダーに onSessionChange={() => router.refresh()} が設定されていることを確認してください。

<NeonAuthUIProvider
  onSessionChange={() => router.refresh()}
  // ...
>
匿名アクセスが機能しない場合

データベースで anonymous ロールに権限を付与してください。

GRANT SELECT ON public.posts TO anonymous;
GRANT SELECT ON public.products TO anonymous;

また、RLS ポリシーを設定してください。

CREATE POLICY "Anyone can read published posts"
  ON public.posts FOR SELECT
  USING (published = true);
ミドルウェアでルートが保護されない場合

matcher の設定を確認してください。

export const config = {
  matcher: [
    '/dashboard/:path*',
    '/account/:path*',
    // Add your protected routes here
  ],
};
OAuth コールバックでエラーが発生する場合

API ルートが app/api/auth/[...path]/route.ts に正しく設定されていることを確認してください。

import { auth } from '@/lib/auth/server';
export const { GET, POST } = auth.handler();
セッションが保持されない場合
  1. Cookie が有効になっていることを確認してください
  2. .env.local で NEON_AUTH_BASE_URL が正しいことを確認してください
  3. NEON_AUTH_COOKIE_SECRET が設定され、32 文字以上であることを確認してください
  4. Cookie がブロックされるシークレットモードを使用していないことを確認してください
セッションデータのキャッシュが機能しない場合
  1. NEON_AUTH_COOKIE_SECRET が 32 文字以上であることを確認してください
  2. cookies.secret が createNeonAuth() に渡されていることを確認してください
  3. 必要に応じて、cookies.sessionDataTtl を設定してください(デフォルト:300 秒)

パフォーマンスに関する注意事項

  • セッションデータのキャッシュ:TTL を設定可能な、JWT 署名付きの session_data Cookie(デフォルト:5 分)
    • 秒単位で cookies.sessionDataTtl を設定します
    • ミリ秒未満のセッション検索(<1ms)が可能になります
    • キャッシュミス時は上流の /get-session に自動的にフォールバックします
  • リクエストの重複排除:並行する呼び出しで単一のネットワークリクエストを共有します(コールドスタートが 10 倍高速になります)
  • サーバーコンポーネント:JavaScript を使用せずにセッションへアクセスするには、auth.getSession() を使用してください
  • タブ間同期:BroadcastChannel 経由で <50ms
  • Cookie ドメイン:サブドメイン間で Cookie を共有するには、必要に応じて cookies.domain を設定します

コメント