401 Unauthorization ์—๋Ÿฌ์˜ ์›์ธ ์ฐพ๊ธฐ

2026. 3. 12. 01:52ใ†Trouble Shooting & Issues/Linkiving

๋ฐ˜์‘ํ˜•

 

์š”์•ฝ
middleware - bff - vercel - backend ์ฒดํฌํ•˜๋‹ค vercel env์—์„œ ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•จ

๋ชฉ์ฐจ

1. ์ƒํ™ฉ: ๋กœ์ปฌ์—์„œ๋Š” ๋กœ๊ทธ์ธ์ด ์ž˜๋˜๋Š”๋ฐ ๋ฐฐํฌ๋ณธ์—์„œ๋Š” ์•ˆ๋œ๋‹ค

2. ํ˜„์žฌ์˜ ์ธ์ฆ ํ๋ฆ„ ์ •๋ฆฌํ•˜๊ธฐ

3. ๋ฌธ์ œ ๋ฐœ์ƒ ์ง€์  ํ™•์ธํ•˜๊ธฐ - middleware, serverApiClient

4. Sentry ์—ฐ๋™ํ•˜๊ธฐ

5. Vercel env ๊ฐ’ ํ™•์ธํ•˜๊ธฐ

6. ๋ฐฐ์šด ๊ฒƒ


1. ์ƒํ™ฉ: ๋กœ์ปฌ์—์„œ๋Š” ๋กœ๊ทธ์ธ์ด ์ž˜๋˜๋Š”๋ฐ ๋ฐฐํฌ๋ณธ์—์„œ๋Š” ์•ˆ๋œ๋‹ค.

๊ตฌ๊ธ€ OAuth ๋กœ๊ทธ์ธ ํ›„ ๋ฐฐํฌ์ฃผ์†Œ๋กœ ๋ฐฑ์—์„œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋˜๋Š” ๊ฒƒ์„ ์ˆ˜์ •์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค ํ•˜์—ฌ

ํ”„๋ก ํŠธ์—์„œ๋Š” ๋ฐฑ์—”๋“œ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ Docker๋กœ http:localhost:8080์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋ฉฐ ๊ฐœ๋ฐœ์„ ์ญ‰ ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

 

๋ฐฑ์—”๋“œ ๋ฌธ์ œ๋กœ ๋ฐฐํฌ์ฃผ์†Œ์—์„œ ๋กœ๊ทธ์ธ๋งŒ ํ•˜๋ฉด ํŠ•๊ธฐ๋‹ค๊ฐ€ ๊ทธ๊ฒŒ ํ•ด๊ฒฐ์ด ๋ผ์„œ ๋“œ๋””์–ด ๋กœ๊ทธ์ธ์ด ์™„์„ฑ๋๋‚˜ ํ–ˆ๋”๋‹ˆ

๋กœ๊ทธ์ธํ•˜์—ฌ ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ–ˆ๋Š”๋ฐ 401 Unauthorization ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค.

 

ํ”„๋ก ํŠธ์™€ ๋ฐฑ์—”๋“œ ๋ชจ๋‘ ์›์ธ์„ ๋ชป์ฐพ๊ณ  ์žˆ๋Š” ์ƒํ™ฉ..

 


2. ํ˜„์žฌ์˜ ์ธ์ฆ ํ๋ฆ„ ์ •๋ฆฌํ•˜๊ธฐ

ํ˜„์žฌ ์ธ์ฆ ํ๋ฆ„์„ ์šฐ์„  ๋‹ค์‹œ ํ•œ ๋ฒˆ ์ •๋ฆฌํ•ด๋ณด์ž.

์‹œ๊ฐ„์ด ์—†์–ด ์•„์ง ๋ธ”๋กœ๊ทธ์— ์ •๋ฆฌํ•˜์ง€๋Š” ๋ชปํ–ˆ์ง€๋งŒ ์šฐ๋ฆฌ ํŒ€์€ BFF(Backend For Frontend) ํŒจํ„ด + middleware๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

๋ธŒ๋ผ์šฐ์ € → Next.js BFF(Route Handler) → ์™ธ๋ถ€ ๋ฐฑ์—”๋“œ(api.linkiving.com)

 

1๋‹จ๊ณ„ - ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํด๋ฆญ

์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ € ์ƒ์—์„œ ๋ฐฑ์—”๋“œ์˜ ์—”๋“œํฌ์ธํŠธ๋กœ ์ง์ ‘ ์ด๋™ํ•œ๋‹ค.

https://api.linkiving.com/oauth2/authorization/google

 

2๋‹จ๊ณ„ - Google OAuth ์ฒ˜๋ฆฌ (๋ฐฑ์—”๋“œ ๋”ด์—์„œ ์ง„ํ–‰)

๋ฐฑ์—”๋“œ์—์„œ(api.linkiving.com) Google ์ธ์ • ์„œ๋ฒ„๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•˜์—ฌ Google ๋กœ๊ทธ์ธ ๋ฐ ๋™์˜๋ฅผ ์™„๋ฃŒํ•œ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ์„ ์™„๋ฃŒํ•˜๋ฉด Google์—์„œ ๋ฐฑ์—”๋“œ callback url๋กœ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

๋ฐฑ์—”๋“œ๋Š” ์ธ์ฆ ์ฝ”๋“œ๋กœ Google๋กœ๋ถ€ํ„ฐ ์œ ์ € ์ •๋ณด๋ฅผ ํš๋“ํ•˜๊ณ , accessToken, refreshToken์„ ๋ฐœ๊ธ‰ํ•œ๋‹ค.

 

3๋‹จ๊ณ„ - ๋ฐฑ์—”๋“œ๊ฐ€ ์ฟ ํ‚ค์— ํ† ํฐ์„ ์‹ฌ์–ด์„œ ํ”„๋ก ํŠธ๋กœ ๋ณด๋ƒ„

๋ฐฑ์—”๋“œ๊ฐ€ Cookie์— ๋ฐœ๊ธ‰ํ•œ ํ† ํฐ์„ ์‹ฌ์–ด์„œ ํ”„๋ก ํŠธ ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ธ๋‹ค.

์ด ๋•Œ ํ† ํฐ์€ HttpOnly ์ฟ ํ‚ค๋กœ, ๋ธŒ๋ผ์šฐ์ € JS์—์„œ ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค.

 

4๋‹จ๊ณ„ - middleware ํ†ต๊ณผ

