🐨CoalaCoding
DocsExamplesTry itBoardB반
🐨CoalaCoding

개발자를 위한 한국어 웹 기술 문서

문서

  • JavaScript
  • Web Publishing
  • React
  • Python

커뮤니티

  • 게시판
  • 예제 모음
  • Try it 에디터

기타

  • GitHub
  • 관리자
© 2026 CoalaCoding. All rights reserved.
  • 01_1-supabase기초
  • 02_2-oauthnext설치
  • 03_3-oauthsupabase설정
  • 04_4-프로필테이블생성인증연동
  • 05_5-reactquery설정
  • 06_6-logout
  1. 홈
  2. 문서
  3. JavaScript
  4. Supabase & Next.js
  5. 06_6-logout

06_6-logout

코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.

구문

1-Profile.tsx

1-1-UI요소 추가

1-1-1-프로필 이미지가 없을 경우 사용자명 렌더링하기

  1. next-with-supabase\components\Profile.tsx
  2. Image 컴포넌트를 수정한다
**...
**</Link>
  ) : (**
****<>
  {data?.image_url ? (
    <Image src={data.image_url || ''} alt={data.display_name || ''} width={50} height={50} className="rounded-full" />
  ) : (
    <div className="h-[50px] w-[50px] flex items-center justify-center">
      <h1>{data.email}</h1>
    </div>
  )}
</>****
...**
  1. 테스트 1. 이미지 데이터가 null이면 email 이 렌더링된다.

2-Logout

2-1-Logout버튼만들기

2-1-1-components\Profile.tsx

  1. createClient함수 임포트 handleLogout 작성
import { createClient } from "@/lib/supabase/client";
...
  const handleLogout = () => {
    const supabase = createClient();
  };
  1. useQueryClient
"use client";
import React from "react";
import { Button } from "./ui/button";
import Link from "next/link";
import useUser from "@/app/hook/useUser";
import Image from "next/image";
import { createClient } from "@/lib/supabase/client";
import { useQueryClient } from "@tanstack/react-query";

const Profile = () => {
  const { isFetching, data } = useUser();
  const queryClient = useQueryClient();
  if (isFetching) {
    return <></>;
  }
 const handleLogout = async () => {
    const supabase = createClient();
    queryClient.clear();
    await supabase.auth.signOut();
  };
  return (
    <div>
      {!data?.id ? (
        <Link href="/auth">
          <Button variant="outline">SignIn</Button>
        </Link>
      ) : (
        <>
          {data?.image_url ? (
            <div className="flex flex-col justify-center items-center">
              <Image src={data.image_url || ""} alt={data.display_name || ""} width={50} height={50} className="rounded-full" />
              {data.display_name}
            </div>
          ) : (
            <div className="h-[50px] w-[50px] flex items-center justify-center">
              <h1>{data.email}</h1>
            </div>
          )}
        </>
        // 1:35
      )}
    </div>
  );
};
export default Profile;

  1. router
import { useRouter } from "next/navigation";
const Profile = () => {
...
  const router = useRouter();
...
}
  const handleLogout = async () => {
  ...
   router.refresh();
}

2-미들웨어 작성

ℹ️INFO

💡 미들웨어는 서버와 클라이언트 사이에서 로그인 정보를 안전하게 관리하는 중개자 역할을 한다.

🔹 클라이언트(사용자)가 페이지에 접근하면 → middleware.ts가 먼저 요청을 가로채서 로그인 상태를 확인 🔹 로그인되어 있으면 → 그냥 원래 가려던 페이지로 이동! 🔹 로그인 안 되어 있으면 → /login 페이지로 강제 이동! 🔹 세션 정보를 유지하면서 쿠키도 관리해서, 사용자가 로그아웃되거나 세션이 만료되면 다시 로그인하게 유도한다. 즉, "이 사용자가 로그인된 상태인지 확인하고, 필요한 경우 로그인 페이지로 보내는 보안 게이트" 같은 역할을 한다. 1.

<div className="flex flex-col justify-center items-center">
              <Image src={data.image_url || ""} alt={data.display_name || ""} width={50} height={50} className="rounded-full** cursor-pointer" onClick={handleLogout}/**>
              {data.display_name}
            </div>
  1. api docs 참조 https://supabase.com/docs/guides/auth/server-side/nextjs 4단계의 middleware Hook을 생성한다
  2. middleware.ts 파일생성
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 for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * Feel free to modify this pattern to include more paths.
     */
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}
  1. utils/supabase/ middleware.ts 파일생성 후 코드 복붙
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
  let supabaseResponse = NextResponse.next({
    request,
  })

  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, options }) => request.cookies.set(name, value))
          supabaseResponse = NextResponse.next({
            request,
          })
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options)
          )
        },
      },
    }
  )

  // Do not run code between createServerClient and
  // supabase.auth.getUser(). A simple mistake could make it very hard to debug
  // issues with users being randomly logged out.

  // IMPORTANT: DO NOT REMOVE auth.getUser()

  const {
    data: { user },
  } = await supabase.auth.getUser()

  if (
    !user &&
    !request.nextUrl.pathname.startsWith('/login') &&
    !request.nextUrl.pathname.startsWith('/auth')
  ) {
    // no user, potentially respond by redirecting the user to the login page
    const url = request.nextUrl.clone()
    url.pathname = '/login'
    return NextResponse.redirect(url)
  }

  // IMPORTANT: You *must* return the supabaseResponse object as it is.
  // If you're creating a new response object with NextResponse.next() make sure to:
  // 1. Pass the request in it, like so:
  //    const myNewResponse = NextResponse.next({ request })
  // 2. Copy over the cookies, like so:
  //    myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
  // 3. Change the myNewResponse object to fit your needs, but avoid changing
  //    the cookies!
  // 4. Finally:
  //    return myNewResponse
  // If this is not done, you may be causing the browser and server to go out
  // of sync and terminate the user's session prematurely!

  return supabaseResponse
}

  1. ./middleware.ts 수정
