본문 바로가기

카테고리 없음

Study_240702 ( Next.js 라우팅 )

React 및 Next.js에서의 라우팅 구현 방법

  1. 기존의 React.js를 사용하여 웹 애플리케이션을 만들 때는 react-router-dom을 이용해서 라우팅을 가능
  2. 이렇게 중요한 라우팅에 대한 근본적인 시각, 전략이 next.js 13버전을 기점으로 변경.
    1. 변경 전 : pages 폴더에 원하는 페이지의 파일 이름을 둠 
    2. 변경 후 : app 폴더 밑에 폴더명을 기반으로 자동 라우팅이 됨. 그래서 app router라고 보통 말을 하는 것

 

랜더링 기법

  • 원시적 방법 MPA : 페이지 이동 및 렌더링 시 깜빡거리는 현상 -> UX 저하
  • 획기적 방법 SPA (Single Page Application ) : 브라우저에서 JS를 이용해 동적으로 페이지를 렌더링 -> root라는 id를 가진 div만 다운로드 (JS로 UI 완성 ) -> UX 크게 향상
    • 초기 로딩 속도가 느림 -> 보완하기 위해 Code Spilitting 방법 제시 / 하나로 번들된 코드를 여러 코드로 나눠 당장 필요한 코드가 아니면 나중에 불러옴 

 

CSR(Client Side Rendering)

  • 특징
    • 순수 리액트 사용했을 때 100%
    • 브라우저에서 JavaScript를 이용해 동적으로 페이지를 렌더링하는 방식
    • 렌더링의 주체 : 클라이언트
  • 장점
    • (최초 한번 로드가 끝나면) 사용자와의 상호작용이 빠르고 부드러움
    • 서버에게 추가적인 요청을 보낼 필요가 없기 때문에, 사용자 경험이 좋음
    • 서버 부하가 적음
  • 단점
    • 첫 페이지 로딩 시간(Time To View)이 길 수 있음
    • JavaScript가 로딩되고 실행될 때까지 페이지가 비어있어 검색 엔진 최적화(SEO)에 불리함
  • 예시 코드
    import React from "react";
    import ReactDOM from "react-dom";

    function App() {
      return <h1>Hello, Client Side Rendering!</h1>;
    }

    // index.js
    ReactDOM.render(<App />, document.getElementById("root"));

SSG(Static Site Generation)

  • 특징
    • 서버에서 페이지를 렌더링하여 클라이언트에게 HTML을 전달하는 방식
    • 최초 빌드시에만 생성이 됨.
      • vercel을 이용할 때 알아서 build를 해주기 때문에 우리는 Local 환경에서 yarn build, npm run build 등의 명령어를 통해 빌드 파일을 직접 생성 할 필요가 없었음
    • 사전에 미리 정적페이지를 여러개 만들어놓음 → 클라이언트가 홈페이지 요청을 하면, 서버에서는 이미 만들어져있는 사이트를 바로 제공! → 클라이언트는 표기만 함
  • 장점
    • 첫 페이지 로딩 시간이 매우 짧아(TTV) 사용자가 빠르게 페이지를 볼 수 있음. 또한, SEO에 유리
    • CDN(Content Delivery Network) 캐싱 가능 ( 결과물을 중간에서 관리해주는 네트워크 느낌 )
  • 단점
    • 정적인 데이터에만 사용할 수 있음
    • 사용자와의 상호작용이 서버와의 통신에 의존하므로, 클라이언트 사이드 렌더링보다 상호작용이 느릴 수 있음. 또한, 서버 부하가 클 수 있음.
    • 마이페이지 처럼 데이터에 의존하여 화면을 그려주는 경우 사용 불가
  • 예시코드
    import React from "react";

    function HomePage({ data }) {
      return <div>{data}</div>;
    }

    export async function getStaticProps() {
      const res = await fetch("https://..."); // 외부 API 호출
      const data = await res.json();

      return { props: { data } };
    }

    export default HomePage;

