TIL

2024.05.25 TIL #Next.js_app_router #Suspense #pre-render #ํŒ€ํšŒ์˜

inz1234 2024. 5. 25. 23:49

๐Ÿšจ  TroubleShotting

Query data cannot be undefined

๋ผ๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

๋ฌธ์ œ ๋ฐœ์ƒ์˜ ๋ฐฐ๊ฒฝ - As Is

1. Next.js์˜ 14๋ฒ„์ „ App-router๋ฅผ ์‚ฌ์šฉ ์ค‘์ด๋‹ค.

2. src > app > chat > page.tsx ํŒŒ์ผ์—์„œ SideBar.tsx๋ผ๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ import ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

import SideBar from '@/components/chat/sidebar/SideBar';

const ChatPage = async ({ params }: { params: { chatroom_id: string } }) => {
  ...
  return (
    <main>
      <HydrationBoundary state={dehydrate(queryClient)}>
        <Suspense fallback={<ChatLoading />}>
          ...
              <section className="lg:flex lg:max-w-96 max-sm:absolute max-sm:z-40 max-sm:bg-white min-h-[36rem] ">
                <SideBar chatRoomId={chatRoomId} /> ---> ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ
              </section>
          ...

3. SideBar.tsx(ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ - ์ดํ•˜ ์ƒ๋žต) ์•ˆ์—์„œ๋Š” useSuspenseQuery๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๊ณ , ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ChatPage.tsx์—์„œ SideBar.tsx๋ฅผ <Suspense>๋กœ ๊ฐ์‹ธ๊ณ  ์žˆ์—ˆ๋‹ค. 

4. ๊ทธ๋Ÿฌ๊ณ  localhost:3000์—์„œ ChatPage.tsx์—์„œ ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜๋ฉด ์œ„์— ์˜ค๋ฅ˜๊ฐ€ ๋–ด๋‹ค.

5. ๊ทผ๋ฐ ์ด์ œ ๋ฌธ์ œ๋Š” ์ € ์˜ค๋ฅ˜๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์—๋Š” ๋œจ์ง€ ์•Š๊ณ  ์„œ๋ฒ„ ์ธก ํ„ฐ๋ฏธ๋„์— ๋–ด๋‹ค๋Š” ๊ฒƒ๊ณผ, ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์—๋Š” ์•„๋ฌด ์˜ค๋ฅ˜๊ฐ€ ๋œจ์ง€ ์•Š์•˜๊ณ  ๋ฐ์ดํ„ฐ๋„ ์ž˜ ๋ฐ›์•„์™€์ ธ์„œ ๋ Œ๋”๋ง๋„ ์ž˜ ๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

์—ฌ๊ธฐ์„œ ์˜๋ฌธ
SideBar.tsx๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์ด๊ณ ,
React-query๋„ ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ๋ฐ
์™œ ์„œ๋ฒ„ ์ธก ํ„ฐ๋ฏธ๋„์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋Š”๊ฐ€?
ํ„ฐ๋ฏธ๋„์—” ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋Š”๋ฐ ๋ธŒ๋ผ์šฐ์ €์—๋Š”
์™œ ์˜ค๋ฅ˜ ์—†์ด ๋ฐ์ดํ„ฐ fetching์ด ์ž˜ ๋˜๋Š”๊ฐ€?

How(๊ณผ์ •) ?

2๊ฐœ์˜ ์›์ธ๊ฐ€์„ค์„ ์„ธ์›Œ๋ณด์•˜๋‹ค.

 

์›์ธ๊ฐ€์„ค1

 Next.js์˜ App-router์—์„œ๋Š” Client ์ปดํฌ๋„ŒํŠธ์—ฌ๋„ server ์ธก์—์„œ pre-render ๊ณผ์ •์ด ์ด๋ฃจ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์—

 

Rendering: Client Components | Next.js

Learn how to use Client Components to render parts of your application on the client.

nextjs.org

๊ทธ ๊ณผ์ •์—์„œ useSuspenseQuery๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค!

 

๊ทธ๋Ÿผ ์—ฌ๊ธฐ์— ๋Œ€ํ•œ ๋˜ ์˜๋ฌธ์ 
(1) 'use client'๋ผ๊ณ  ํ•ด๋‘” Client ์ปดํฌ๋„ŒํŠธ๊ฐ€ pre-render๋กœ ์ธํ•ด์„œ ๊ทธ ๋กœ์ง๋“ค์ด server์—์„œ ๋ฏธ๋ฆฌ ์‹คํ–‰์ด ๋œ๋‹ค๋ฉด, useSuspenseQuery ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ useState๋‚˜ useEffect ๊ฐ™์€ React hook๋“ค๋„ ์‹คํ–‰๋˜์–ด์•ผ ํ• ํ…๋ฐ ์™œ ๊ฑ”๋„ค๋“ค์— ๋Œ€ํ•œ ์˜ค๋ฅ˜๋Š” ํ„ฐ๋ฏธ๋„์— ์ฐํžˆ์ง€ ์•Š๋Š”์ง€?

 

(2) ๊ทธ๋ž˜ ํ”„๋ฆฌ๋ Œ๋”๋ง์ด ์›์ธ์ด๋ผ๊ณ  ์น˜์ž. ๊ทธ๋Ÿผ ์–ด์จŒ๋“  ์„œ๋ฒ„ ์ชฝ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚œ๋‹ค๋Š” ๊ฒƒ์€ ๋นŒ๋“œ ์‹œ์—๋„ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์•ผ ํ• ํ…๋ฐ ์™œ ๋นŒ๋“œ ์‹œ์—๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์ง€ ์•Š๋Š”์ง€?

 

(3) ํ”„๋ฆฌ๋ Œ๋”๋ง์ด ๋˜๋Š” ์ข‹์€๋ฐ, pre-๋ Œ๋”๋ง์ด ๋ง ๊ทธ๋Œ€๋กœ "๋ฏธ๋ฆฌ ์„œ๋ฒ„ ์ธก์—์„œ ๋ Œ๋”๋ง" ๋˜๋Š” ๊ฑฐ๋ผ๋ฉด ์™œ ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•œ๋ฒˆ ๋” ๋ Œ๋”๋ง์ด ๋˜์–ด useSuspenseQuery๊ฐ€ ์‹คํ–‰๋˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š”์ง€? ์ฆ‰, ํ•œ๋ฒˆ pre-๋ Œ๋”๋ง์œผ๋กœ ์‹คํ–‰๋์œผ๋ฉด ๋˜์ง€ ์™œ ํด๋ผ์ด์–ธํŠธ ์ธก์— ํ•œ๋ฒˆ ๋” ์‹คํ–‰๋˜๋Š”์ง€?

 

์›์ธ๊ฐ€์„ค 2

์•„๋ƒ ์ด๊ฑฐ ํ”„๋ฆฌ๋ Œ๋”๋ง ๋•Œ๋ฌธ ์•„๋‹Œ ๊ฒƒ ๊ฐ™์•„. ์ด๊ฑฐ Suspense ๋•Œ๋ฌธ์ธ ๊ฒƒ ๊ฐ™์•„.

์™œ๋ƒ๋ฉด ๋‹ค๋ฅธ ๊ณณ์—์„œ๋Š” useSuspenseQuery ๋ง๊ณ  useQuery ์ผ๋Š”๋ฐ ์ด๋Ÿฐ ์˜ค๋ฅ˜ ์•ˆ ๋‚˜. 

์ด๊ฑฐ๋ด. ์—ฌ๊ธฐ์„œ ๋งํ•˜๊ธธ Suspense์™€ SSR์ด ๋งŒ๋‚˜๋ฉด ๋‚˜๋Š” Next.js์˜ ๋ฒ„๊ทธ๋ž˜

 

 

SSR ํ™˜๊ฒฝ์—์„œ Suspense์™€ useSuspenseQuery๊ฐ€ ์˜ค์ž‘๋™ํ•˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

๊ณตํ†ต์ ์„ ํ†ตํ•ด ๋ฌธ์ œ ๋ฐœ์ƒ ์›์ธ ํƒ์ƒ‰ํ•˜๊ธฐ

velog.io

 

์ด๊ฒƒ์— ๋Œ€ํ•œ ๋˜ ์˜๋ฌธ์ 

  (1) Suspense์™€ SSR์ด ๋งŒ๋‚˜๋ฉด ์ •ํ™•ํžˆ ๋ญ ๋•Œ๋ฌธ์— ๊ทธ๋Ÿฐ๊ฑด์ง€?

 

  (2) ์˜ค์ผ€์ด ํ”„๋ฆฌ๋ Œ๋”๋ง ์•„๋‹ˆ๋ผ๊ณ  ์น˜์ž. ๊ทธ๋Ÿผ Suspense๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ฒ˜์Œ์— undefined๋ฅผ ๋ฐ˜ํ™˜ํ•ด์„œ ์˜ค๋ฅ˜๋ฅผ ๋ฑ‰์—ˆ์œผ๋ฉด ๊ทธ๊ฑธ๋กœ ๋์ด์ง€ ์ด๊ฒƒ๋„ ์™œ ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•œ๋ฒˆ ๋” ์‹คํ–‰๋˜๋Š”์ง€?


Why(์ด์œ ) ?

๊ฒฐ๊ตญ ์ด์œ ๋Š” ๋‘ ๊ฐ€์„ค์˜ ์งฌ๋ฝ•์ด์—ˆ๋‹ค.

Suspense๊ฐ€ pre-render์˜ ๋Œ€์ƒ์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฆ‰, SSR-Streaming์˜ ๋Œ€์ƒ์ด ๋œ๋‹ค.

์œ„ ๋งํฌ์˜ Example ๋ถ€๋ถ„์— By using Suspense, you get the benefits of: 1.  Streaming Server Rendering ์— ๋ณด๋ฉด

Suspense๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์„œ๋ฒ„์—์„œ rendering ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋‚˜์™€์žˆ๋‹ค.

 

(์—ฌ๊ธฐ์„œ ์›์ธ๊ฐ€์„ค1์˜ (1)์˜ ๋‹ต์ด ๋‚˜์˜จ๋‹ค. pre-render๊ฐ€ ๋˜๋Š” ๋Œ€์ƒ์€ ๋”ฐ๋กœ ์žˆ๋‹ค. Suspense๋Š” ๊ทธ pre-render์˜ ๋Œ€์ƒ, ์ฆ‰ ์„œ๋ฒ„ ์ธก ์ฝ”๋“œ๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์— react-hook ๊ฐ™์€ client ์ฝ”๋“œ์™€ ๋‹ค๋ฅด๊ฒŒ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋œ๋‹ค.)

 

Suspense๋Š” useSuspenseQuery ๋‚ด queryFn๊ฐ€ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” Promise ๋ฐ˜ํ™˜๊ฐ’์„ ์บ์น˜ํ•ด์„œ, ๊ทธ ๊ฐ’์ด pending์ด๋ฉด streamig์„ ๊ธฐ๋‹ค๋ ค์ฃผ๊ณ , resolve๋ฉด Suspense ๋ฐ”์šด๋”๋ฆฌ ์•ˆ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง ํ•ด์ค€๋‹ค. 

 

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„ ์ธก์—์„œ Suspense๋ฅผ ์œ„ํ•œ useSuspenseQuery๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด์—ˆ๊ณ , ๋ฌธ์ œ๋Š” useSuspenseQuery์˜ queryFn(fetchChatData) ๋‚ด์—์„œ ์„œ๋ฒ„ ์ธก์—์„œ ์‹คํ–‰๋  ์‹œ์ ์— clientSupabase๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด๋‹ค. 

export const fetchChatData = async (chatRoomId: string) => {
  console.log('aaaa', chatRoomId);
  const { data: chatData, error: chatDataErr } = await clientSupabase ---> ํด๋ผ์ด์–ธํŠธ ํ•จ์ˆ˜
    .from('chatting_room')
    .select('*')
    .eq('chatting_room_id', chatRoomId)
    .single();
  if (chatDataErr) {
    throw chatDataErr;
  } else {
    return chatData;
  }
};

 

์›์ธ๊ฐ€์„ค๋“ค์˜ ์˜๋ฌธ์  ์ค‘ ํ•˜๋‚˜์ธ "์™œ ํ•œ๋ฒˆ ๋” ์‹คํ–‰๋˜๋Š”์ง€"๋Š” ์•„๋งˆ ์„œ๋ฒ„ ์ธก์—์„œ queryFn์„ ์‹คํŒจํ–ˆ์„ ๋•Œ, ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์žฌ์‹คํ–‰ํ•˜๋Š” ์˜ต์…˜์ด ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ๊ฒฐ๋ก ์„ ์ง€์—ˆ๋‹ค.


์›์ธ์€ ์ด์ œ ์•Œ์•˜๋‹ค. ๊ทธ๋Ÿผ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์€?

what(๊ฒฐ๊ณผ) - To Be

์ด ์„ธ ๊ฐ€์ง€์˜ ์˜๊ฒฌ์ด ๋‚˜์™”๋‹ค.

1. Next.js ๊ณต์‹๋ฌธ์„œ์— lazy-loading > Skipping SSR ๋ถ€๋ถ„์— Client ์ปดํฌ๋„ŒํŠธ์˜ pre-rendering์„ ์›ํ•˜์ง€ ์•Š์œผ๋ฉด, Server ์ปดํฌ๋„ŒํŠธ์—์„œ Suspense๋กœ ์ฝ”๋“œ์Šคํ”Œ๋ฆฌํŒ… ํ•œ ๋ถ€๋ถ„ = Client ์ปดํฌ๋„ŒํŠธ(์—ฌ๊ธฐ์„  SideBar.tsx)๋ฅผ import ํ•˜๋Š” ๊ฒƒ ์ž์ฒด๋ฅผ dynamic, {ssr: false} ์˜ต์…˜์„ ๊ฑธ์ž!

 

์‹œ๋„๊ฒฐ๊ณผ -> ์˜ค๋ฅ˜ ํ•ด๊ฒฐ

// src > app > chat > .. > page.tsx
...
// import SideBar from '@/components/chat/sidebar/SideBar';
const SideBarWrapper = dynamic(() => import('@/components/chat/sidebar/SideBar'), { ssr: false });

const ChatPage = async ({ params }: { params: { chatroom_id: string } }) => {
 ...
  return (
    <main>
      <HydrationBoundary state={dehydrate(queryClient)}>
        <Suspense fallback={<ChatLoading />}>
              <section className="lg:flex lg:max-w-96 max-sm:absolute max-sm:z-40 max-sm:bg-white min-h-[36rem] ">
                <SideBarWrapper chatRoomId={chatRoomId} />  --> SideBar๋ฅผ SideBarWrapper๋กœ ๋ณ€๊ฒฝ
              </section>
  ...

 

2.  ๊ทธ๊ฒƒ๋ณด๋‹ค๋Š” pre-render๋ฅผ ํ—ˆ์šฉํ•ด์„œ ๋ฏธ๋ฆฌ data๋ฅผ ์ž˜ ๋ฐ›์•„์˜ค๋ฉด ๋กœ๋”ฉ ์†๋„๋„ ๋นจ๋ฆฌ์งˆ ๊ฒƒ ๊ฐ™์€๋ฐ, ๊ทธ๋ƒฅ queryFn ๋‚ด์˜ ClientSupabase๋ฅผ window์˜ type์— ๋”ฐ๋ผ์„œ server ์ธก์—์„œ๋Š” serverSupabse() ์‚ฌ์šฉํ•˜๊ณ  client ์ธก์—์„œ๋Š” clientSupabase() ์‚ฌ์šฉํ•˜๋„๋ก ์กฐ๊ฑด๋ฌธ ๊ฑธ์–ด๋ณด์ž! 

 

์‹œ๋„๊ฒฐ๊ณผ -> ๋‹ค๋ฅธ ์˜ค๋ฅ˜ ๋ฐœ์ƒ

const getSupabase = () => {
  if (typeof window === undefined) {
    const supabase = serverSupabase();
    return supabase;
  } else {
    return clientSupabase;
  }
};

export const fetchChatData = async (chatRoomId: string) => {
  console.log('aaaa', chatRoomId);
  const { data: chatData, error: chatDataErr } = await getSupabase()
    .from('chatting_room')
    .select('*')
    .eq('chatting_room_id', chatRoomId)
    .single();
  if (chatDataErr) {
    throw chatDataErr;
  } else {
    return chatData;
  }
};

 

ํ•˜์ง€๋งŒ

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹ค๋ฅธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

serverSupabase() ํ•จ์ˆ˜๋Š” ์˜ค์ง server ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ๋ฐ, ์šฐ๋ฆฌ๋Š” ์ง€๊ธˆ fetchChatData๋ผ๋Š” useSuspenseQuery์˜ queryFn์„ client ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์„œ ์ธ ๊ฒƒ ๊ฐ™๋‹ค.


๐Ÿ’ก ์ƒˆ๋กญ๊ฒŒ ์•Œ๊ฒŒ๋œ ์ 

Nest.js์˜ app-router์—์„œ๋Š” "use client"๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค๊ณ  ํ•ด์„œ ์™„์ „ํ•œ CSR์€ ์•„๋‹ ์ˆ˜ ์žˆ๋‹ค.

์™œ๋ƒํ•˜๋ฉด? pre-rendering์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

SSR๊ณผ CSR์˜ hybrid ์ •๋„๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.