본문 바로가기
  • 살짝 구운 김 유나
Web/Next.js

[Next.js] layout에서 dynamic 사용하기 (13 버전)

by yunae 2023. 5. 12.

현재 웹소켓을 활용한 실시간 게임 웹 서비스를 만들고 있다.

사용자가 웹소켓에 연결한 이후에 새로고침을 하거나 페이지를 떠날 때, 서버 측에 이 사용자의 토큰을 보내 퇴장을 알려야 한다.

서버에 보내 주어야 하는 내용은 다음과 같다.

여기서 저 roomId는 redux의 guestSlice에서 관리되고 있다! (useSelector를 사용해야함)

 

사용자가 어느 페이지에 있든 상관없이 적용되어야 하므로  layout.tsx에 작성했다.

import './globals.css';
import { Inter } from 'next/font/google';
import { Providers } from '@/store/provider';
import { SocketProvider } from '../context/SocketContext';

const inter = Inter({ subsets: ['latin'] });

export const metadata = {
  title: '말랑연구소',
  description: 'Generated by Next.js',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const guest = useSelector((state: RootState) => state.guest);
  const router = useRouter();
  const token = localStorage.getItem('token');

  // 새로고침 또는 페이지를 이동할 때
  useEffect(() => {
    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
      e.preventDefault();
      e.returnValue = '';
      userOutApi(guest.pin);
    };

    window.addEventListener('beforeunload', handleBeforeUnload);
    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload);
    };
  }, [guest]);

  // 토큰이 없는 경우 홈화면으로
  useEffect(() => {
    if (!token) router.push('/');
  }, [token]);

  return (
    // redux 적용
    <html lang="en" className={inter.className}>
      <body className="text-black">
        <Providers>
          <SocketProvider>{children}</SocketProvider>
        </Providers>
      </body>
    </html>
  );
}

총 2가지 문제가 발생했다.

1. Redux가 적용되는 <Provider> 밖에서 useSelector로 redux에 접근불가! (사실 당연해서 할말 없음)

2. useEffect, useState와 같은 리액트 훅은 SSR에서 사용불가!

 

1번 문제는 js 코드를 담은 <Check> 라는 컴포넌트를 생성하여 해결할 수 있었다.

<Check> 컴포넌트를 <Provider>로 감싸진 부분에 넣는다.

import './globals.css';
import { Inter } from 'next/font/google';
import { Providers } from '@/store/provider';
import { SocketProvider } from '../context/SocketContext';

const inter = Inter({ subsets: ['latin'] });

export const metadata = {
  title: '말랑연구소',
  description: 'Generated by Next.js',
};

function Check {
  const guest = useSelector((state: RootState) => state.guest)
    const router = useRouter()
    const token = localStorage.getItem('token')

    // 새로고침 또는 페이지를 이동할 때
    useEffect(() => {
        const handleBeforeUnload = (e: BeforeUnloadEvent) => {
            e.preventDefault();
            e.returnValue = '';
            userOutApi(guest.pin)
        }

        window.addEventListener('beforeunload', handleBeforeUnload)
        return () => {
            window.removeEventListener('beforeunload', handleBeforeUnload);
        }
    }, [guest])

    // 토큰이 없는 경우 홈화면으로
    useEffect(() => {
        if (!token) router.push('/')
    }, [token])

    return null;
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    // redux 적용
    <html lang="en" className={inter.className}>
      <body className="text-black">
        <Providers>
          <Check />
          <SocketProvider>{children}</SocketProvider>
        </Providers>
      </body>
    </html>
  );
}

하지만 Check 컴포넌트는 동적이고 layout은 서버측(정적)에서 렌더링 되기 때문에 오류가 발생한다.

 

dynamic?

Next.js는 JavaScript 모듈을 동적으로 가져올 수 있는 dynamic을 지원한다.

이를 통해서 사용자와 동적으로 상호작용할 부분만 서버사이드에서 렌더링 할 수 있게 된다.

 

<Check> 컴포넌트 파일을 따로 생성한다.

// Check.tsx

'use client';

import { userOutApi } from "@/apis/apis"
import { RootState } from "@/store/store"
import { useRouter } from "next/navigation"
import { useEffect } from "react"
import { useSelector } from "react-redux"

export default function Check() {
    const guest = useSelector((state: RootState) => state.guest)
    const router = useRouter()
    const token = localStorage.getItem('token')

    // 새로고침 또는 페이지를 이동할 때
    useEffect(() => {
        const handleBeforeUnload = (e: BeforeUnloadEvent) => {
            e.preventDefault();
            e.returnValue = '';
            userOutApi(guest.pin)
        }

        window.addEventListener('beforeunload', handleBeforeUnload)
        return () => {
            window.removeEventListener('beforeunload', handleBeforeUnload);
        }
    }, [guest])

    // 토큰이 없는 경우 홈화면으로
    useEffect(() => {
        if (!token) router.push('/')
    }, [token])

    return null;
}

2. <Check> 를 동적으로 import 한다.

import dynamic from 'next/dynamic';
// <Check> 컴포넌트를 동적으로 로드
const Check = dynamic(() => import('../components/common/Check'),
  {
    ssr: false, // 서버측에는 렌더링하지 않음
  }
);

 

 

결론

Next.js는 기본적으로 서버 사이드에서 렌더링 되지만, 필요할 때 dynamic import를 사용하여 동적으로 컴포넌트를 렌더링 할 수 있다. 결과적으로는 전달되는 JS 코드를 최소화 하여 로드 시간을 개선할 수 있는 것!  짱!

 

 

 

우리 서비스 화면,, 귀엽지 않나요,,ㅎ 근데 이제 배경은 그냥 이미지,, 담에는 유리질감 구현하기 도전~̆̈

 

'Web > Next.js' 카테고리의 다른 글

[Next.js] Server Component & Client Component  (0) 2023.04.26
[Next.js] 개발환경 설정  (0) 2023.04.25
[Next.js] 왜 Next.js를 쓸까?  (0) 2023.04.25

댓글