/home ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋ฉด ๋ฏธ๋“ค์›จ์–ด๊ฐ€ accessToken์˜ ์กด์žฌ๋ฅผ ํ™•์ธํ•˜์—ฌ ํ† ํฐ์ด ์กด์žฌํ•˜๋ฉด /home์œผ๋กœ, ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ๋กœ๊ทธ์ธํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜ ์‹œํ‚จ๋‹ค.

 

5๋‹จ๊ณ„ - ์ธ์ฆ๋œ API ์š”์ฒญ ๋ฐ ์‘๋‹ต

๋ธŒ๋ผ์šฐ์ €๋Š” ์ด์ œ ๋ฐœ๊ธ‰๋ฐ›์€ ํ† ํฐ์—์„œ ์ฟ ํ‚ค๋ฅผ ๊บผ๋‚ด ์‚ฌ์šฉํ•œ๋‹ค.

BFF๋ฅผ ๊ฑฐ์ณ์„œ ๋ฐฑ์—”๋“œ์—๊ฒŒ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ serverApiClient๊ฐ€ ์ฟ ํ‚ค๋กœ๋ถ€ํ„ฐ ํ† ํฐ์„ ์ฝ๊ณ ,
ํ—ค๋” Authorization์— ํ† ํฐ์„ ์ฒจ๋ถ€ํ•ด์„œ ์š”์ฒญํ•œ๋‹ค.

 

๋ฐฑ์—”๋“œ๋Š” ์š”์ฒญ ํ—ค๋”์˜ ํ† ํฐ์„ ํ™•์ธํ•ด์„œ ์‘๋‹ต์„ ๋ณด๋‚ธ๋‹ค.

 


3. ๋ฌธ์ œ ๋ฐœ์ƒ ์ง€์  ํ™•์ธํ•˜๊ธฐ - middleware, serverApiClient

1. middleware

import { COOKIES_KEYS } from '@/lib/constants/cookies';	// ์ฟ ํ‚ค์— ์‚ฝ์ž…๋œ ํ† ํฐ๋ช… ์ •์˜
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const publicRoutes = ['/'];
export function middleware(req: NextRequest) {
  const token = req.cookies.get(COOKIES_KEYS.ACCESS_TOKEN)?.value;	// ์ฟ ํ‚ค์—์„œ ACCESS_TOKEN ๊ฐ’ ์ฝ์–ด์˜ด
  const { pathname } = req.nextUrl;

  if (publicRoutes.includes(pathname)) {
    if (token) {	// ํ† ํฐ์ด ์žˆ์„ ๊ฒฝ์šฐ
      return NextResponse.redirect(new URL('/home', req.url));	// /home์œผ๋กœ ์ด๋™
    }
    return NextResponse.next();
  }

  if (!token) {	// ํ† ํฐ์ด ์—†์„ ๊ฒฝ์šฐ
    const loginUrl = new URL('/', req.url);
    loginUrl.searchParams.set('redirect', pathname);	// /์œผ๋กœ ์ด๋™
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.next();
}

// ์ ์šฉ ๋ฒ”์œ„ (api, _next/static, _/next/image, favicon.ico, fonts, images, monitoring(sentry)๋Š” ์ธ์ฆ ๊ฒ€์‚ฌ ํ†ต๊ณผ
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico|fonts|images|monitoring).*)'],
};

์ผ๋‹จ ๋ฏธ๋“ค์›จ์–ด๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ ์ง€์ ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.

๋ฏธ๋“ค์›จ์–ด๋Š” ๋กœ๊ทธ์ธ ํ›„ ๋ฐ›์€ ์ฟ ํ‚ค์— accessToken์ด ํฌํ•จ๋˜์–ด์žˆ๋Š”์ง€๋ฅผ ์ฒดํฌํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

/home์œผ๋กœ ๋„˜์–ด๊ฐ„ ์‹œ์ ์—์„œ ํ”„๋ก ํŠธ๋Š” ํ† ํฐ์ด ํฌํ•จ๋œ ์ฟ ํ‚ค๋ฅผ ๋ฐ›์•˜๋‹ค๋Š” ์˜๋ฏธ๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

 

2. server/apiClient.ts

serverApiClient๋Š” ์ข€ ๋” ์ฃผ๋ชฉํ• ๋งŒํ•œ ์ฝ”๋“œ์ด๋‹ค.

ํ”„๋ก ํŠธ๋Š” ํ† ํฐ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์—ฌ๊ธฐ์„œ ์ฟ ํ‚ค๋ฅผ ๊บผ๋‚ด์„œ ๋ฐฑ์—”๋“œ ์š”์ฒญ์— ๊ฐ™์ด ๋„˜๊ฒจ์•ผํ•˜๋Š”๋ฐ ๊ทธ ์–ด๋”˜๊ฐ€์—์„œ ํ† ํฐ์ด ์—†์–ด์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

import { createFetchError } from '@/hooks/util/api/error/errors';
import { COOKIES_KEYS } from '@/lib/constants/cookies';
import { cookies } from 'next/headers';

import { ApiError } from '../errors/ApiError';

const API_BASE_URL = process.env.NEXT_PUBLIC_BASE_API_URL; // TODO: ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋…ผ์˜ ํ›„ BASE_API_URL๋กœ ๋ณ€๊ฒฝ

if (!API_BASE_URL) {
  throw new Error('Missing environment variable: NEXT_PUBLIC_BASE_API_URL');
}

/**
 * ์„œ๋ฒ„ ์‚ฌ์ด๋“œ API ํด๋ผ์ด์–ธํŠธ
 * Next.js API Routes์—์„œ๋งŒ ์‚ฌ์šฉ
 */
export async function serverApiClient<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const cookieStore = await cookies(); // await ์ถ”๊ฐ€
  const token = cookieStore.get(COOKIES_KEYS.ACCESS_TOKEN)?.value;
  if (!token) {
    throw new ApiError(401, 'No authentication token');
  }

  const response = await fetch(`${API_BASE_URL}${endpoint}`, {
    ...options,
    cache: options.cache ?? 'no-store',
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
      Authorization: `Bearer ${token}`,
    },
  });

  if (!response.ok) {
    const errorData = await response.json().catch(() => ({}));
    throw createFetchError(errorData.message || `Request failed`, {
      status: response.status,
    });
  }

  return response.json();
}

1. ์šฐ์„  ํ”„๋ก ํŠธ๊ฐ€ ๋ณด๋Š” ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋Š” ์•„๋ž˜์™€ ๊ฐ™์•˜๋‹ค.

 

