Next14でSupabase Login
簡単だけど難しい
Next14とSupabaseを使ったことがある方を対象としております。
公式ドキュメントを読んで機能を実装しようとしたが、TypeScriptだとデータ型の定義が必要だったり、AppRouterだと新しくて情報があまりなくてどれがいいのかと悩みました💦
最近増えてきた気もしますが、Auth.jsなるものを使ったり他のパッケージでログインするものが多かったりする。あまりパッケージに頼りたくない💦
今回も使ってはいるが...
対象者
- Supabaseで認証機能を使ってみたい
- 少ないコードで仕組みを理解したい
- Next14を学習してる人
AIに頼って機能実装しようとするものの中々うまくいかない。使わない方がいいのか...
メモ書き程度なので参考程度にみてください🙏
やること
- プロジェクトを作成したら必要なパッケージを追加する
- Supabaseのメールアドレス&パスワードログインの設定をしておく
.env.local
にURL, anon keyの設定をする。
NEXT_PUBLIC_SUPABASE_URL=https://*************.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6*****************
パッケージを追加する。
npm install @supabase/auth-helpers-nextjs @supabase/supabase-js
でもこっちが必要みたいだ
npm i @supabase/ssr
認証の設定
設定ファイルや必要なコードは全てapp/
配下に作成します。
app/lib/supabase-client.ts
を作成。
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
export const createClient = () => createClientComponentClient()
ログイン後のページ
app/dashboard/page.tsx
を作成する。User
型のデータがないとエラーが出たので追加した。
'use client'
import { useEffect, useState } from 'react'
import { createClient } from '../lib/supabase-client'
import { useRouter } from 'next/navigation'
import { User } from '@supabase/supabase-js'
export default function Dashboard() {
const [user, setUser] = useState<User | null>(null)
const router = useRouter()
const supabase = createClient()
useEffect(() => {
const getUser = async () => {
const { data: { user } } = await supabase.auth.getUser()
if (user) {
setUser(user)
} else {
router.push('/login')
}
}
getUser()
}, [supabase, router])
const handleLogout = async () => {
await supabase.auth.signOut()
router.push('/login')
}
if (!user) return <div>Loading...</div>
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {user.email}!</p>
<button onClick={handleLogout}>ログアウトする</button>
</div>
)
}
新規作成・ログインのページを作成
こちらはlogin/
のページとして作成していましたが、コンポーネントとして使って最初のページに表示してます。説明のページを見た後にログインページに来るのが望ましいかも。
今回は練習ということでハードコーディングになってしまいましたが、ユーザーの新規作成・ログインを体験できると思います。
'use client'
import { useState } from 'react'
import { createClient } from '../lib/supabase-client'
import { useRouter } from 'next/navigation'
export default function Login() {
const [loading, setLoading] = useState(false)
const router = useRouter()
const supabase = createClient()
const handleLogin = async () => {
try {
setLoading(true)
const { error } = await supabase.auth.signInWithPassword({
email: 'hoge@co.jp',
password: '1234qq',
})
if (error) throw error
router.push('/dashboard')
} catch (error) {
console.error('Error logging in:', error)
alert('Failed to log in')
} finally {
setLoading(false)
}
}
const handleSignUp = async () => {
try {
setLoading(true)
const { error } = await supabase.auth.signUp({
email: 'hoge@co.jp',
password: '1234qq',
})
if (error) throw error
router.push('/dashboard')
} catch (error) {
console.error('Error logging in:', error)
alert('Failed to log in')
} finally {
setLoading(false)
}
}
return (
<div>
<h1>Login</h1>
<button onClick={handleSignUp} disabled={loading}>
{loading ? 'Loading...' : '新規登録する'}
</button><br/>
<button onClick={handleLogin} disabled={loading}>
{loading ? 'Loading...' : 'ログインする'}
</button>
</div>
)
}
こちらが最初に表示されるpage.tsx
です。こちらでLoginコンポーネントを読み込みましょう。
import Login from "./login/page";
export default function Home() {
return (
<div>
<Login />
</div>
);
}
動作確認してみましょう。
新規作成、ログイン前。
ログインしたページ
Supabaseのコンソールを確認するとユーザーのアカウントが登録されておりました。メソッドは実行できている。
まとめ
Auth.jsなるものを使わずに認証機能を実装してみました。この変は難しいですね。簡単には作れないですね💦
Prismaを使ったデータベースとのやり取りはなんとなくわかってきたが、認証はまだ理解できていない部分が多いですね。
色々なパターンがあるからどれが最適なのか...
先ほど追加したパッケージだともう非推奨みたい💦
公式によると...
Supabase Auth with the Next.js App Router
The auth-helpers package has been replaced with the @supabase/ssr package. We recommend setting up Auth for your Next.js app with @supabase/ssr instead. See the Next.js Server-Side Auth guide to learn how.
Next.jsアプリルータを使ったSupabase Auth
auth-helpersパッケージは@supabase/ssrパッケージに置き換えられました。Next.jsアプリのAuthは、@supabase/ssrで設定することをおすすめします。その方法については、Next.js Server-Side Auth ガイドをご覧ください。
これか???
package.jsonの内容は今はこんな感じになっております。
{
"name": "post-writer-webapp",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@prisma/client": "^5.21.1",
"@supabase/auth-helpers-nextjs": "^0.10.0",
"@supabase/ssr": "^0.5.1",
"@supabase/supabase-js": "^2.45.6",
"next": "14.2.15",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20.16.13",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.15",
"postcss": "^8",
"prisma": "^5.21.1",
"tailwindcss": "^3.4.1",
"ts-node": "^10.9.2",
"typescript": "^5"
},
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}
}
app/
の外に配置したこのコードを使わなくても動くようだ...
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export function createClient() {
const cookieStore = 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(e) {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
console.error(`supabase connet error: ${e}`)
}
},
},
}
)
}
Discussion