ISR(Incremental Static Regeneration) -> SSG이지만 가끔씩은 다시 생성 가능

  • 특징
    • SSG처럼 정적 페이지를 제공
    • 설정한 주기만큼 페이지를 계속 생성해 줌
      • ex : 주기가 10분이라면? → 10분마다 데이터베이스 또는 외부 영향 때문에 변경된 사항을 반영하는 역할
    • 정적 페이지를 먼저 보여주고, 필요에 따라 서버에서 페이지를 재생성하는 방식
  • 장점
    • 정적 페이지를 먼저 제공하므로 사용자 경험이 좋으며, 콘텐츠가 변경되었을 때 서버에서 페이지를 재생성하므로 최신 상태를 (그나마) 유지 가능
    • CDN 캐싱 가능
  • 단점
    • 동적인 콘텐츠를 다루기에 한계가 있을 수 있습니다. 실시간 페이지 아님
    • 마이페이지 처럼 데이터에 의존하여 화면을 그려주는 경우 사용 불가
  • 예시코드
    import React from "react";

    function HomePage({ data }) {
      return <div>{data}</div>;
    }

    export async function getStaticProps() {
      const res = await fetch("https://..."); // 외부 API 호출
      const data = await res.json();

      return {
        props: { data },
        revalidate: 60, // 1초 후에 페이지 재생성
      };
    }

    export default HomePage;

SSR : App Server인게 중요 -> 랜더링 빈도가 많은 곳 SSG<ISR<SSR 순

  • 특징
    • 빌드 시점에 모든 페이지를 미리 생성하여 서버 부하를 줄이는 방식
    • SSG, ISR처럼 렌더링 주체가 서버!
    • 클라이언트의 요청 시 렌더링
      • C → S : 이 페이지 줘!
      • S → C : (데이터베이스 읽고 등등 한 후) html 파일을 제공
  • 장점
    • 빠른 로딩 속도(TTV)와 높은 보안성을 제공.
    • SEO 최적화 좋음
    • 실시간 데이터를 사용
    • 마이페이지 처럼 데이터에 의존한 페이지 구성 가능
    • CDN 캐싱 불가
  • 단점
    • 사이트의 콘텐츠가 변경되면 전체 사이트를 다시 빌드해야 하는데, 이 과정이 시간이 오래 걸릴 수 있음. → 서버 과부하
    • 요청할 때 마다 페이지를 만들어야 함
  • 예시 코드
    import React from "react";

    function HomePage({ data }) {
      return <div>{data}</div>;
    }

    export async function getServerSideProps() {
      const res = await fetch("https://..."); // 외부 API 호출
      const data = await res.json();

      return { props: { data } };
    }

    export default HomePage;

비교

 
CSR
SSR
SSG
ISR
빌드 시간
짧다
짧다
길다
길다
SEO
나쁨
좋음
좋음
좋음
페이지 요청에 따른 응답 시간
보통
길다
짧다
짧다
최신 정보인가?
맞음
맞음
아님
아닐 수 있음

 

Hydration : TTV 와 TTI 시간의 간격을 줄여줌

  • TTV : 최초 사용자가 웹 페이지를 볼 수 있기까지의 시간 
  • TTI : 화면은 보이지만 클릭 ( Interaction ) 이 되기까지의 시간
  1. CSR
    • React에서 CSR로만 컴포넌트 렌더링을 할 때는 TTV가 오래 걸림. 모든 React 소스파일을 다운로드 받아야만 화면을 볼 수 있기 때문.
    • 여기에서 Hydration 개념이 들어감. 최초 서버에서는 index.html 파일만 제공하지만 이후 React 소스파일을 바탕으로 한 자바스크립트 파일이 모두 다운로드 돼야만(즉, Hydration이 돼야만) 최종 소스코드를 볼 수 있는 것.
    • 하지만 CSR의 과정에서의 Hydration 과정을 Hydration으로 볼 것이냐 하는 것은 이견이 있을 수 있음.
  2. SSR
    • 서버에서는 사용자의 요청이 있을 때 마다 페이지를 새로 그려서 사용자에게 제공.
    • 두 과정으로 나눠서 제공.
      1. pre-rendering : 사용자와 상호작용하는 부분을 제외한 껍데기만을 먼저 브라우저에게 제공. TTV가 엄청나게 빨라짐.
      2. hydration : 이 과정이 일어나기 전까지는 껍데기만 있는 html 파일이기 때문에 사용자가 아무리 버튼을 click 해도 아무 동작이 일어나지 않음. 인터렉션에 필요한 모든 파일을 다운로드 받는 과정 즉, hydration 과정이 끝나야 그제서야 인터렉션이 가능. TTI를 줄이는 것이 관건.
  3. SSG, ISR도 SSR과 마찬가지로 hydration 과정이 존재.

