본문 바로가기

[Nextjs] Nextjs + nprogress 구현

@Jeeqong 2024. 9. 30. 17:23
반응형

서론

Next.js에서 페이지 로딩 시 ProgressBar를 구현하는 방법은 몇 가지가 있지만, 가장 일반적인 방법은 클라이언트 측에서 라우팅이 변경될 때 이를 감지하고 ProgressBar를 보여주는 방식입니다. 이를 위해 Next.js의 Router 이벤트를 활용할 수 있다.

 


본문

1. nprogress 설치

먼저, nprogress 라이브러리를 설치합니다. nprogress는 경량화된 progress bar 라이브러리로, 쉽게 페이지 로딩에 적용할 수 있다.

npm install nprogress
 

https://www.npmjs.com/package/nprogress

 

nprogress

Simple slim progress bars. Latest version: 0.2.0, last published: 10 years ago. Start using nprogress in your project by running `npm i nprogress`. There are 2962 other projects in the npm registry using nprogress.

www.npmjs.com


2. Tailwind와 ProgressBar 커스터마이징

Tailwind CSS와 함께 사용하는 경우, nprogress의 기본 스타일을 커스터마이징할 수 있습니다. 기본적으로 nprogress는 상단에 얇은 파란색 로딩 바를 표시합니다. 이 스타일을 Tailwind로 쉽게 커스터마이징할 수 있습니다.

#nprogress .bar {
	@apply bg-primary h-1.5 !important;
}

3. nprogress 설정

_app.tsx 설정 (CSR용)

src/app/layout.tsx 또는 src/app/_app.tsx 파일에 다음과 같이 nprogress를 설정합니다. Router 이벤트를 활용하여 라우팅이 시작될 때와 완료될 때, 로딩 바를 시작하고 종료할 수 있습니다.

// src/app/_app.tsx

'use client';

import { useEffect } from 'react';
import { useRouter } from 'next/router';
import NProgress from 'nprogress'; // nprogress 라이브러리
import 'nprogress/nprogress.css'; // 기본 스타일, 필요에 따라 커스터마이징 가능

export default function MyApp({ Component, pageProps }: any) {
  const router = useRouter();

  useEffect(() => {
    const handleStart = () => NProgress.start(); // 로딩 시작
    const handleStop = () => NProgress.done(); // 로딩 완료

    router.events.on('routeChangeStart', handleStart); // 라우트 시작 이벤트
    router.events.on('routeChangeComplete', handleStop); // 라우트 완료 이벤트
    router.events.on('routeChangeError', handleStop); // 라우트 에러 발생 시

    return () => {
      router.events.off('routeChangeStart', handleStart);
      router.events.off('routeChangeComplete', handleStop);
      router.events.off('routeChangeError', handleStop);
    };
  }, [router]);

  return <Component {...pageProps} />;
}

 


3.1. TS7016: Could not find a declaration file for module nprogress.

이 오류는 TypeScript가 nprogress 라이브러리에 대한 타입 선언 파일을 찾지 못해서 발생

일부 패키지들은 기본적으로 타입 선언 파일을 제공하지 않으며, 이 경우 타입 선언을 수동으로 추가해야 합니다.

nprogress는 기본적으로 타입 선언 파일을 제공하지 않으므로, 이를 해결하는 방법은 두 가지가 있습니다.

 

3.1.1 타입 선언 파일 설치 

npm install --save-dev @types/nprogress

or 

3.1.2 타입 선언을 수동으로 추가

// src/global.d.ts
declare module 'nprogress' {
  interface NProgress {
    start: () => NProgress;
    done: (force?: boolean) => NProgress;
    set: (n: number) => NProgress;
    inc: (amount?: number) => NProgress;
    configure: (options: Partial<NProgressConfigureOptions>) => NProgress;
    status: null | number;
  }

  interface NProgressConfigureOptions {
    minimum: number;
    easing: string;
    speed: number;
    trickle: boolean;
    trickleSpeed: number;
    showSpinner: boolean;
  }

  const nprogress: NProgress;
  export default nprogress;
}

4. PageLayout에서는 그대로 SSR을 유지, 별도의 클라이언트 전용 컴포넌트로 ProgressBar를 관리하는 방법

4.1. 클라이언트 전용 ProgressBar 컴포넌트 생성

ProgressBar는 클라이언트에서만 동작하므로 이를 클라이언트 전용 컴포넌트로 분리합니다.

// src/components/common/ProgressBar.tsx
'use client'; // 클라이언트 전용 컴포넌트로 지정

import { useEffect } from 'react';
import { useRouter } from 'next/router';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; // 기본 nprogress 스타일