์ด๋Š” ๊ณตํ†ต ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ์— ์ •์˜๋˜์–ด ์žˆ๋Š” ๋ฐ”๋”” ํ˜•ํƒœ๋กœ, createFetchError์—์„œ ์žกํžˆ๊ณ  ์žˆ๋Š” ์—๋Ÿฌ์ธ ๊ฒƒ์ด๋‹ค.

๊ทธ๋Ÿผ token๋„ ์žˆ๋Š”๋ฐ fetch๊ฐ€ ์‹คํŒจํ•œ ์ด์œ ๊ฐ€ ๋ญ˜๊นŒ?

 

๊ทธ๊ฑธ ์œ„ํ•ด์„œ serverApiClient ๋‚ด๋ถ€์— ์ฝ˜์†”์„ ์ฐ์–ด๋ดค๋‹ค.

์‚ฌ์‹ค ๋กœ์ง์ƒ์œผ๋กœ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„์ด์ง€๋งŒ ์•ˆ๋˜๋‹ˆ๊นŒ ์ง์ ‘ ์ฐ์–ด์„œ vercel ๋กœ๊ทธ๋กœ ํ™•์ธํ•ด๋ณธ ๊ฒฐ๊ณผ ์—ญ์‹œ endpoint์™€ token์ด ์ •์ƒ์ ์œผ๋กœ ์ถœ๋ ฅ๋œ๋‹ค.

 

์ดํ›„๋กœ ๋ฐฑ์—”๋“œ์˜ postman๊ณผ ํ”„๋ก ํŠธ curl ๊ฐ๊ฐ์—์„œ ํ™•์ธํ•ด๋ณธ ๊ฒฐ๊ณผ ์–‘์ชฝ ๋ชจ๋‘ ๋กœ์ปฌ ํ…Œ์ŠคํŠธ๋กœ๋Š” ์ž˜ ๋˜๋Š”๋ฐ ๋ฐฐํฌ์ฃผ์†Œ์—์„œ ํ…Œ์ŠคํŠธ๋งŒ ํ•˜๋ฉด ํ”„๋ก ํŠธ์—์„œ๋Š” ์š”์ฒญ์„ ๋ถ„๋ช…ํžˆ ๋ณด๋‚ด๊ณ , ๋ฐฑ์—”๋“œ์—์„œ๋Š” ๋ถ„๋ช…ํžˆ ์š”์ฒญ์ด ์˜ค์ง€๋„ ์•Š๋Š” ์ƒํ™ฉ์ด ๊ณ„์†๋œ๋‹ค. (์‚ฌ์‹ค ๊ฒฝํ—˜๊ณผ ์ž์‹ ์ด ์ข€ ๋” ์žˆ์—ˆ์œผ๋ฉด ์—ฌ๊ธฐ์„œ ์ข€ ๋” ์‹ค๋งˆ๋ฆฌ๋ฅผ ๋น ๋ฅด๊ฒŒ ์žก์„ ์ˆ˜ ์žˆ์—ˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.)

 

๊ทธ๋ ‡๊ฒŒ ์ดํ‹€์— ๊ฑธ์ณ์„œ ๋‹ค์–‘ํ•œ ํ•ด์„์ด ๋‚˜์˜จ๋‹ค.

 

๋ฐฑ์—”๋“œ 2์ธ: 

>> ํด๋ผ์ด์–ธํŠธ → next.js Backend → Backend๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— ์š”์ฒญ URL์€ ์Šค์›จ๊ฑฐ์— ๋ช…์‹œ๋œ ์—”๋“œํฌ์ธํŠธ์™€ ๋‹ค๋ฅธ๊ฒŒ ๋งž๋‹ค.

ํด๋ผ์ด์–ธํŠธ๊ฐ€ next.js route๊ฐ€ ๋“ค์–ด์žˆ๋Š” api/links/route.ts๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ๊ฑฐ๊ธฐ์„œ ์‹ค์ œ ๋ฐฑ์—”๋“œ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ.

์—ฌ๊ธฐ์„œ ์ด ์นœ๊ตฌ๋“ค์ด ์ž ๊น ๊ฝค ํ˜ผ๋ž€์Šค๋Ÿฌ์›Œํ–ˆ๋Š”๋ฐ ์ด๋Ÿฐ๊ฑธ ๋ณด๋ฉด ์ •๋ง ๋ฐฑ๊ณผ ํ”„๋ก ํŠธ๋„ ์„œ๋กœ์˜ ๋ฐฉ์‹์— ๋Œ€ํ•ด ์–ด๋А์ •๋„๋Š” ์•„๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ๊ตฌ๋‚˜ ์‹ถ์—ˆ๋‹ค.

 

๋ฏธ๋“ค์›จ์–ด๋Š” ์•„๋‹ˆ๋‹ˆ๊นŒ ๋„˜์–ด๊ฐ€๊ณ , me ์š”์ฒญ์ด ๋œ๋‹ค๊ธธ๋ž˜ ํ™•์ธํ•ด๋ณธ ๊ฒฐ๊ณผ, 

์ € me์—์„œ๋Š” ์•„์˜ˆ ๋ฐฑ์—”๋“œ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์ง€ ์•Š๊ณ  ์žˆ์—ˆ๋‹ค. ์ € ๋œ๋‹ค๋Š” ๊ฒƒ์€ ๊ทธ๋ƒฅ 200ok๋ฅผ ๋ฐ›๋Š”๋‹ค๋Š” ์–˜๊ธฐ์˜€๋˜๋“ฏ.

์ฟ ํ‚ค์—์„œ ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ”๋กœ ์ฝ์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ์— ์ธ์ฆ์„ ๋‹ด์€ ์š”์ฒญ ๋กœ์ง์ด ์—†์—ˆ๋‹ค.

 

 

ํด๋กœ๋“œ & ๋‚˜

curl -v https://api.linkiving.com/v1/chats \
  -H "Authorization: Bearer ํ† ํฐ๊ฐ’

๋กœ์ปฌ ํ„ฐ๋ฏธ๋„๋กœ ์œ„์™€ ๊ฐ™์ด ์š”์ฒญ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋ฉด ์‘๋‹ต์„ ์ž˜ ๋ฐ›์•„์˜จ๋‹ค.