Routing

설치 명령어 : npx create-next-app@latest

결과

 

라우팅을 이해하기 위한 주요 용어

  • Tree 관련

    • Tree
      • 계층 구조를 시각적으로 잘 보기 위한 규칙(위 → 아래). DOM tree와 비슷하죠?
    • Subtree
      • tree의 한 부분
      • root부터 시작해서 leaf들에 이르기까지의 범위
    • Root
      • Tree 또는 Subtree의 첫 번째 노드
      • root layout 같은 것
    • Leaf
      • children이 더이상 없는 node
  • URL 관련
    • URL Segment
      • 슬래시(/)로 분류된 URL path의 한 부분
    • URL Path

 

파일(폴더) 기반 라우팅

  • page.tsx : main UI가 표시될 곳
  • static routing
    • src > app 폴더 밑에 test 폴더를 새로 만들고 그 안에 page.tsx 파일 만듦
    • 예시코드 
    • // src>app>test>page.tsx
      import React from "react";

      const TestPage = () => {
        return (
          <div>
            <h1>Test Page</h1>
            <p>안녕하세요! 테스트 페이지입니다</p>
          </div>
        );
      };

      export default TestPage;
    • 결과
  • dynamic routing
    • 폴더 이름을 대괄호로 감싸면 됨 -> app/posts/[id]/page.tsx
    •  
    • // app>test>[id]>page.tsx
      import React from "react";

      const TestDetailPage = ({
        params,
      }: {
        params: {
          id: string;
        };
      }) => {
        return <div>Detail 페이지 : {params.id}</div>;
      };

      export default TestDetailPage;
  • route groups
    • 폴더를 Routing에 포함 시키지 않는 방법

    • // src>app>(admin)>about>page.tsx
      import React from "react";

      const AdminAboutPage = () => {
        return <div>소개 페이지입니다.</div>;
      };

      export default AdminAboutPage;
    • // src>app(marketing)>business>page.tsx
      import React from "react";

      const MarketingBusinessPage = () => {
        return <div>마케팅 사업 관련 페이지입니다.</div>;
      };

      export default MarketingBusinessPage;