export async function middleware(request: NextRequest) {
  const url = new URL(request.url);
  console.log("접속 경로:", url.pathname);
  ...

콘솔에 pathname이 확인된다

  1. Supabase 서버 클라이언트 생성

https://supabase.com/docs/guides/auth/server-side/nextjs 표시된 부분을 복붙하고 필요없는 코드는 삭제한다

createServerClient()는 Supabase의 클라이언트 라이브러리에서 서버 측 클라이언트를 생성하는 함수입니다. 이 함수는 Supabase 프로젝트와의 연결을 설정하고, 데이터베이스와의 상호작용을 가능하게 합니다. 일반적으로 이 함수는 서버 환경에서 사용되며, 인증된 요청을 처리할 수 있도록 설정됩니다.

  • supabaseUrl: Supabase 프로젝트의 URL입니다.
  • supabaseKey: 인증을 위한 키로, 서비스 역할 키 또는 익명 키를 사용할 수 있습니다.
  • createServerClient()를 호출하면 Supabase 클라이언트 인스턴스가 생성되어 데이터베이스와의 상호작용을 수행할 수 있습니다. 이 클라이언트를 사용하여 데이터베이스 쿼리, 인증, 스토리지 작업 등을 수행할 수 있습니다.
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
import { protectedPaths } from "./lib/constant";

export async function middleware(request: NextRequest) {
  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: {
      get(name: string) {
        return request.cookies.get(name)?.value;
      },
      set(name: string, value: string, options: CookieOptions) {
        request.cookies.set({
          name,
          value,
          ...options,
        });
        response = NextResponse.next({
          request: {
            headers: request.headers,
          },
        });
        response.cookies.set({
          name,
          value,
          ...options,
        });
      },
      remove(name: string, options: CookieOptions) {
        request.cookies.set({
          name,
          value: "",
          ...options,
        });
        response = NextResponse.next({
          request: {
            headers: request.headers,
          },
        });
        response.cookies.set({
          name,
          value: "",
          ...options,
        });
      },
    },
  });

  const { data } = await supabase.auth.getSession();
  const url = new URL(request.url);
  if (data.session) {
    if (url.pathname === "/auth") {
      return NextResponse.redirect(new URL("/", request.url));
    }
    return response;
  } else {
    if (protectedPaths.includes(url.pathname)) {
      return NextResponse.redirect(new URL("/auth?next=" + url.pathname, request.url));
    }
    return response;
  }
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * Feel free to modify this pattern to include more paths.
     */
    "/((?!_next/static|_next/image|favicon.ico).*)",
  ],
};

2-1-constant 설정

  1. next-with-supabase\lib\constant\index.ts 파일생성
export const protectedPaths = ["/dashboard", "/profile"];

  1. dashboard 와 profile 이동시 아래와 같이 라우팅 된다.

3-Profile수정

  1. handleLogout 함수에 router 추가
...
import { **usePathname**, useRouter } from "next/navigation";
**import { protectedPaths } from "@/lib/constant";**
...
const Profile = () => {
...
**  const pathname = usePathname();**
  const handleLogout = async () => {
...
    router.refresh();
**    if (protectedPaths.includes(pathname)) {
      router.replace(`/auth?next=${pathname}`);
    }**
  };

목차

  • 구문