๋ฐฑ์—”๋“œ์˜ ๋‹ค๋ฅธ ์‘๋‹ต ํ˜•ํƒœ์— ๋Œ€ํ•ด ๋‚ด๊ฐ€ ์ž˜ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ํด๋กœ๋“œ์—๊ฒŒ ์ฒดํฌ ํ•ด๋ดค๋‹ค. ๋ฌด์กฐ๊ฑด ๊ฒ€์ƒ‰ํ•˜๋ผ๊ณ  ํ•จ

์‘๋‹ต ํ˜•ํƒœ ์ค‘ Access-Control-Allow-Origin: * ๋ฅผ ์งš์—ˆ๋‹ค.

allowCredentials: true ์™€ Access-Control-Allow-Origin: *์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฐจ๋‹จ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ง์ด๋‹ค.

์กฐ๊ธˆ ์ฐพ์•„๋ณด๋‹ˆ๊นŒ ์‹ค์ œ๋กœ ์žˆ๋Š” ๊ฒฝ์šฐ์ด๊ธธ๋ž˜ ๋ฐฑ์—”๋“œ์— ํ™•์ธ ์š”์ฒญํ–ˆ๋‹ค.

 

 

๋ฐฑ์—”๋“œ ๋งˆ์ง€๋ง‰ ๋‹ต๋ณ€

๋ฐฑ์—”๋“œ์—์„œ ๋กœ๊น… ์ž‘์—…์„ ์™„๋ฃŒํ•œ ํ›„, ์š”์ฒญ ์ž์ฒด๊ฐ€ ์•ˆ๋“ค์–ด์˜จ๋‹ค๋Š” ์‘๋‹ต์„ ๋ฐ›์•˜๋‹ค.

๊ทธ๋Ÿผ ์—ฌ๊ธฐ์„œ ๋“œ๋Š” ์˜๋ฌธ.. ๋ฐฑ์—”๋“œ์— ์š”์ฒญ์„ ์•ˆ๋ณด๋ƒˆ๋Š”๋ฐ 401์ด๋ผ๋Š” ์‘๋‹ต์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ผ๊นŒ..??

๊ทธ๋ฆฌ๊ณ  ์ •๋ง ํ”„๋ก ํŠธ ๋ฌธ์ œ๋ผ๋ฉด ์™œ Docker์—์„œ 8080๊ณผ ์š”์ฒญ์‘๋‹ต์„ ์ง„ํ–‰ํ•  ๋•Œ๋Š” ์™œ ์ž˜๋๋˜ ๊ฑธ๊นŒ??

 

๋”ฐ๋ผ์„œ ๋ฌธ์ œ๋Š” ํ”„๋ก ํŠธ์—์„œ ๋ฐฑ์—”๋“œ๋กœ ์š”์ฒญ์„ ๋˜์ง€๊ณ  ๋‚œ ์ดํ›„ ๋ฐฑ์—”๋“œ์— ๋‹ฟ๊ธฐ ์ด์ „ ๊ทธ ์‚ฌ์ด์—์„œ ๋ฐœ์ƒํ•œ๋‹ค๋Š” ์ƒ๊ฐ์ด ๊ฐ•ํ•ด์กŒ๋‹ค.

ํ•˜์ง€๋งŒ ํ˜น์‹œ ๋ชจ๋ฅด๋‹ˆ๊นŒ ๋” ํ™•์‹คํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋‹จ๊ณ„์ ์œผ๋กœ ํ™•์ธํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

 


4. Sentry ์—ฐ๋™ ํ›„ ์ฒดํฌํ•˜๊ธฐ

1. ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ์— Sentry ์‹ฌ๊ธฐ

์†”์งํžˆ ๋งํ•˜๋ฉด ํ”„๋ก ํŠธ์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ๊ณ„์† ์›์ธ์„ ์ฐพ์•„๋ดค์œผ๋‚˜, ์•„๋ฌด๋ฆฌ ์ƒ๊ฐํ•ด๋„ ํ˜„์žฌ ํ”„๋ก ํŠธ ์ฝ”๋“œ์—์„œ ๋ฌธ์ œ๊ฐ€ ์—†์–ด๋ณด์˜€๋‹ค.

ํ† ํฐ์„ ๋ฐ›์•˜๊ณ , apiServerClient์—์„œ ํ—ค๋”์— ํ† ํฐ์„ ๋‹ด์•„์„œ BFF ๊ฒฝ๋กœ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค. ๊ทธ ์‚ฌ์ด์— ๋‹ค๋ฅธ ๋ฌธ์ œ๊ฐ€ ๋ผ์–ด๋“ค ํ‹ˆ์ด ์—†๋‹ค.

 

ํ•˜์ง€๋งŒ ์ž˜ ๋ชจ๋ฅด๋Š” ์ผ์ด๊ธฐ์— ์ง€๊ธˆ๊นŒ์ง€ ๋ฌด์ž‘์ • ์ฝ˜์†”์„ ์ถ”๊ฐ€ํ–ˆ์œผ๋‚˜,

๊ฐ์„ ๋ชป์žก๋Š” ์ƒํ™ฉ์—์„œ ๊ทธ๊ฑด ๋„ˆ๋ฌด ๊ด‘๋ฒ”์œ„ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋А๊ปด Sentry๋ฅผ ์—ฐ๋™ํ•ด๋ณด์•˜๋‹ค.

 

Sentry ํ”„๋กœ์ ํŠธ ์ถ”๊ฐ€ ํ›„, ์ž๋™ ์„ธํŒ…์œผ๋กœ ์ง„ํ–‰ํ–ˆ๊ณ , handleApiError์— Sentry ์ถ”์ ์„ ์‹ฌ์—ˆ๋‹ค.

import { zodErrorToResponse } from '@/hooks/util/zodError';
import { ApiError } from '@/lib/errors/ApiError';
import * as Sentry from '@sentry/nextjs';

import { RequestValidationError } from '../request/requestError';
import { FetchError, ParseError, TimeoutError } from './errors';

export function handleApiError(err: unknown): Response {
  if (err instanceof Error && !('sentryReported' in err)) {
    // ์ด๋ฏธ ๋ณด๊ณ ๋œ ์—๋Ÿฌ์ธ์ง€ ํ™•์ธ
    Sentry.captureException(err);
  }

  if (err instanceof RequestValidationError) {
    return zodErrorToResponse(err.zodError);
  }

  if (err instanceof TimeoutError) {
    return Response.json({ success: false, message: 'Upstream timeout' }, { status: 504 });
  }

  if (err instanceof FetchError) {
    return Response.json(
      { success: false, message: err.message || 'Upstream request failed' },	// ๋ฐฑ์—”๋“œ์˜ ์ •ํ™•ํ•œ ์—๋Ÿฌ๋ฉ”์„ธ์ง€ ๋…ธ์ถœ
      { status: err.status ?? 502 }
    );
  }

  if (err instanceof ParseError) {
    return Response.json({ success: false, message: 'Invalid upstream response' }, { status: 502 });
  }

  if (err instanceof ApiError) {
    return Response.json({ success: false, message: err.message }, { status: err.status });
  }

  return Response.json({ success: false, message: 'Internal server error' }, { status: 500 });
}

 

