web
next_js
error

2025-02-13

25초의 로딩

일단 내 프로젝트는 총 4개의 server component에서 ‘calendar-entry’라는 client component를 렌더링하고 거기서 ‘bookmark-button’이라는 client component를 렌더링하고 있다. 북마크 버튼인데 사용자가 클릭해서 strapi cms의 calendar에다 POST를 해서 북마크를 추가하고 삭제하는 컴포넌트다. 현재 로그인한 사용자가 그 일정을 북마크 했느냐 안했느냐에 따라 아이콘과 기능이 달라진다.

그럴려면 현재 유저 데이터를 fetch해야 하겠지. 그 fetch를 bookmark-button에서 하고 있었다. 근데 수십개의 calendar-entry에 북마크버튼이 하나씩 있으니까 캘린더가 들어있는 페이지를 로딩할려면 수십개의 fetch가 북마크버튼에서 이뤄지는 거다. 그래서 로딩이 25초나 걸렸다.

Refactor fetchUser()

유저 정보를 가져오는 비슷비슷한 함수가 가장 끝단인 북마크버튼과 그 위의 컴포넌트들 곳곳에 있었다.

useEffect(() => {
	async function fetchUser() {
		try {
			const res = await fetch("/api/auth/me");
			const text = await res.text();
			const data = JSON.parse(text);
			setUserData(data.data || null);
		} catch(error) {
			console.log("Failed to fetch user:", error);
		}
	}
	fetchUser();
}, [])

저걸 get-auth-me라는 파일로 분리했다. 그리고 북마크버튼을 가지고 있는 4개의 서버컴포넌트에서 call했다. 그리고 자식 클라이언트 컴포넌트에 prop으로 보내고, 거기서 setUserData를 해서 쓰고 계속 prop으로 보내서 북마크버튼에까지 보냈다.

// .../page.tsx
import { getAuthToken } from '@/app/lib/services/get-token';
import { fetchUser } from '@/app/lib/get-auth-me';
...
const token = await getAuthToken();
const user = await fetchUser();
...
<Calendar
	key={calendar.id}
	calendar={calendar}
	token={token ?? undefined}
	user={user}
/>

/api/auth/me 라우트는 내가 만든 건데 쿠키를 가져와서 헤더에 포함해서 strapi api users/me에서 현재 로그인한 유저 정보를 가져오는 거였다.

import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET() {
	const cookieStore = await cookies();
	const token = cookieStore.get("jwt")?.value || null;

	try {
		const res = await fetch("https://{...}/api/users/me", {
			headers: {
				Authorization: `Bearer &{token}`,
			},
		});
		const data = await res.json();
		return NextResponse.json({ username, data });
	} catch(error) {
		return NextResponse.json({ error }, { status: 500 });
	}
}

위의 리팩터링을 하기 전에는 잘 동작했었다.

api에서 토큰을 갑자기 못 받아온다

근데 에러가 떴다. 먼저 url을 /api/auth/me라고만 써놓은게 서버컴포넌트에서 call해서 그런지 parse를 못했다고 에러가 떠서 .env.local 이랑 .env.production 만들어서 각각 로컬호스트랑 deploy된 도메인 base url 넣어서 링크 문제를 해결했다.

그다음엔 401 unauthorize 에러가 떴다. 쿠키에 토큰 있는데도. 에러가 어디서 나는지 console.log하면서 찾아보니까 원인은 api/auth/me/route.ts에서 토큰이 null, undefined로 나와서였다. 원래 토큰을 잘 받아오던 route였는데 왜 갑자기 이럴까 생각해봤다. 달라진 건 그 route를 사용하는 컴포넌트들 뿐이었다.

서버컴포넌트에서 자기 도메인의 api route를 사용하면 토큰을 못 꺼내오는 걸까? 확실한 답을 찾지는 못했지만 인터넷에서 ‘consuming your own route handlers in a server component is anti-pattern’라는 말을 봤다. 그냥.. 서버컴포넌트에서는 원래 외부 api랑 뭔갈 하기 쉽기 때문에 굳이 api 레이어를 추가할 필요가 없는건 사실인 것 같다.

그래서 /api/auth/me를 없애버리고 거기서 하던 쿠키받아와서 strapi api fetch하는걸 get-auth-me.ts로 가져왔더니 해결됐다.

결과

결과적으로 fetch를 수십개의 클라이언트 컴포넌트가 아닌 최상위 서버컴포넌트 4개에서만 하고 props로 클라이언트에 넘겨주니까 fetch를 페이지가 로딩될 때만 한번 하면 된다. 로딩 속도가 25초에서 1초로 확 줄어들었다.