본문 바로가기
Next.js

[Next.js, TS,Zustand] 페이지 이동전 로딩효과 추가하기

by goodchuck 2024. 5. 30.

목차

     Next.js 14이상 app라우터에서 페이지 로딩효과 추가하기

    Next.js 에서 app라우터로 개발을 하는 도중 라우터 이동전 로딩효과를 주는게 필요하였습니다.

     

    사용 버전

    • react : ^18
    • zustand : ^4.5.2
    • next : 14.1.0

     진행과정

     

    loadingStore.ts 추가

    /src/store/loadingStore.ts파일을 만들어줍니다.

    import { create } from 'zustand';
    
    interface LoadingState {
      isLoading: boolean;
    }
    
    interface LoadingActions {
      setLoading: (loading: boolean) => void;
    }
    
    const useLoadingStore = create<LoadingState & LoadingActions>((set) => ({
      isLoading: false,
      setLoading: (loading: boolean) => set({ isLoading: loading }),
    }));
    
    export default useLoadingStore;

     

     

    RouteChangeHandler.tsx 추가

    'use client';
    
    import React, { useEffect, useTransition } from 'react';
    import { usePathname, useRouter } from 'next/navigation';
    import useLoadingStore from '@/store/loadingStore';
    
    const RouteChangeHandler: React.FC = () => {
      const setLoading = useLoadingStore((state) => state.setLoading);
      const router = useRouter();
      const [isPending, startTransition] = useTransition();
      const pathname = usePathname();
    
      useEffect(() => {
        setLoading(isPending);
      }, [isPending, setLoading]);
    
      useEffect(() => {
        setLoading(false);
      }, [pathname, setLoading]);
    
      return null;
    };
    
    export default RouteChangeHandler;

     

    LoadingSpinner.tsx, LoadingSpinner.styles.ts 추가

     LoadingSpinner.tsx

    /src/components/Loading/Spinner/LoadingSpinner.tsx

    'use client';
    
    import React from 'react';
    import useLoadingStore from '@/store/loadingStore';
    import { StyledSpinner, StyledSpinnerOverlay } from './LoadingSpinner.styles';
    
    const LoadingSpinner: React.FC = () => {
      const isLoading = useLoadingStore((state) => state.isLoading);
    
      if (!isLoading) return null;
    
      return (
        <StyledSpinnerOverlay>
          <StyledSpinner />
        </StyledSpinnerOverlay>
      );
    };
    
    export default LoadingSpinner;

     LoadingSpinner.styles.ts

    /src/components/Loading/Spinner/LoadingSpinner.styles.tsx

    import styled from 'styled-components';
    
    export const StyledSpinnerOverlay = styled.div`
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: rgba(255, 255, 255, 0.7);
      z-index: 9999;
    `;
    
    export const StyledSpinner = styled.div`
      border: 16px solid #f3f3f3;
      border-top: 16px solid #3498db;
      border-radius: 50%;
      width: 120px;
      height: 120px;
      animation: spin 2s linear infinite;
    
      @keyframes spin {
        0% {
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(360deg);
        }
      }
    `;

     

    Layout.tsx에 추가

    src/app/layout.tsx

    import type { Metadata } from 'next';
    import { Flex } from 'antd';
    import './globals.css';
    import LoadingSpinner from '@/components/Loading/Spinner/LoadingSpinner';
    import RouteChangeHandler from '@/components/RouteChangeHandler';
    
    export const metadata: Metadata = {
      title: 'Create Next App',
      description: 'Generated by create next app',
    };
    
    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <html lang="en">
          <head>
          </head>
          <body >
                <Flex vertical style={{ width: '100%' }}>
                  <LoadingSpinner />
                  <RouteChangeHandler />
                  {children}
    
                </Flex>
          </body>
        </html>
      );
    }

     

    로딩을 줄 컴포넌트에서 로딩 주기

    'use client';
    
    import React from 'react';
    import { Avatar, Button, Card, Image } from 'antd';
    import { useRouter } from 'next/navigation';
    import useLoadingStore from '@/store/loadingStore';
    
    const { Meta } = Card;
    
    type Props = {
      assistant: any;
      isPreparing?: boolean;
    };
    
    const AssistantCard = ({ assistant, isPreparing = false }: Props) => {
      const router = useRouter();
      const setLoading = useLoadingStore((state) => state.setLoading);
      const onHandleClickBtn = (assistantId: string) => {
        setLoading(true);
        router.push(`assistant/chatting/${assistantId}`);
      };
    
    	...생략
    };
    
    export default AssistantCard;

    useLoadingStore를 가져오고 setLoading(true)로 해주면 끝!

    router이동 전에 로딩바가 사이트 중앙에서 빙빙돌고있으면 정상 반영된거다.

     

     적용사진