06 6 logout
1-Profile.tsx
1-1-UI요소 추가
1-1-1-프로필 이미지가 없을 경우 사용자명 렌더링하기
- next-with-supabase\components\Profile.tsx
- Image 컴포넌트를 수정한다
1**...2**</Link>3 ) : (**4****<>5 {data?.image_url ? (6 <Image src={data.image_url || ''} alt={data.display_name || ''} width={50} height={50} className="rounded-full" />7 ) : (8 <div className="h-[50px] w-[50px] flex items-center justify-center">9 <h1>{data.email}</h1>10 </div>11 )}12</>****13...**- 테스트
- 이미지 데이터가 null이면 email 이 렌더링된다.

2-Logout
2-1-Logout버튼만들기
2-1-1-components\Profile.tsx
- createClient함수 임포트 handleLogout 작성
import { createClient } from "@/lib/supabase/client";... const handleLogout = () => { const supabase = createClient(); };- useQueryClient
1"use client";2import React from "react";3import { Button } from "./ui/button";4import Link from "next/link";5import useUser from "@/app/hook/useUser";6import Image from "next/image";7import { createClient } from "@/lib/supabase/client";8import { useQueryClient } from "@tanstack/react-query";9
10const Profile = () => {11 const { isFetching, data } = useUser();12 const queryClient = useQueryClient();13 if (isFetching) {14 return <></>;15 }16 const handleLogout = async () => {17 const supabase = createClient();18 queryClient.clear();19 await supabase.auth.signOut();20 };21 return (22 <div>23 {!data?.id ? (24 <Link href="/auth">25 <Button variant="outline">SignIn</Button>26 </Link>27 ) : (28 <>29 {data?.image_url ? (30 <div className="flex flex-col justify-center items-center">31 <Image src={data.image_url || ""} alt={data.display_name || ""} width={50} height={50} className="rounded-full" />32 {data.display_name}33 </div>34 ) : (35 <div className="h-[50px] w-[50px] flex items-center justify-center">36 <h1>{data.email}</h1>37 </div>38 )}39 </>40 // 1:3541 )}42 </div>43 );44};45export default Profile;- router
1import { useRouter } from "next/navigation";2const Profile = () => {3...4 const router = useRouter();5...6}7 const handleLogout = async () => {8 ...9 router.refresh();10}2-미들웨어 작성
정보
💡 미들웨어는 서버와 클라이언트 사이에서 로그인 정보를 안전하게 관리하는 중개자 역할을 한다.
🔹 클라이언트(사용자)가 페이지에 접근하면 → middleware.ts가 먼저 요청을 가로채서 로그인 상태를 확인
🔹 로그인되어 있으면 → 그냥 원래 가려던 페이지로 이동!
🔹 로그인 안 되어 있으면 → /login 페이지로 강제 이동!
🔹 세션 정보를 유지하면서 쿠키도 관리해서, 사용자가 로그아웃되거나 세션이 만료되면 다시 로그인하게 유도한다.
즉, “이 사용자가 로그인된 상태인지 확인하고, 필요한 경우 로그인 페이지로 보내는 보안 게이트” 같은 역할을 한다.
1.
1<div className="flex flex-col justify-center items-center">2 <Image src={data.image_url || ""} alt={data.display_name || ""} width={50} height={50} className="rounded-full** cursor-pointer" onClick={handleLogout}/**>3 {data.display_name}4 </div>- api docs 참조 https://supabase.com/docs/guides/auth/server-side/nextjs 4단계의 middleware Hook을 생성한다
- middleware.ts 파일생성
1import { type NextRequest } from 'next/server'2import { updateSession } from '@/utils/supabase/middleware'3
4export async function middleware(request: NextRequest) {5 return await updateSession(request)6}7
8export const config = {9 matcher: [10 /*11 * Match all request paths except for the ones starting with:12 * - _next/static (static files)13 * - _next/image (image optimization files)14 * - favicon.ico (favicon file)15 * Feel free to modify this pattern to include more paths.16 */17 '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',18 ],19}- utils/supabase/ middleware.ts 파일생성 후 코드 복붙
1import { createServerClient } from '@supabase/ssr'2import { NextResponse, type NextRequest } from 'next/server'3
4export async function updateSession(request: NextRequest) {5 let supabaseResponse = NextResponse.next({6 request,7 })8
9 const supabase = createServerClient(10 process.env.NEXT_PUBLIC_SUPABASE_URL!,11 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,12 {13 cookies: {14 getAll() {15 return request.cookies.getAll()16 },17 setAll(cookiesToSet) {18 cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))19 supabaseResponse = NextResponse.next({20 request,21 })22 cookiesToSet.forEach(({ name, value, options }) =>23 supabaseResponse.cookies.set(name, value, options)24 )25 },26 },27 }28 )29
30 // Do not run code between createServerClient and31 // supabase.auth.getUser(). A simple mistake could make it very hard to debug32 // issues with users being randomly logged out.33
34 // IMPORTANT: DO NOT REMOVE auth.getUser()35
36 const {37 data: { user },38 } = await supabase.auth.getUser()39
40 if (41 !user &&42 !request.nextUrl.pathname.startsWith('/login') &&43 !request.nextUrl.pathname.startsWith('/auth')44 ) {45 // no user, potentially respond by redirecting the user to the login page46 const url = request.nextUrl.clone()47 url.pathname = '/login'48 return NextResponse.redirect(url)49 }50
51 // IMPORTANT: You *must* return the supabaseResponse object as it is.52 // If you're creating a new response object with NextResponse.next() make sure to:53 // 1. Pass the request in it, like so:54 // const myNewResponse = NextResponse.next({ request })55 // 2. Copy over the cookies, like so:56 // myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())57 // 3. Change the myNewResponse object to fit your needs, but avoid changing58 // the cookies!59 // 4. Finally:60 // return myNewResponse61 // If this is not done, you may be causing the browser and server to go out62 // of sync and terminate the user's session prematurely!63
64 return supabaseResponse65}
- ./middleware.ts 수정
1export async function middleware(request: NextRequest) {2 const url = new URL(request.url);3 console.log("접속 경로:", url.pathname);4 ...콘솔에 pathname이 확인된다

- Supabase 서버 클라이언트 생성

createServerClient()는 Supabase의 클라이언트 라이브러리에서 서버 측 클라이언트를 생성하는 함수입니다. 이 함수는 Supabase 프로젝트와의 연결을 설정하고, 데이터베이스와의 상호작용을 가능하게 합니다. 일반적으로 이 함수는 서버 환경에서 사용되며, 인증된 요청을 처리할 수 있도록 설정됩니다.
supabaseUrl: Supabase 프로젝트의 URL입니다.supabaseKey: 인증을 위한 키로, 서비스 역할 키 또는 익명 키를 사용할 수 있습니다.createServerClient()를 호출하면 Supabase 클라이언트 인스턴스가 생성되어 데이터베이스와의 상호작용을 수행할 수 있습니다. 이 클라이언트를 사용하여 데이터베이스 쿼리, 인증, 스토리지 작업 등을 수행할 수 있습니다.
1import { createServerClient, type CookieOptions } from "@supabase/ssr";2import { NextResponse, type NextRequest } from "next/server";3import { protectedPaths } from "./lib/constant";4
5export async function middleware(request: NextRequest) {6 let response = NextResponse.next({7 request: {8 headers: request.headers,9 },10 });11
12 const supabase = createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, {13 cookies: {14 get(name: string) {15 return request.cookies.get(name)?.value;16 },17 set(name: string, value: string, options: CookieOptions) {18 request.cookies.set({19 name,20 value,21 ...options,22 });23 response = NextResponse.next({24 request: {25 headers: request.headers,26 },27 });28 response.cookies.set({29 name,30 value,31 ...options,32 });33 },34 remove(name: string, options: CookieOptions) {35 request.cookies.set({36 name,37 value: "",38 ...options,39 });40 response = NextResponse.next({41 request: {42 headers: request.headers,43 },44 });45 response.cookies.set({46 name,47 value: "",48 ...options,49 });50 },51 },52 });53
54 const { data } = await supabase.auth.getSession();55 const url = new URL(request.url);56 if (data.session) {57 if (url.pathname === "/auth") {58 return NextResponse.redirect(new URL("/", request.url));59 }60 return response;61 } else {62 if (protectedPaths.includes(url.pathname)) {63 return NextResponse.redirect(new URL("/auth?next=" + url.pathname, request.url));64 }65 return response;66 }67}68
69export const config = {70 matcher: [71 /*72 * Match all request paths except for the ones starting with:73 * - _next/static (static files)74 * - _next/image (image optimization files)75 * - favicon.ico (favicon file)76 * Feel free to modify this pattern to include more paths.77 */78 "/((?!_next/static|_next/image|favicon.ico).*)",79 ],80};2-1-constant 설정
- next-with-supabase\lib\constant\index.ts 파일생성
1export const protectedPaths = ["/dashboard", "/profile"];- dashboard 와 profile 이동시 아래와 같이 라우팅 된다.

3-Profile수정
- handleLogout 함수에 router 추가
1...2import { **usePathname**, useRouter } from "next/navigation";3**import { protectedPaths } from "@/lib/constant";**4...5const Profile = () => {6...7** const pathname = usePathname();**8 const handleLogout = async () => {9...10 router.refresh();11** if (protectedPaths.includes(pathname)) {12 router.replace(`/auth?next=${pathname}`);13 }**14 };