๋˜ํ•œ serverApiClient์—๋Š” ํ—ค๋”์— Authorization๊ณผ ์ฟ ํ‚ค๋ฅผ ์ œ๋Œ€๋กœ ์‹ฌ์—ˆ๋Š”์ง€ ์ฒดํฌํ•˜๊ธฐ ์œ„ํ•ด ์ฝ˜์†”์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

  console.log('[serverApiClient] Authorization:', headers.get('Authorization')); // TODO: ์ถ”ํ›„ ์‚ญ์ œ
  console.log('[serverApiClient] Cookie:', headers.get('Cookie')); // TODO: ์ถ”ํ›„ ์‚ญ์ œ

 

 

์ผ๋‹จ ์•„์‰ฝ๊ฒŒ๋„ Sentry์—์„œ๋Š” ํ˜„์žฌ๊นŒ์ง€ ํ™•์ธํ•œ ๊ฒƒ ์™ธ์˜ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€๋กœ ํ™•์ธํ•˜์ง€๋Š” ๋ชปํ–ˆ๋‹ค.

 

 

2. claude ์‹ฌ๊ธฐ

๋‹ค์Œ์œผ๋กœ vscode์— claude๋ฅผ ์‹ฌ์–ด์„œ ์ฒดํฌ๋ฅผ ํ•ด๋ดค๋‹ค.

I'm getting 401 errors after sending requests. The browser Application tab shows accessToken and refreshToken cookies are present. All request endpoints match the backend configuration.
Please check these folders first: src/apis, app/api, src/lib/server/apiClient.ts, middleware.ts
The backend insists it's a frontend issue. We're using middleware + BFF pattern.
This is a web-only service.

Check if there are any issues in the current code.
In api/member/me/route.ts and api/links/route.ts, the GET requests read cookies directly. This returns HTTP 200 in the response headers, but the actual response body contains an unauthorized error. Other routes using serverApiClient return 401 even in the response headers.
Please suggest a solution.
From Sentry logs, the request to https://api.linkiving.com/v1/links returns 401. The serverApiClient sets both Authorization header and Cookie header before sending to the backend, but the backend logs show "Token not found (missing in both header and cookie)". This suggests the headers are being stripped somewhere between Vercel and the backend server.
We confirmed via Vercel logs that the token exists in the cookie and the Authorization header is correctly set in serverApiClient before the request is sent to the backend. So the issue is not on the Vercel side.

Please answer in Korean.

"ํ”„๋ก ํŠธ ์ธก์—์„œ ํ—ค๋”๊ฐ€ ์„ค์ •๋œ ๊ฒƒ์ด ํ™•์ธ๋˜๋‚˜, ๋ฐฑ์—”๋“œ์—๋Š” ํ—ค๋”๊ฐ€ ์—†๋‹ค"๊ณ  ์ „ํ–ˆ๋‹ค.

์‚ฌ์‹ค ๋ฐฑ์—”๋“œ์—์„œ ์ค‘๊ฐ„์ค‘๊ฐ„ ํ—ค๋” ์—†๋Š” ์š”์ฒญ์„ ๋ฐ›์•˜๋‹ค๊ณ ๋Š” ํ•˜๋‚˜, ๊ฒฐ๋ก ์ ์œผ๋กœ ์‹ค์ œ๋กœ ์š”์ฒญ ์ž์ฒด๋ฅผ ๋ฐ›์ง€ ๋ชปํ–ˆ๋‹ค๊ณ  ํ–ˆ๋‹ค.

(๊ฑ”๋„ค๊ฐ€ ๋ฐ›์€ ์š”์ฒญ์€ ๋ญ์˜€์„๊นŒ)

ํ•˜์ง€๋งŒ ์ผ๋‹จ ์ด๋Ÿฐ ํŒจํ„ด์˜ ์œ ๋ ฅํ•œ ์›์ธ์ด HTTP ๋ฆฌ๋””๋ ‰์…˜์ด๋ผ๊ณ  ํ•œ๋‹ค.

 

serverApiClient์—์„œ fetch๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ 'redirect' ํ”„๋กœํผํ‹ฐ ๊ฐ’๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์ด๋‹ค.

  • follow(default): ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ํ—ˆ์šฉ(30x)ํ•˜๋Š”๋ฐ ๋ณด์•ˆ์ƒ ํ† ํฐ์„ ์ œ๊ฑฐํ•˜๊ณ  ๊ฐ€๋ฒ„๋ฆผ
  • manual: ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š๊ณ  ๋ณด๊ณ ํ•จ
  • error: ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์‘๋‹ต์„ ์—๋Ÿฌ๋กœ ์ฒ˜๋ฆฌ

์ฆ‰, ํ† ํฐ์„ ๋‹ด์•„์„œ fetch ์š”์ฒญ์„ ๋ณด๋‚ธ ๋’ค, 301/302 ์‘๋‹ต์„ ๋ฐ›์•„, node.js fetch๊ฐ€ ํ† ํฐ์„ ๋ฒ„๋ฆฌ๊ณ  ๋ฆฌ๋””๋ ‰์…˜์„ ๋”ฐ๋ผ๊ฐ€์„œ(follow) ํ—ค๋”๊ฐ€ ๋น„์—ˆ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

์ด ๋ถ€๋ถ„ ํ™•์ธ์„ ์œ„ํ•ด์„œ redirect ๊ฐ’์„ 'manual'๋กœ ์ˆ˜์ •ํ•˜๊ณ  ๋ช…์‹œ์ ์ธ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