특별한 파일들

  • layout
    • export default function DashboardLayout({
        children, // will be a page or nested layout
      }: {
        children: React.ReactNode;
      }) {
        return (
          <section>
            {/* Include shared UI here e.g. a header or sidebar */}
            <nav></nav>

            {children}
          </section>
        );
      }
    • // src>app>layout.tsx
      import type { Metadata } from "next";
      import { Inter } from "next/font/google";
      import "./globals.css";

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

      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">
            <body className={inter.className}>
              <nav>
                <a href="/">Home</a>
                <a href="/about">About</a>
                <a href="/contact">Contact</a>
                <a href="/blog">Blog</a>
              </nav>
              {children}
            </body>
          </html>
        );
      }
    • (admin) 폴더 안에서 layout.tsx를 만듦으로 페이지별로 따로 묶어서 적용도 가능
  • template
    • template 파일은 방금 학습한 layout과 상당히 유사한 컴포넌트
      • 경로 전반에 걸쳐서 상태가 유지되는 레이아웃과 달리, 템플릿은 라우팅을 탐색할 때 각 하위 항목에 대해 새 인스턴스를 만듦. User 입장에서 동일한 Template을 공유하는 경로 사이를 왔다갔다 할 때 DOM 요소가 다시 생성된다는 것을 의미
    • 템플릿을 통한 페이지 open animation
      • 페이지 간 전환 시 애니메이션을 계속해서 주고 싶을 때 layout으로 만들어놓으면, 최초 렌더링시에만 animation이 적용되고 끝나버리므로 template 사용
    • useEffect, useState에 의존하는 기능
      • 예시코드
      • // src>app>text>layout.tsx
        // src>app>text>template.tsx
        "use client";

        import Link from "next/link";
        import React from "react";

        const TestTemplate = ({ children }: { children: React.ReactNode }) => {
          useEffect(() => {
            console.log("최초 렌더링 한 번만 호출합니다.");
          }, []);

          return (
            <div className="m-8 p-8 bg-white">
              <h1>테스트 페이지</h1>
              <p>테스트 경로 하위에서의 이동을 확인해봅니다.</p>
              <nav>
                <ul>
                  <li>
                    <Link href="/test">테스트 페이지</Link>
                  </li>
                  <li>
                    <Link href="/test/1">테스트 페이지 1</Link>
                  </li>
                  <li>
                    <Link href="/test/2">테스트 페이지 2</Link>
                  </li>
                </ul>
              </nav>
              {children}
            </div>
          );
        };

        export default TestTemplate;
      • layout은 콘솔 로그가 최초 1번만 찍히지만 template는 페이지 이동 시 마다 호출 됨
  • not-found
    • 예시 코드
      // src>app>not-found.tsx
      import React from "react";

      const NotFound = () => {
        return <div>존재하지 않는 페이지입니다.</div>;
      };

      export default NotFound;
  • metadata와 SEO
    • metadata는 페이지 별로 적용 가능 -> 향상된 SEO ( Search Engine Optimization, 검색 엔진 최적화 ) 제공
    • SEO : 웹사이트나 웹페이지를 검색 엔진에서 더 높은 순위에 노출시키기 위한 방법 -> 검색 결과에서 웹사이트가 더 위에 나타나도록 만드는 여러 기술과 전략
      • <img src="../assets/sample.jpg" />
        <img src="../assets/sample.jpg" alt="sample image" />
      • 검색 엔진에 이미지 내용 설명하기 : alt 텍스트는 검색 엔진에 이미지의 내용과 맥락을 설명 -> 이미지가 무엇에 관한 것인지 이해하고, 관련 검색 쿼리에 대한 결과로 해당 이미지를 더 정확하게 랭킹
      • 접근성 향상 : alt 텍스트는 사용자가 웹사이트의 이미지 콘텐츠를 이해하는 데 도움을 줌 -> 접근성 개선은 검색 엔진에 의해 긍정적인 신호로 해석되어, 전반적인 사이트의 SEO 점수를 향상
    • static
      • 원하는 page.tsx 또는 layout.tsx 어디든 적용 가능
        export const metadata: Metadata = {
          title: "Sparta Next App",
          description: "This is awesome Website",
        };
        • metadata in page.tsx : 해당 page.tsx 컴포넌트에만 적용
        • metadata in layout.tsx : 해당 layout의 하위 요소에 모두 적용
      • // src>app>page.tsx
        import { Metadata } from "next";

        export const metadata: Metadata = {
          title: "Sparta Next App",
          description: "This is awesome Website",
        };

        export default function Home() {
          return <div>안녕하세요! 내배캠 리액트.. 아니아니 넥스트입니다!</div>;
        }
      • import type { Metadata } from "next";
        import { Inter } from "next/font/google";
        import "./globals.css";

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

        export const metadata: Metadata = {
          title: "Sparta Next App",
          description: "This is awesome Website",
        };

        export default function RootLayout({

          ...기존코드 }
    • dynamic
      • dynamic route를 갖고있는 route에서 동적으로 변경되는 params를 기반으로 metadata를 변경 시 -> generateMetadata function 사용
        import React from "react";

        type Props = {
          params: {
            id: string;
          };
        };

        export function generateMetadata({ params }: Props) {
          return {
            title: `Detail 페이지 : ${params.id}`,
            description: `Detail 페이지 : ${params.id}`,
          };
        }

        const TestDetailPage = ({ params }: Props) => {
          return <div>Detail 페이지 : {params.id}</div>;
        };

        export default TestDetailPage;

페이지 이동과 관련된 기능 목록

  • Link : a태그로 만들어내므로 SEO가 유리
    • prefetching 지원 
      • 뷰포트에 링크가 나타나는 순간 해당 페이지의 코드와 데이터를 미리 가져오는 프리페칭 기능을 지원 -> 사용자가 링크를 클릭하기 전에 데이터를 미리 로드함으로써 사용자가 링크를 클릭했을 때 거의 즉시 페이지를 볼 수 있게 함
        • 뷰포트 ( Viewport) 는 사용자의 웹 브라우저에서 현재 보이는 부분을 의미
    • route 사이에 client-side navigation 지원
      • 브라우저가 새 페이지를 로드하기 위해 서버에 요청을 보내는 대신, 클라이언트 측에서 페이지를 바꾸어 주기 때문에 페이지 전환 시 매우 빠른 사용자 경험(UX)을 제공
      • 페이지의 HTML을 서버에서 다시 가져올 필요 없이, 필요한 JSON 데이터만 서버로부터 가져와서 클라이언트에서 페이지를 재구성하여 렌더링
  • useRouter : 자동 완성 시 next/navigation으로 해야함
    • 사용 시 항상 코드 최상단에 use client를 삽입해야 함
    • a 태그를 알아차릴 수 없기 때문에 크롤러 입장에서는 해당 요소가 ‘이동을 원한다’라는 것을 알 수 없음 ⇒ SEO 불리
    • 대부분 onClick 같은 이벤트 핸들러에서 사용
    • 클릭 후 로직의 순서에 따라 실행하므로, 즉시 이동이 아님
      "use client";

      import Link from "next/link";
      import { useRouter } from "next/navigation";
      import React, { useEffect } from "react";

      const TestTemplate = ({ children }: { children: React.ReactNode }) => {
        const router = useRouter();

        useEffect(() => {
          console.log("최초 렌더링 시에만 호출됩니다.");
        }, []);

        return (
          <div className="m-8 p-8 bg-white">
            <h1>테스트 페이지</h1>
            <p>테스트 경로 하위에서의 이동을 확인해봅니다.</p>
            <nav>
              <ul>
                <li
                  onClick={() => {
                    router.push("/test");
                  }}
                >
                  테스트 페이지
                </li>
                <li
                  onClick={() => {
                    router.push("/test/1");
                  }}
                >
                  테스트 페이지 1
                </li>
                <li
                  onClick={() => {
                    router.push("/test/2");
                  }}
                >
                  테스트 페이지 2
                </li>
              </ul>
            </nav>
            {children}
          </div>
        );
      };

      export default TestTemplate;
    • history stack
      • 방문자의 페이지 방문 순서를 기록하는 시스템 -> 웹 사이트 내에서 페이지 이동 시 URL이 추가됨

        1. router.push
          • 새로운 URL을 히스토리 스택에 추가.
          • 사용자가 router.push로 페이지를 이동하면, 이동한 페이지의 URL이 히스토리 스택의 맨 위에 추가.
          • 이후 사용자가 브라우저의 '뒤로 가기' 버튼을 클릭하면, 스택에서 가장 최근에 추가된 URL로부터 이전 페이지(URL)로 돌아감.
        2. router.replace
          • 현재 URL을 히스토리 스택에서 새로운 URL로 대체
          • 현재 페이지의 URL이 새로운 URL로 교체되며, '뒤로 가기'를 클릭했을 때 이전 페이지로 이동하지만, 교체된 페이지로는 돌아갈 수 없음.
          • 현재 페이지를 히스토리에서 완전히 대체.
        3. router.back
          • 사용자를 히스토리 스택에서 한 단계 뒤로 이동.
          • 마치 브라우저의 '뒤로 가기' 버튼을 클릭한 것과 같은 효과를 내며, 사용자를 이전에 방문했던 페이지로 돌아감.
        4. router.reload
          • 현재 페이지를 새로고침.
          • 히스토리 스택에 영향을 미치지 않음. 페이지의 데이터를 최신 상태로 업데이트하고 싶을 때 사용.

Tailwind CSS

세팅 : create-next-app을 할 때 이미 세팅해 놓았음

https://tailwindcss.com/

 

Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.

Tailwind CSS is a utility-first CSS framework for rapidly building modern websites without ever leaving your HTML.

tailwindcss.com

위 사이트에서 디자인 검색 후 React 컴포넌트의 className에 삽입 시 적용