일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- express
- useEffect
- lazy()
- 리액트
- 리액트 훅
- 리액트훅
- docker
- key
- NextJs
- 초기마운트
- next-cookies
- SSR
- ErrorBoundary
- react
- Database
- reactquery
- Firebase
- CSR
- msw
- 클래스
- react-hook-form
- 모던자바스크립트
- react-hook
- useLayoutEffect
- Today
- Total
한우의 개발일기
[Next.js] next-cookies(서버/클라이언트 쿠키 사용기) 본문
일단 쿠키란 무엇인가??
쿠키(Cookie)란?
쿠키는 웹 서버가 생성하여 웹 브라우저로 전송하는 작은 정보 파일입니다. 웹 브라우저는 수신한 쿠키를 미리 정해진 기간 동안 또는 웹 사이트에서의 사용자 세션 기간 동안 저장합니다. 웹 브라우저는 향후 사용자가 웹 서버에 요청할 때 관련 쿠키를 첨부합니다.
그렇다면 Next에서의 쿠키는 어떻게 쓰일까??
Next.js의 쿠키 처리는 크게 4가지 환경에서 이루어진다고한다
- 서버 컴포넌트(Server Components)에서의 쿠키
// Server Component
import { cookies } from 'next/headers'
export default function Page() {
const cookieStore = cookies()
const theme = cookieStore.get('theme')
return <div>현재 테마: {theme?.value}</div>
}
- 서버 액션(Server Actions)에서의 쿠키
'use server'
import { cookies } from 'next/headers'
export async function updateTheme(theme: string) {
cookies().set('theme', theme)
}
- 미들웨어(Middleware)에서의 쿠키
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 쿠키 읽기
const theme = request.cookies.get('theme')
// 쿠키 설정
const response = NextResponse.next()
response.cookies.set('visited', 'true')
return response
}
- Route Handlers에서의 쿠키
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const cookieStore = cookies()
const token = cookieStore.get('token')
return NextResponse.json({ token: token?.value })
}
이렇게 사용이 되는데 여기서의 주요 특징과 개념을 간단하게 말해보자면
주요 특징과 개념
- 서버/클라이언트 분리
- 서버 컴포넌트:
cookies()
API 사용 - 클라이언트:
document.cookie
또는js-cookie
사용 - 미들웨어:
NextRequest/NextResponse
쿠키 API 사용
- 보안 기능
- HttpOnly 쿠키 지원
- Secure 플래그 지원
- SameSite 설정 가능
- 유용한 기능들
- 쿠키 설정/읽기/삭제
- 만료 시간 설정
- 도메인 및 경로 설정
- 암호화된 쿠키 지원
이렇게 되어있다.
그렇다면
내가 프로젝트에서 쓰기위해 만든 next-cookies 유틸함수를 살펴보자
"use server";
import { cookies } from "next/headers";
interface CookieOptions {
/**
* 쿠키의 만료 일시를 설정
* Date 객체나 타임스탬프(밀리초) 사용
* @example
* expires: new Date('2024-12-31')
* expires: Date.now() + 24 * 60 * 60 * 1000 // 24시간 후
*/
expires?: Date | number;
/**
* 쿠키가 유효한 상대적 시간(초)
* @example
* maxAge: 60 * 60 // 1시간
* maxAge: 24 * 60 * 60 // 1일
* maxAge: 30 * 24 * 60 * 60 // 30일
*/
maxAge?: number;
/**
* 쿠키가 유효한 경로
* @default "/"
* @example
* path: "/" // 모든 경로에서 접근 가능
* path: "/admin" // /admin 경로에서만 접근 가능
* path: "/shop/cart" // /shop/cart 경로에서만 접근 가능
*/
path?: string;
/**
* 쿠키가 유효한 도메인
* @example
* domain: "example.com" // example.com과 그 서브도메인에서 접근 가능
* domain: "api.example.com" // api.example.com에서만 접근 가능
*/
domain?: string;
/**
* HTTPS 전송 여부
* - true: HTTPS 연결에서만 쿠키 전송
* - false: HTTP에서도 쿠키 전송
* @default true in production
* @example
* secure: true // HTTPS에서만 쿠키 전송
* secure: false // HTTP에서도 쿠키 전송 (개발 환경)
*/
secure?: boolean;
/**
* JavaScript에서의 쿠키 접근 제한
* - true: document.cookie로 접근 불가 (보안 강화)
* - false: document.cookie로 접근 가능
* @default true
* @example
* httpOnly: true // JavaScript에서 접근 불가
* httpOnly: false // JavaScript에서 접근 가능
*/
httpOnly?: boolean;
/**
* 크로스 사이트 요청에 대한 쿠키 전송 정책
* - "strict": 같은 사이트 요청에만 쿠키 전송
* - "lax": 일부 크로스 사이트 요청에 쿠키 전송 허용 (기본값)
* - "none": 모든 크로스 사이트 요청에 쿠키 전송 (secure: true 필요)
* @default "lax"
* @example
* sameSite: "strict" // 가장 엄격한 보안
* sameSite: "lax" // 적절한 보안과 사용성 균형
* sameSite: "none" // 크로스 사이트 요청 허용
*/
sameSite?: "lax" | "strict" | "none";
}
export async function setCookie(
name: string,
value: string,
options: CookieOptions = {},
): Promise<void> {
const defaultOptions: CookieOptions = {
path: "/",
maxAge: 24 * 60 * 60, // 1일
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
};
const cookieOptions = { ...defaultOptions, ...options };
cookies().set(name, value, cookieOptions);
}
export async function getCookie(name: string): Promise<string | undefined> {
try {
return cookies().get(name)?.value;
} catch (error) {
console.error(`Error getting cookie ${name}:`, error);
return undefined;
}
}
export async function deleteCookie(
name: string,
options: Pick<CookieOptions, "path" | "domain"> = {},
): Promise<void> {
try {
cookies().delete({ name, ...options });
} catch (error) {
console.error(`Error deleting cookie ${name}:`, error);
}
}
export async function getAllCookies(): Promise<Record<string, string>> {
const cookieStore = cookies();
return Object.fromEntries(
cookieStore.getAll().map((cookie) => [cookie.name, cookie.value]),
);
}
export async function hasCookie(name: string): Promise<boolean> {
return cookies().has(name);
}
만약 httponly
를 false
로 해준다면 document.cookie
를 통하여 클라이언트 컴포넌트에서도 쿠키를 가져올 수 있지만 보안상 좋지 않을거 같음과 동시에 클라이언트 단에서 쿠키가 자주 필요할경우 매번 서버를 한번 거쳐 통신을 하게 될거같았다
클라이언트 → document.cookie 설정
→ 서버 액션 호출
→ 서버에서 쿠키 설정
→ 응답
이런식으로 된다면 네트워크 요청이 계속 발생하므로 서버에 좋지 않을거라 판단이 되었다.
그렇다면 어떻게 해결할까?
인터넷을 찾아보면 여러가지 쿠키 관련 라이브러리를 통해 해결을 할 수있다고는 하는데
나는 전역상태 관리 툴인 zustand
를 이용해 보기로 했다
전역상태관리로 어떻게 해결을할까?
동작 원리
- 상태 중앙화
- Zustand store가 token 상태를 전역적으로 관리
- 모든 컴포넌트가 동일한 token 값을 참조
- 상태 변경시 자동으로 구독 컴포넌트 리렌더링
- 영구 저장소 동기화
- token 상태 변경시 자동으로 쿠키에 동기화
- 새로고침해도 쿠키에서 상태 복원 가능
토큰을 메모리에 유지하면서 필요할 때마다 서버 요청 없이 바로 사용할 수 있고 , 새로고침시에도 쿠키에서 토큰을 복원할 수 있을것이다
로그인/로그아웃시에만 서버와 통신하면 되므로 서버에 부담도 없을거라고 생각을 했다.
코드
"use client";
import { create } from "zustand";
import { deleteCookie, getCookie, setCookie } from "@/utils/next-cookies";
interface AuthStore {
token: string | null;
setToken: (token: string) => Promise<void>;
clearToken: () => Promise<void>;
initToken: () => Promise<void>;
}
/**
* 인증 토큰을 관리하는 Zustand 스토어 훅
*
* @example
* // 2. API 요청시 토큰 사용
* function ProtectedComponent() {
* const { token } = useAuth();
*
* const fetchData = async () => {
* const response = await fetch('/api/protected', {
* headers: {
* 'Authorization': `Bearer ${token}`
* }
* });
* };
* }
*
* @example
* // 3. 컴포넌트 마운트시 토큰 초기화
* function App() {
* const { initToken } = useAuth();
*
* useEffect(() => {
* initToken(); // 페이지 로드시 쿠키에서 토큰 복원
* }, []);
* }
*
* @example
* // 4. 로그아웃시 토큰 제거
* function LogoutButton() {
* const { clearToken } = useAuth();
*
* const handleLogout = async () => {
* await clearToken(); // 로그아웃시 토큰 삭제
* };
* }
*/
export const useAuth = create<AuthStore>((set) => ({
token: null,
setToken: async (token: string) => {
await setCookie("token", token);
set({ token });
},
clearToken: async () => {
await deleteCookie("token");
set({ token: null });
},
initToken: async () => {
const token = await getCookie("token");
if (token) set({ token });
},
}));
이러한 방식으로 클라이언트 단에선 Zustand store에서 중앙관리 된 쿠키의 토큰을 사용하고 서버에선 서버액션을 통한 쿠키의 토큰을 사용해서 최적화를 할 수 있었다.
'NextJS' 카테고리의 다른 글
[Next.js]next 프로젝트 도커 빌드하기(2) (0) | 2024.11.11 |
---|---|
[Next.js]next 프로젝트에 도커 적용기 (0) | 2024.11.11 |
[Next.js]Nextjs 프로젝트 도커로 빌드하기 (0) | 2024.11.11 |
[Next.js] ReactQuery 적용기 (1) | 2024.11.11 |
[Next.js]Next.js에 MSW 적용하기 (0) | 2024.11.11 |