const response = await fetch(`${API_BASE_URL}${endpoint}`, {
    ...options,
    cache: options.cache ?? 'no-store',
    redirect: 'manual',
    headers,
  });

  // ๋ฆฌ๋””๋ ‰์…˜ ๋ฐœ์ƒ ์‹œ Authorization ํ—ค๋”๊ฐ€ ์ œ๊ฑฐ๋˜๋ฏ€๋กœ ๋ช…์‹œ์ ์œผ๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
  if ([301, 302, 303, 307, 308].includes(response.status)) {
    const rawLocation = response.headers.get('location');
    const safeLocation = rawLocation ? new URL(rawLocation, API_BASE_URL) : null;

    console.error('[serverApiClient] Redirect detected', {
      status: response.status,
      location: safeLocation ? `${safeLocation.origin}${safeLocation.pathname}` : null,
    });
    throw createFetchError('Unexpected redirect from upstream API', { status: 502 });
  }

 

3. ์›์ธ ๋ฐœ๊ฒฌ

์ˆ˜์ • ํ›„ ๋จธ์ง€ํ•ด๋ณด๋‹ˆ ๋“œ๋””์–ด 402์—๋Ÿฌ๋ฅผ ๋ฒ—์–ด๋‚˜์„œ 502์—๋Ÿฌ๋ฅผ ๋ฐ›๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด์„œ ๋ฆฌ๋””๋ ‰์…˜์ด ์‹ค์ œ๋กœ ๋ฐœ์ƒํ–ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

curl -v -L"https://api.linkiving.com/v1/links" \
  -H "Authorization: Bearer ํ† ํฐ๊ฐ’" \
  -H "Content-Type: application/json"

์œ„์™€ ๊ฐ™์€ curl์˜ ๊ฒฐ๊ณผ๋กœ๋Š” 200์„ ๋ฐ›์œผ๋ฏ€๋กœ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ ์ž์ฒด๋Š” ์ •์ƒ์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ redirect๊ฐ€ ๊ฐ์ง€๋œ๋‹ค๋Š” ๊ฒƒ์€

ํ”„๋ก ํŠธ์™€ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ ์‚ฌ์ด Vercel์— ์„ค์ •๋œ env ๋ฌธ์ œ์ผ ์ˆ˜ ์žˆ๋‹ค.

 

์‚ฌ์‹ค ํ˜„์žฌ ๋ฒ„์…€์ด ๋ฌด๋ฃŒ ํ”Œ๋žœ์ด๊ณ , ๋‹ค๋ฅธ ํŒ€์› ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ ํ•ด์„œ ๋‚ด๊ฐ€ ์ง์ ‘ ํ™•์ธ์„ ๋ชปํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

๋งˆ์นจ ํŒ€์›์ด ์•ฝ์†์„ ๋‚˜๊ฐ„๋Œ€์„œ ๊ทธ๋ƒฅ ๋‚ด๊ฐ€ ์ง์ ‘ ๋กœ๊ทธ์ธํ•ด์„œ ํ™•์ธํ•ด๋ณด๊ฒ ๋‹ค ํ•˜๊ณ  ์ด ์นœ๊ตฌ์˜ ๊ณ„์ •์„ ๋ฐ›์•„์„œ ๋กœ๊ทธ์ธ ํ•ด๋ดค๋‹ค.

 


5. Vercel env ๊ฐ’ ํ™•์ธํ•˜๊ธฐ

๊ฒฐ๋ก ์€ ์ข€ ํ—ˆ๋ฌดํ•˜์ง€๋งŒ env์— NEXT_PUBLIC_BASE_API_URL ๊ฐ’์ด ์ž˜๋ชป ์ž…๋ ฅ๋˜์–ด ์žˆ์—ˆ๋‹ค.

https://๋กœ ์‹œ์ž‘ํ•ด์•ผํ•˜๋Š”๋ฐ http://๋กœ ์‹œ์ž‘ํ•˜๊ณ  ์žˆ์—ˆ๋˜ ๊ฒƒ..ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹

 

 

 

 

 


6. ๋ฐฐ์šด ๊ฒƒ

์‚ด์ง ํ—ˆ๋ฌดํ•˜๊ฒŒ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธด ํ–ˆ์ง€๋งŒ ๋งŽ์ด ํ—ค๋ฉ˜๋งŒํผ ๋งŽ์ด ๋ฐฐ์› ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

1. ๋ฆฌ๋””๋ ‰์…˜๊ณผ ํ—ค๋” ํƒˆ๋ฝ

fetch์˜ ๊ธฐ๋ณธ๊ฐ’์ธ 'follow'๋Š” 30X ์‘๋‹ต์—์„œ Authorization, Cookie ํ—ค๋”๋ฅผ ์ž๋™์œผ๋กœ ์ œ๊ฑฐํ•œ๋‹ค.

์ธ์ฆ์ด ํ•„์š”ํ•œ ์š”์ฒญ์—๋Š” redirect: 'manual'๋กœ ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ ์˜ˆ์™ธ ์ƒํ™ฉ์„ ์žก์„ ์ˆ˜ ์žˆ๋‹ค.

 

2. ์—๋Ÿฌ ์ขํ˜€๋‚˜๊ฐ€๊ธฐ

์ด์ „์ฒ˜๋Ÿผ ๋ง‰์—ฐํ•˜๊ฒŒ ์ฝ˜์†”์„ ํ•˜๋‚˜ํ•˜๋‚˜ ์ฐ์–ด๋ดค๋Š”๋ฐ ์ด๋ฒˆ์˜ ๊ฒฝ์šฐ๋Š” ๋ฐฐํฌ๋ฅผ ํ•ด์•ผ ํ™•์ธ์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค.

๊ทธ๋ ‡๊ฒŒ ํ…Œ์ŠคํŠธํ•˜๋Š” ์˜๋ฏธ๊ฐ€ ์—†๋Š” pr์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์˜ฌ๋ฆฌ๊ฒŒ ๋˜์—ˆ๋‹ค.

์ฝ”๋“œ ๊ตฌ์กฐ์™€ ๋กœ์ง์— ๋Œ€ํ•œ ํ™•์‹ ์„ ๊ฐ€์ง„ ์ดํ•ด์™€ Sentry ์‚ฌ์šฉ์˜ ์˜์˜๋ฅผ ์•Œ์•˜๋‹ค๋ฉด ์ด๋ ‡๊ฒŒ ํ—ค๋ฉ”์ง€๋Š” ์•Š์•˜์„ ๊ฒƒ์ด๋‹ค.

