개발자
안녕하세요. 혼자서 해결해 보려고 했지만, 오랜 시간 해결하지 못해 지푸라기라도 잡는 심정으로 질문 올려봅니다.. 기존에 localstorage를 사용해 fetch로 데이터를 요청했으나 SSR 과정에서 window 사용이 불가능하기 때문에 쿠키를 사용을 계획했습니다. 로그인 -> 쿠키에 토큰값 저장 -> 데이터 요청 시 쿠키에서 토큰 값을 꺼내고 헤더 Authorization에 담아 SSR에서도 사용하려고 했습니다. next에 내장되어있는 'next/headers'가 아닌 'cookies-next'를 사용하고 있습니다. <문제 상황> 처음에는 토큰 유효 기간이 지났다(토큰 값이 들어오지 않았다)는 에러가 발생하고 곧이어 데이터를 정상적으로 받아옵니다 (제 추측으로는 SSR 과정에서 토큰 값을 인식하지 못하고 에러가 발생하고 클라이언트 단에서는 쿠키 값을 정상적으로 반영해 데이터 페칭이 진행된 것 같습니다.) <질문> 1. Suspense를 적용하지 않을 경우 에러 없이 동작하지만 next streaming이 적용되지 않습니다.. 그리고 getCookie를 통해 가져온 값이 SSR 시 적용되지 않는 이유가 궁금합니다! 2. 현재 쿠키를 사용해 SSR 시 토큰 값을 전달하려는 방법이 최선의 방법이 맞는지 궁금합니다.
1'use client'
2
3import React, { useEffect } from 'react'
4
5import { setCookie } from 'cookies-next'
6import useGetSearchParam from '../_hook/common/useGetSearchParams'
7
8export function GetSearchParams() {
9 const token = useGetSearchParam('accessToken')
10
11 useEffect(() => {
12 if (token) {
13 setCookie('accessToken', token)
14 }
15 }, [token])
16
17 return <div>쿠키 저장 임시 로직</div>
18}
19
20
21==========================
22
23import { useMemo } from 'react'
24import {
25 InfiniteData,
26 useSuspenseInfiniteQuery,
27} from '@tanstack/react-query'
28
29import { PagesResponse, SortOption } from '@/app/_types/review.type'
30
31import { getCookie } from 'cookies-next'
32
33interface ReviewQueryParams {
34 pageParam: string | null
35 itemId: number
36 sortOption: SortOption
37}
38
39async function fetchReviewData({
40 pageParam,
41 itemId,
42 sortOption,
43}: ReviewQueryParams) {
44 const accessToken = getCookie('accessToken') ⭐️
45
46 /** 기본 3개 - 추가 10개 */
47 const REVIEW_DATA_SIZE = !pageParam ? 3 : 10
48
49 const res = await fetch(
50 `${process.env.NEXT_PUBLIC_BASE_URL}`,
51 {
52 method: 'GET',
53 headers: {
54 'Content-Type': 'application/json',
55 Authorization: `Bearer ${accessToken}`, ⭐️
56 },
57 cache: 'no-store',
58 },
59 )
60
61 const data = await res.json()
62
63 if (!res.ok) {
64 throw new Error(data.message)
65 }
66
67 return data
68}
69
70... useSuspenseInfiniteQuery를 사용한 react query 코드
답변 1
지금 토큰을 쿠키에 설정하는 부분이 클라이언트 측인데 이렇게 해주는 이유가 있을까요? 일반적으로 로그인 로직은 사용자 아이디 비번 입력 -> 요청 -> 검증 -> 토큰 생성 -> 응답 간단히 이런 식으로 돼서 서버에서 응답헤더에 쿠키를 설정해 쿠키에 토큰을 담습니다. 현재 오류가 나는 부분도 클라이언트 측에서 쿠키를 설정하는 부분 때문에 발생하는 거 같아요. 쿠키를 설정하는 컴포넌트는 클라이언트 컴포넌트이고 로그인 요청을 보내는 부분은 서버 컴포넌트이기 때문에 쿠키를 설정하기도 전에 요청이 가버려서 에러가 발생하는 거 같습니다.
박하민
작성자
취준생 • 2024년 02월 09일
안녕하세요! 답변 감사합니다. 현재 소셜 로그인 시 쿼리 파라미터를 사용해서 토큰 값을 저장하고 리뷰 목록을 조회하기 위해 백엔드 서버에 데이터 요청할 때 Authorization: `Bearer ${accessToken}`으로 토큰을 담아서 보내야 하는 상황입니다. 'next/headers'를 사용해서 따로 util 파일을 만들었을 때에도 에러가 발생해서 cookies-'next'를 사용하게 되었습니다. 쿠키를 사용한 이유는 서버에서 쿼리 파라미터의 값을 저장하고(localstorage 사용 불가) API 요청 시 사용하는 목적으로 사용을 계획하고 있었습니다! 그런데 위의 코드가 SSR 시 쿠키에서 꺼낸 값을 인식하지 못하는 것 같습니다..
김태우
Founding Engineer • 2024년 02월 09일
소셜 로그인이라면 리다이렉트 시에 access 토큰 말고 인가코드를 쿼리 파라미터로 받고 그 인가코드를 서버로 넘겨줘서 서버에서 소셜로그인 측으로 access 토큰을 받는 방식을 주로 사용하는 것으로 알고 있는데 어떤 소셜 로그인인가요?
김태우
Founding Engineer • 2024년 02월 09일
OAuth 프로토콜 방식에서 클라이언트에서 직접 access 토큰을 받게 되면 브라우저 URI에 바로 노출되는 문제가 있어 위와 같은 방식으로 인가 코드를 받고 바로 노출되지 않게 서버에서 인가 코드를 통해 access 토큰을 받는 방식을 사용합니다!
박하민
작성자
취준생 • 2024년 02월 09일
현재 카카오 소셜로그인을 사용하고 있습니다! 백엔드에서 쿼리 파라미터 방식을 제안하셔서 말씀해 주신 방법은 사용하지 않고 있는 상태입니다!
김태우
Founding Engineer • 2024년 02월 09일
만약 저 방법대로 하신다면 Next.js 서버 컴포넌트에서도 쿼리 마라미터를 Page Props로 받을 수 있습니다. Props 객체 안에 searchParams라는 속성이 있습니다. 따라서 데이터 페칭하는 함수에서 이 값을 매개변수로 받도록 하면 따로 브라우저 저장소에 저장하지 않아도 될 거 같습니다!
김태우
Founding Engineer • 2024년 02월 09일
리다이렉트 된 uri에 access_token 키 값으로 저장되어 있을 텐데 export default async function Page({searchParams}) const datafetching = getData(searchParams.access_token); 이런 방식으로 해보시는 건 어떨까요?
김태우
Founding Engineer • 2024년 02월 09일
이렇게 되면 처음 로그인할 때 서버에서 쿠키에 토큰을 저장하고 리뷰 데이터를 받을 때에도 따로 클라이언트 측에서 처리하지 않고 서버에서 쿠키에 토큰이 있는지 없는지 확인 후 데이터를 내려주는 방식이 될 거 같습니다.
김태우
Founding Engineer • 2024년 02월 09일
만약 액세스토큰을 꼭 클라이언트에서 쿠키에 저장하고 싶으시면 14버전부터 클라이언트에서 서버 액션을 할 수 있습니다. 서버 액션을 하는 함수를 따로 빼서 페이지에서 받아온 쿼리 파라미터를 넘겨주시는 방법도 있을 거 같네요. 다음은 해당 내용에 대한 공식문서 내용입니다. https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#cookies
박하민
작성자
취준생 • 2024년 02월 09일
좋은 정보 감사합니다! 최대한 반영해 보려고 했으나 마음처럼 잘 안되네요.. 말씀해 주신 부분처럼 searchParams를 사용했을 때 해당 페이지에서 uri에 있는 accessToken을 저장할 수 있지만, 다른 페이지에 이동해서 accessToken을 사용하기 위해서 localstorage와 쿠키에 값을 저장하려고 했습니다. 클라이언트 컴포넌트에서 현재 코드를 호출해서 사용하고 있었습니다. react-query 로직이 포함되어 있어 'use server' 키워드를 사용할 수 없었고 'next/headers'를 따로 util 파일로 만들어 호출해서 사용하게 되었습니다. 쿠키를 꺼내서 Bearer 토큰에 값을 포함해 사용해 보려고 했으나 다른 에러가 발생하게 되네요.. 😭 Uncaught Error: Server Functions cannot be called during initial render. This would create a fetch waterfall. Try to use a Server Component to pass data to Client Components instead. Cannot update a component (`Router`) while rendering a different component (`ReviewSection`). To locate the bad setState() call inside `ReviewSection`, follow the stack trace as described in 에러를 살펴보니, const accessToken = getCookie('accessToken') 유틸 파일에서 호출한 next/headers 쿠키 값을 꺼내는 과정에서 에러가 발생하는 것 같습니다. 이 부분을 해결하기 위해 노력해 봐야겠네요..!
지금 가입하면 모든 질문의 답변을 볼 수 있어요!
현직자들의 명쾌한 답변을 얻을 수 있어요.
이미 회원이신가요?
커리어리 AI 봇
GPT가 달아주는 답변입니다.
2024년 02월 08일
본문을 보면 사용하시는 'cookies-next' 라이브러리가 클라이언트 사이드에서 동작하는 것으로 추측됩니다. 따라서 요청이 서버에서 발생할 때 쿠키를 가져올 방법이 없어, 처음에는 토큰 값이 없다고 보고 에러를 반환한 후, 클라이언트 사이드로 왔을 때 정상적으로 토큰 값을 가져와 데이터를 받아오는 것으로 보입니다. 1번 질문에 대한 답변: SSR 시간에 localStorage나 cookies 등의 클라이언트 측 코드를 실행하려고 하면 window or document is not defined 등의 에러 메시지가 나올 수 있습니다. 이럴 경우 해당 코드가 서버에서 실행되지 않도록 조건부 처리해야 합니다. 2번 질문에 대한 답변: 보안을 위해 access token은 httpOnly 쿠키에 저장하는 것이 좋으며 SSR에서도 쿠키 자체가 요청과 함께 전송되기 때문에 가장 안전합니다. 그러나 이 경우 서버사이드에서 쿠키 파싱을 해야하는 추가 작업이 필요합니다. 따라서 getInitialProps나 getServerSideProps 같은 Next.js 의 데이터 패칭 메소드 안에서 ctx.req.headers.cookie 를 접근해 쿠기값을 얻어낼 수 있습니다. ```typescript InfiniteData.getInitialProps = async (ctx) => { const token = ctx.req.headers.cookie.split('; ').find(c => c.startsWith('token=')).split('=')[1]; } ``` 이렇게 해서 얻어진 토큰값을 fetch 함수에 넘겨주면 됩니다. ```typescript const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}`, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, cache: 'no-store', }) ``` 다만, 위 예제에서는 모든 쿠키가 ;와 공백으로 분리되고 각 쿠키가 key=value 형태라는 가정하에 작성된 코드입니다. 실제 상황에 맞게 수정하거나 적절한 라이브러리를 사용하여 파싱해주시면 좋습니다.
지금 가입하면 모든 질문의 답변을 볼 수 있어요!