Next.jsとSupabaseの導入からGoogleログインまで
Next.jsの環境構築
以下のコマンドでNext.jsの環境構築をする。
途中いろいろ聞かれるが、こだわりがなければデフォルトの選択肢で。
npx create-next-app@latest
環境構築が終わったら以下のコマンドで画面を確認
npm run dev
必要なライブラリをインストール
@supabase/ssr と @supabase/supabase-js をインストールする
npm install @supabase/ssr @supabase/supabase-js
Supabaseの設定
Supabaseでプロジェクトを作り、Project APIに書かれているProject URLとAPI Keyを取得する
Next.jsのプロジェクトルートに .env を作成し、Project URLとAPI Keyを書く
NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=xxxxxxxxxxxxxxxxx
GCP
プロジェクト作成
Google Cloud Platformにアクセスし、プロジェクトを作成する。
OAuth同意画面
「APIとサービス」 > 「OAuth同意画面」
必要な情報を入力。
「対象」は今回は「外部」を選択。
他の情報も入力したら登録する

OAuth クライアント ID の作成
必要な情報を入力。
「承認済みのリダイレクト URI」はsupabaseから取得する
「Authentication」> 「Sign in / Providers」> 「Google」を開き「Callback URL (for OAuth)」をコピーし、GCPの「承認済みのリダイレクト URI」に入力しOAuth クライアントを作成
Supabaseに情報を保存
OAuth クライアントを作成すると、「クライアント ID」と「クライアント シークレット」が発行されるので、それらをSupabaseの「Authentication」> 「Sign in / Providers」> 「Google」「Client IDs」と「Client Secret (for OAuth)」にそれぞれ保存する
Supabaseクライアント
以下のファイルを作成しコードを書く
import { type NextRequest } from "next/server";
import { updateSession } from "@/utils/supabase/middleware";
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: [
/*
* Match all request paths except:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - images - .svg, .png, .jpg, .jpeg, .gif, .webp
* Feel free to modify this pattern to include more paths.
*/
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};
import { createBrowserClient } from "@supabase/ssr";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const createClient = () =>
createBrowserClient(supabaseUrl, supabaseAnonKey);
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export const createClient = async () => {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) => {
cookieStore.set(name, value, options);
});
} catch (error) {
console.log(error);
// The `set` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
);
};
import { createServerClient } from "@supabase/ssr";
import { type NextRequest, NextResponse } from "next/server";
export const updateSession = async (request: NextRequest) => {
// This `try/catch` block is only here for the interactive tutorial.
// Feel free to remove once you have Supabase connected.
try {
// Create an unmodified response
let response = NextResponse.next({
request: {
headers: request.headers,
},
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value)
);
response = NextResponse.next({
request,
});
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
);
},
},
}
);
// This will refresh session if expired - required for Server Components
// https://supabase.com/docs/guides/auth/server-side/nextjs
const user = await supabase.auth.getUser();
// protected routes
if (request.nextUrl.pathname.startsWith("/protected") && user.error) {
return NextResponse.redirect(new URL("/sign-in", request.url));
}
if (request.nextUrl.pathname === "/" && !user.error) {
return NextResponse.redirect(new URL("/dashboard", request.url));
}
return response;
// eslint-disable-next-line
} catch (e) {
// If you are here, a Supabase client could not be created!
// This is likely because you have not set up environment variables.
// Check out http://localhost:3000 for Next Steps.
return NextResponse.next({
request: {
headers: request.headers,
},
});
}
};
ログイン機能の実装
以下の様に修正する
"use client";
import { createClient } from "@/utils/supabaseClient";
export default function Home() {
const handleGoogleSignIn = async () => {
try {
const supabase = await createClient();
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${location.origin}/auth/callback`,
},
});
if (error) {
console.error("Google auth error:", error.message);
}
} catch (error) {
console.error("Google sign in error:", error);
}
};
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<h1>welcome</h1>
<button onClick={handleGoogleSignIn}>google login</button>
</div>
);
}
ログイン後のページとしてdashboardページを作る
import { createClient } from "@/utils/supabaseServer";
const Dashboard = async () => {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
return (
<div>
ログイン後<div>{user?.email}</div>
</div>
);
};
export default Dashboard;
コールバック関数を作る
import { NextResponse } from "next/server";
import { createClient } from "@/utils/supabaseServer";
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get("code");
const next = searchParams.get("next") ?? "/dashboard";
if (code) {
const supabase = await createClient();
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (!error) {
return NextResponse.redirect(`${origin}${next}`);
}
}
return NextResponse.redirect(`${origin}/auth/auth-code-error`);
}
ログイン
簡素な画面ですがログインボタンを押すと、googleログインの画面に切り替わりアカウント登録とログインができる
supabaseのユーザーページへ行くと、ログインしたユーザーの情報が追加されている。
また、一度ログインすると、indexページへ行っても /dashboard ページへリダイレクト