์ฒ˜์Œ๋ถ€ํ„ฐ Sentry๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด, ๊ทธ๋ฅผ ํ†ตํ•ด ์ฟ ํ‚ค์— ํ† ํฐ์ด ์žˆ์Œ์„ ํ™•์‹ ํ–ˆ๋‹ค๋ฉด, curl๊ณผ postman์˜ ๊ฒฐ๊ณผ๋ฅผ ํ†ตํ•ด ํ™•์ •์ ์œผ๋กœ ์—๋Ÿฌ ๊ฒฝ๋กœ๋ฅผ ์ขํ˜”๋‹ค๋ฉด ๊ธˆ๋ฐฉ ์ฐพ์„ ์ˆ˜ ์žˆ์—ˆ๋˜ ๋ฌธ์ œ์˜€๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

 

๋‹ค๋งŒ ํ˜„์žฌ ๋ชจ๋‘ ์‹ ์ž…์„ ์ค€๋น„ํ•˜๋Š” ์ค‘์ด์—ˆ๊ธฐ์— ๋กœ์ง๊ณผ ์„ธํŒ…์— ํ™•์‹ ์„ ๊ฐ€์ง€๊ธฐ ์–ด๋ ค์›Œ์„œ ๋” ๋Œ๊ณ  ๋Œ์•˜๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

 

3. ๋ฐฑ์—”๋“œ์™€ ํ”„๋ก ํŠธ ์‚ฌ์ด์˜ ๊ณต์œ 

์šฐ๋ฆฌํŒ€์˜ ๋ฐฑ์—”๋“œ๋Š” ๋ฐฑ์—”๋“œ๋งŒ, ํ”„๋ก ํŠธ์—”๋“œ๋Š” ํ”„๋ก ํŠธ์—”๋“œ๋งŒ ์ฃผ๋กœ ๋‹ค๋ค„์™€์„œ ๊ฐ์ž์˜ ์˜์—ญ์—์„œ ์ฝ”๋“œ์˜ ํ๋ฆ„์„ ์ž˜ ์•Œ์ง€ ๋ชปํ–ˆ๋‹ค.

๋ฌผ๋ก  ์„œ๋กœ์˜ ์ƒํ™ฉ์— ๋Œ€ํ•ด ๋ฌด์กฐ๊ฑด์ ์œผ๋กœ ์ง€์‹์„ ์Œ“๊ธฐ์—๋Š” ๋ฌด๋ฆฌ๊ฐ€ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

 

ํ•˜์ง€๋งŒ ์–ด๋А์ •๋„์˜ ์ง€์‹์€ ์žˆ๋Š” ๊ฒƒ์ด ์ข‹์ง€ ์•Š์„๊นŒ ํ•˜๋Š” ์ƒ๊ฐ์„ ํ–ˆ๋‹ค. ๊ทธ ์–ด๋А์ •๋„๊ฐ€ ์–ผ๋งˆ๋‚˜์ผ์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ..

๋˜ํ•œ BFF์˜ ๊ฒฝ์šฐ ๋‚ด๊ฐ€ ์„ค๋ช…์„ ์ œ๋Œ€๋กœ ํ•˜์ง€ ๋ชปํ•œ ํƒ“๋„ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๋ถ€๋ถ„์ด๊ธฐ์—..

๋‚ด๊ฐ€ ๋ฐฑ์—”๋“œ์˜ ๊ตฌ์กฐ๋ฅผ ์•„๋Š” ๊ฒƒ์€ ๋‘˜์งธ์น˜๊ณ , ๋‹ด๋‹น์ž๋กœ์„œ ์ด๋Ÿฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ๊ฒ ๋‹ค๋Š” ๊ฒƒ์„ ์ธ์ง€ํ–ˆ๋‹ค.

 

4. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฒดํฌ

docker๋ฅผ ๋„์ž…ํ•˜๋ฉด์„œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ๋ฒˆ ๋ฐ”๋€Œ๊ฒŒ ๋˜์—ˆ๋‹ค. ์ €๊ฒŒ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜๋ฉด์„œ๋„ ๊ธฐ์กด์˜ env๋กœ๋Š” ์ฑ„ํŒ…์ด ์•ˆ๋˜๊ฑฐ๋‚˜ ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ž˜์„œ ์ด๋ฒˆ์— ๋ฌธ์ œ๊ฐ€ ๋˜์—ˆ๋˜ PUBLIC_BASE_API_URL๊ฐ€ ๋กœ์ปฌ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š” ์ค‘์—์„œ๋„ ํ•„์š”์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๊ฐ’์„ ํ™œ์šฉํ•˜๊ธฐ๋„ ํ–ˆ๋Š”๋ฐ,

์ด๋ฒˆ์— ๊ฒฐ๊ตญ ํ•ต์‹ฌ ๋ฌธ์ œ๊ฐ€ ๋˜์—ˆ์—ˆ๋‹ค.

http์™€ https์˜ ์ฐจ์ด๋กœ ๋‹จ์ˆœ ์˜คํƒ€์ผ ์ˆ˜๋„ ์žˆ์ง€๋งŒ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ฐ๊ฐ์˜ ์—ญํ• ๊ณผ ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—์„œ ๊ฐ€์ง€๋Š” ์˜๋ฏธ๋ฅผ ํŒŒ์•…ํ•˜๊ณ  ์žˆ์—ˆ์œผ๋ฉด ๋” ์ข‹์•˜์„ ๊ฒƒ ๊ฐ™๋‹ค.

๋˜ํ•œ ํŒ€์› ๊ฐ„ ์ •ํ™•ํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ์ •์˜ํ•ด๋‘๊ณ  ๋ฌด์กฐ๊ฑด ํ•ด๋‹น ๋‚ด์šฉ์„ ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๋ฐฉ๋ฒ•์ด ๋  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

5. Vercel๊ณผ ๊ฐ™์€ ๋„๊ตฌ์˜ ๊ณต์œ 

์‚ฌ์‹ค ์ด๋ถ€๋ถ„์€ ํ˜„์žฌ๋กœ์จ๋Š” ์–ด์ฉ” ์ˆ˜ ์—†๋Š” ์ƒํ™ฉ์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋‚ด๊ฐ€ ์ด๋ฒˆ์— ์ถ”๊ฐ€ํ•œ Sentry๋„ ํŒ€์› ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ํ”Œ๋žœ์„ ์—…๊ทธ๋ ˆ์ด๋“œ ํ•ด์•ผํ•˜๋Š”๋ฐ, ๋‹น์žฅ ์ˆ˜์ต์„ ๋‚ด๋Š” ์„œ๋น„์Šค ๊ฐœ๋ฐœ์„ ๋ชฉํ‘œ๋กœ ํ•˜๊ธฐ๋ณด๋‹ค๋Š”