const ProgressBar = () => {
  const router = useRouter();

  useEffect(() => {
    const handleStart = () => NProgress.start();
    const handleStop = () => NProgress.done();

    router.events.on('routeChangeStart', handleStart);
    router.events.on('routeChangeComplete', handleStop);
    router.events.on('routeChangeError', handleStop);

    return () => {
      router.events.off('routeChangeStart', handleStart);
      router.events.off('routeChangeComplete', handleStop);
      router.events.off('routeChangeError', handleStop);
    };
  }, [router]);

  return null; // 실제로 화면에 렌더링되는 요소가 필요 없으므로 null 반환
};

export default ProgressBar;

4.2. PageLayout에 ProgressBar 적용

이제 PageLayout은 여전히 SSR에서 동작하지만, ProgressBar 컴포넌트는 클라이언트에서만 실행되므로 ProgressBar 컴포넌트를 레이아웃에 추가하면 됩니다.

// src/app/pages/layout.tsx
import ProgressBar from '@/components/common/ProgressBar' // 클라이언트 사이드 ProgressBar
import React from 'react'
import { getServerSession } from 'next-auth/next'
import { authOptions } from '@/lib/auth'
import { Session } from 'next-auth'

const PageLayout = async ({ children }: { children: React.ReactNode }) => {
  const session: Session | null = await getServerSession(authOptions) // 세션 타입 명시

  return (
    <div className="relative flex min-h-screen">
      <ProgressBar /> {/* ProgressBar 컴포넌트 추가 */}
      <div className="w-full mt-10">{children}</div>
    </div>
  )
}

export default PageLayout

4.3. 설명

  • ProgressBar 컴포넌트는 use client로 지정되어 클라이언트 사이드에서만 동작하도록 설정
  • PageLayout은 서버 측에서 동작하며, 여전히 세션을 서버 측에서 처리
  • ProgressBar는 PageLayout 내부에 포함되었지만 클라이언트 전용으로 작동하므로, 라우터 이벤트에 반응하여 페이지 전환 시 로딩 바를 표시

이렇게 하면 SSR 환경에서 페이지 전환 시 ProgressBar를 클라이언트 사이드에서만 실행하게 할 수 있습니다.


5. `NextRouter` was not mounted

https://nextjs.org/docs/messages/next-router-not-mounted

 

에러를 찾아보면

"NextRouter was not mounted" 오류는 Next.js의 useRouter 훅을 사용하는 컴포넌트가 서버 측에서 렌더링될 때 발생하는 문제, useRouter가 클라이언트 측에서만 사용 가능한데, 해당 컴포넌트가 서버 측에서도 렌더링되기 때문에 발생합니다.

 

라고 나오지만 

Next.js 13부터 App Router(app 디렉토리)를 사용할 때 next/router 에서 지원되는 모듈이 next/navigation 바꼈단다.

그래서 router.events.on 방식은 사용불가!!!!!

라우터 이벤트를 감지하려면 App Router에서는 usePathname, useSearchParams, useRouter 훅 등을 사용해 URL이 변경되었을 때 필요한 동작을 구현가능!!!

허나 router 를 안쓰고도 ProgressBar 구현이 가능함..


최종버전

'use client';

import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';

const ProgressBar = () => {
  const pathname = usePathname(); 

  useEffect(() => {
    const handleStart = () => NProgress.start();
    const handleComplete = () => NProgress.done();

    handleStart(); // 경로 변경 시 NProgress 시작

    // 라우팅이 완료되면 NProgress 종료
    handleComplete();

    // 컴포넌트가 언마운트될 때 클린업
    return () => {
      NProgress.done();
    };
  }, [pathname]); // pathname이 변경될 때마다 실행

  return null;
};

export default ProgressBar;
  1. usePathname: 이 훅을 사용하면 현재 URL 경로를 추적할 수 있습니다. pathname이 변경될 때마다 useEffect가 실행되어 ProgressBar를 시작하고 완료하는 동작을 처리할 수 있습니다.
  2. useEffect: URL 경로가 변경될 때마다 useEffect가 실행되며, 페이지 전환이 감지됩니다. NProgress.start()는 라우트가 변경될 때 실행되고, NProgress.done()는 완료되었을 때 호출됩니다.
  3. NProgress.done() 클린업: 경로가 변경된 후에 NProgress.done()을 호출하여 ProgressBar를 종료합니다. 컴포넌트가 언마운트될 때도 이를 안전하게 처리하기 위해 클린업 함수를 제공합니다.
반응형
Jeeqong
@Jeeqong :: JQVAULT

Jeeqong's vault : 정보/기록을 쌓아두는 공간 웹개발 포스팅 일상 리뷰를 기록하는 공간입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차