๋‹จ์ˆœ ์™„์„ฑ์„ ๋ชฉํ‘œ๋กœ ํ•˜๊ณ  ์žˆ๋Š” ์„œ๋น„์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด์ž‘์ • ๊ทธ๋Ÿฐ ๊ตฌ๋…๋“ค์„ ํ•  ์ˆ˜๊ฐ€ ์—†์—ˆ๋‹ค.

๊ฒŒ๋‹ค๊ฐ€ ์นœ๋ถ„์ด ์žˆ๋Š” ์‚ฌ์ด์–ด์„œ ์„œ๋กœ ํ™•์ธ์„ ์ž˜ ํ•˜๋ฉด ๋œ๋‹ค๋Š” ์•ˆ์ผํ•จ๋„ ์žˆ์—ˆ๋‹ค.

 

์ด๋ฒˆ ๊ฒฝ์šฐ๋„ ๋‚ด๊ฐ€ vercel์— ๋Œ€ํ•ด ์ž˜ ์ดํ•ดํ•˜๊ณ  "์ด ๋ถ€๋ถ„ ์บก์ฒ˜๋ฅผ ๋ณด์—ฌ๋‹ฌ๋ผ"๋Š” ์‹์œผ๋กœ ๊ณต์œ ๋ฅผ ๋น ๋ฅด๊ฒŒ ํ–ˆ์—ˆ์œผ๋ฉด ์˜คํƒ€๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์—ˆ์„ ํ…๋ฐ ๋ง‰์—ฐํ•˜๊ฒŒ ์ž˜ ๋๊ฒ ์ง€ํ•˜๊ณ  ์ƒ๊ฐํ–ˆ๋˜ ๊ฒƒ๋„ ์‹ค์ˆ˜์˜€๋‹ค.

 

๋ฐฑ์—”๋“œ ์ƒํ™ฉ์€ ์ •๋ง ๋ณผ ์ˆ˜๊ฐ€ ์—†์œผ๋‹ˆ๊นŒ ์บก์ฒ˜๋ณธ์„ ๊ทธ๋ ‡๊ฒŒ ์ž˜๋งŒ ์š”๊ตฌํ•ด๋†“๊ณ  ์ •์ž‘ ๋” ๊ฐ€๊นŒ์ด์— ์žˆ๋˜ Vercel ์ฒดํฌ๋ฅผ ๋ชปํ–ˆ๋‹ค๋‹ˆ..

 

์—๋Ÿฌ๋Š” "๋˜๊ฒ ์ง€"๊ฐ™์ด ์ƒ๊ฐํ•˜๋ฉด์„œ๋Š” ์žก์„ ์ˆ˜๊ฐ€ ์—†๋‹ค. ํ˜„์žฌ๋„ ์—๋Ÿฌ๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด ์ž‘์„ฑํ•œ ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ/์ฝ˜์†” ์ฝ”๋“œ๋“ค์„ ๋‹ค์‹œ ๋งŒ์ ธ์•ผํ•œ๋‹ค.

๋‹ค์–‘ํ•œ ํŒจํ„ด์„ ํ™•์‹คํ•˜๊ฒŒ ๋จธ๋ฆฟ์†์— ๋„ฃ์–ด๋‘๋Š” ๊ฒŒ ์ค‘์š”ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

 

6. ai ๋„๊ตฌ์˜ ํ™œ์šฉ

์•„ ์ถ”๊ฐ€๋กœ ai ๋„๊ตฌ ํ™œ์šฉ์— ๋Œ€ํ•ด ๋‹ค์‹œ ๊ณ ๋ คํ•˜๊ฒŒ๋˜์—ˆ๋‹ค. claude๋ฅผ ์ง€๊ธˆ๊นŒ์ง€ ๋ธŒ๋ผ์šฐ์ €๋กœ๋งŒ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ ํ˜„์žฌ ์ ์šฉํ•œ BFF ๋ฐ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ๊ตฌ์กฐ์— ๋ญ”๊ฐ€ ์‚๋—ํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒŒ ์•„๋‹๊นŒ ํ•˜์—ฌ vscode์— claude๋ฅผ ์„ค์น˜ํ•˜๊ณ  ์‚ฌ์šฉํ•ด๋ดค๋Š”๋ฐ ๋ฐ”๋กœ ๊ธธ์ด ๋‚˜์™”๋‹ค. ์ด ๋ถ€๋ถ„์€ ๊ตฌ์กฐ์˜ ๋ฌธ์ œ๋ผ๊ธฐ๋ณด๋‹ค๋Š” ํ•œ ํŒŒ์ผ์—์„œ ๋ฏธ์ฒ˜ ์žก์ง€ ๋ชปํ•œ ์—๋Ÿฌ ๊ฒฝ๋กœ๊ธด ํ–ˆ์ง€๋งŒ.. ๊ทธ๋ž˜๋„ ํ•œ ๋ฒˆ ๋น ๋ฅด๊ฒŒ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด๋ณด๋‹ˆ ๋ฏธ๋ฃจ๋˜ ai ๋„๊ตฌ ๋„์ž…(md ํŒŒ์ผ ์ž‘์„ฑ์ด๋ผ๋˜์ง€)์„ ์ •๋ง๋กœ ํ•˜๊ธด ํ•ด์•ผ๊ฒ ๋‹ค๊ณ  ๋А๊ผˆ๋‹ค.

๋˜ํ•œ ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•˜๋ฉด์„œ ์งˆ๋ฌธ์˜ ๋พฐ์กฑํ•จ, ์ƒํ™ฉ ์ •๋ฆฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ๋„ ์—ญ์‹œ ์ค‘์š”ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋А๊ผˆ๋‹ค. ๋˜ ์˜์–ด๋กœ ์งˆ๋ฌธํ•˜๋Š” ๊ฒƒ๋„ ๊ดœ์ฐฎ์€ ๊ฒƒ ๊ฐ™๋‹ค.

 

 

 

 

์ฃผ์ €๋ฆฌ์ฃผ์ €๋ฆฌ ๋ง์ด ๋งŽ์•˜์ง€๋งŒ.. ์˜ค๋ž˜๊ฑธ๋ ธ๋˜ ์—๋Ÿฌ ํ•ด๊ฒฐ!

 

 

 

 

 

์ฐธ๊ณ 

๋”๋ณด๊ธฐ

์ฐธ๊ณ ์ž๋ฃŒ๋งํฌ

๋ฐ˜์‘ํ˜•