App Router

LEE GYUHO·2024년 1월 19일
0
post-thumbnail

App Router

  • app/ 폴더로 기존 파일 시스템 라우팅을 구현할 수 있으며, 여기의 page.js가 해당 경로(라우팅)의 페이지 컴포넌트가 됨

Data Fetching

  • fetch() Web API를 사용할 수 있게 되어, 컴포넌트 레벨에서도 SSR 적용 가능
  • React는 fetch() API의 중복제거를 제공하며, Next.js는 캐싱과 재요청 처리까지 지원하려고 함.
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
fetch(URL, { cache: 'force-cache' }); //ssg

// This request should be refetched on every request.
// Similar to `getServerSideProps`.
fetch(URL, { cache: 'no-store' }); //ssr

// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
fetch(URL, { next: { revalidate: 10 } }); // isr(Incremental Static Regeneration)

Layout

  • app/ 디렉토리를 생성하여 라우팅을 설정할 수 있다.
  • 공통적인 레이아웃 UI를 children을 감싸는 컴포넌트 형태로 제공. 이를 통해 공통 레이아웃의 상태를 유지하고, 불필요한 리렌더링을 방지할 수 있으며, 컴포넌트 간 상호작용 향상
  • 폴더 경로 안에 layout.js 컴포넌트를 추가하면 공통 레이아웃 적용 가능
// app/page.js
import Image from 'next/image';

export default function Home() {
  return (
    <>
      <h2>Welcome</h2>
      Hello, WEB!
    </>
  );
}

// app/layout.js
import Link from 'next/link';
import './globals.css';
import Image from 'next/image';

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <h1>
          <Link href='/'>WEB</Link>
        </h1>
        <ol>
          <li>
            <Link href='/read/1'>html</Link>
          </li>
          <li>
            <Link href='/read/2'>css</Link>
          </li>
        </ol>
        {children}
        <ul>
          <li>
            <Link href='/create'>Create</Link>
          </li>
          <li>
            <Link href='/update/1'>Update</Link>
          </li>
          <li>
            <input type='button' value='delete' />
          </li>
        </ul>
      </body>
    </html>
  );
}


Server Component

  • app 디렉토리 내 파일은 디폴트로 서버 컴포넌트로 동작함
  • React 서버 컴포넌트를 사용하면, 컴포넌트를 정기적으로 다시 가져올 수 있다. 새 데이터가 있을 때 리렌더링 되는 컴포넌트가 서버에서 실행되므로 클라이언트에 전송되는 코드의 양을 제한 할 수 있다.
  • 서버 컴포넌트는 복잡한 인터페이스를 구축하는 동시에 클라이언트로 전송되는 JavaScript의 양을 줄여, 초기 페이지 로드 속도를 높일 수 있음
    (참고자료 https://ui.toast.com/weekly-pick/ko_20210119)

Client Component

  • 만약 app directory 내부에서 클라이언트 컴포넌트를 사용하고 싶다면 파일 최상단에 use client라는 directive를 명시해야 함
    • useState, useEffect 훅을 사용하는 경우
    • 특정 브라우저 API에 의존성이 있는 경우
    • 특정 Event Listeners를 추가하는 경우

서버 컴포넌트 vs 클라이언트 컴포넌트

  • 컴포넌트가 렌더링 되는 장소가 서버인지 클라이언트인지의 차이.
  • 서버 컴포넌트는 서버에서 한 번 해석 된 후 클라이언트로 전달됨
  • 클라이언트 컴포넌트는 클라이언트가 js번들을 다운로드 받은 후 (hydration) 해석하게 됨

  • Server component
    • 데이터 fetching
    • 백엔드 자원에(직접적으로) 접근
    • 민감한 정보를 서버에서 유지(JWT, API Key 등등)
    • large dependencies를 서버에서 유지/클라이언트 JS 번들 사이즈 감소
  • Client component
    • interactivity, event listener(onClick 등)가 필요할때
    • state 및 라이프사이클이 필요할 때
    • browser-only API 사용
    • custom hook depend on state or browser-only API 사용

자동 코드 분할(Code-splitting)

  • 코드 스플리팅: 애플리케이션의 자바스크립트 코드를 여러 개의 작은 부분으로 나누어 로딩하는 기술. 초기 로딩 시간을 최적화하고 성능을 향상시킬 수 있는 장점이 있다.

  • next.js app router는 기본적으로 코드 스플리팅을 제공하지만 이는 페이지 단위의 코드 스플리팅입니다.

  • 코드 분할(Code Splitting)은 싱글 페이지 애플리케이션의 성능을 향상시키는 방법이다. 싱글 페이지 애플리케이션(Single Page Application)은 초기 실행시에 필요한 웹 자원을 모두 다운 받는 특징이 있다. 코드 분할을 활용하게 되면 초기 로딩시에 모든 웹 자원을 다운받지 않고 필요한 시점에 다운 받아 성능 상의 이점이 생긴다.

  • 단점

    • (Next.js 같은)메타 프레임워크 외부에서는, import 구문을 dynamic import 구문으로 대체해 최적화 작업을 수동으로 처리해야 하는 경우가 많다.
    • 유저 경험에 영향을 주는 컴포넌트를 로드하기 시작하면 지연될 수 있다.

dynamic 함수로 동적 import와 일반적인 import의 차이

// pages/index.js

import React, { useState } from 'react';
import dynamic from 'next/dynamic';

// 방식 1: dynamic 함수 사용 (동적 임포트)
const DynamicComponent = dynamic(() => import('../components/SomeComponent'));

// 방식 2: 일반적인 import 문 사용
import SomeComponent from '../components/SomeComponent';

function HomePage() {
  const [showDynamicComponent, setShowDynamicComponent] = useState(false);

  // 예시: 버튼 클릭 시 동적 컴포넌트 로딩
  const loadDynamicComponent = () => {
    setShowDynamicComponent(true);
  };

  return (
    <div>
      <h1>Hello Next.js</h1>

      {/* 방식 1: dynamic 함수 사용 */}
      {showDynamicComponent && <DynamicComponent />}

      {/* 방식 2: 일반적인 import 문 사용 */}
      <SomeComponent />

      {/* 버튼 클릭 시 동적 컴포넌트 로딩 */}
      <button onClick={loadDynamicComponent}>Load Dynamic Component</button>
    </div>
  );
}

export default HomePage;

위 코드에서 는 dynamic 함수로 import하였고 버튼을 클릭했을 때만 로딩되도록 하였습니다. dynamic함수를 사용하여 명시적으로 코드 스플리팅을 함으로써 컴포넌트 단위에서 더욱 세밀하게 초기 로딩 속도를 줄일 수 있습니다.

Streaming

  • app/ 디렉토리는 UI의 렌더링 단위를 점진적으로 렌더링하고 렌더링된 단위를 클라이언트로 부분적으로 스트리밍할 수 있는 기능을 제공
  • SSR로 렌더링을 하면 블로킹이 될 수 있음.
  • 이를 개선하기 위해 스트리밍으로 페이지의 HTML을 작은 청크로 분할하고, 서버에서 클라이언트로 점진적으로 청크를 전송할 수 있음. 이를 통해 UI를 렌더링하기 전에 모든 데이터를 기다릴 필요 없이 페이지의 일부를 더 빨리 표시할 수 있음
  • loading.js 파일을 만들어서 트리밍 형태의 로딩 UI를 생성할 수 있음. loading.js 파일을 생성하면 React Suspense를 자동으로 래핑하여 로딩 화면을 보여주고, 라우트 세그먼트의 내용을 로드하는 동안 서버에서 즉시 로딩 상태를 표시하고, 렌더링이 완료되면 자동으로 새로운 콘텐츠로 교체됨
/* app/dashboard/loading.tsx */

export default function Loading() {
  // You can add any UI inside Loading, including a Skeleton.
  return <LoadingSkeleton />;
}
  • 같은 폴더에서 loading.js는 layout.js 안에 중첩됨. 경계에서 page.js 파일과 그 아래의 모든 자식을 자동으로 래핑함

파일 규칙

Next.js는 중첩된 라우트에서 특정 동작을 가진 UI를 생성하기 위해 일련의 특별한 파일을 제공함

  • page.js: route에 대한 UI를 생성하고 접근가능하게 한다.
  • route.js: route에 대해 server-side API 엔드포인트를 생성한다.
  • layout.js: segment와 children들에 대해 공통 UI를 생성한다. layout이 page나 child를 감싸는 형태
    • template.js: layout.js랑 비슷한데 새로운 component instance가 이동시 mount 된다고 한다.(언제 필요한거지?) 이게 필요없으면 layout쓰면 됨
  • loading.js: segment와 children들에 대해 로딩 UI를 생성한다. 요건 Suspense랑 세트다. Suspense Boundary내에서 page나 child를 감싸고 로드 중에 로딩 UI를 보여준다.
  • error.js: 로딩이랑 똑같다. error UI를 생성한다. 이건 Error Boundary랑 세트다. 에러 발생시 이걸 보여준다.
    • global-error.js: 이건 error랑 똑같은데 root의 layout.js에 대한 에러를 catch하기 위해서 사용한다.
  • not-found.js: notFound 함수가 route segment에서 throw됐을때나 URL에 일치하는 route가 존재하지 않을때 표시한다.


Image 컴포넌트

  • Next.js의 Image 컴포넌트는 다음의 항목들을 자동으로 최적화한다.
    • 이미지가 로딩되는 동안 layout shift를 방지한다.
    • viewport에 따라 이미지의 크기를 조정한다.
    • Lazy loading를 기본적으로 제공한다. (사용자의 viewport에 들어왔을 때 이미지가 로드된다.
    • WebP, AVIF와 같은 포맷으로 이미지를 제공한다.

Route Groups

  • 일반적으로 앱 디렉토리에서는 중첩된 폴더가 URL 경로에 매핑됨. 하지만, 폴더를 Route 그룹으로 표시하여 해당 폴더가 경로의 URL 경로에 포함되지 않도록 할 수 있음
  • Route Group을 통해 URL 경로 구조에 영향을 주지 않고, 라우트 세그먼트와 프로젝트 파일을 논리적으로 구성할 수 있음
  • 컨벤션: 라우트 그룹은 폴더 이름을 괄호로 묶음으로써 생성할 수 있음 (folderName)
  • 아래와 같은 상황에서 Route Group 활용 가능:
    • 사이트 섹션, 목적별 또는 팀별로 경로를 구성 할 때 (Grouping)
    • 동일한 라우트 세그먼트 수준에서 중첩된 레이아웃을 사용할 때
      • 여러 루트 레이아웃을 포함하여 동일한 세그먼트에 여러 중첩 레이아웃 만들기
    • 특정 세그먼트를 레이아웃으로 선택 할 때
  • 각각의 Route Group 마다 같은 URL 계층을 가져도, 다른 layout을 적용할 수 있음.
    • 아래 예시처럼 (marketing), (shop)은 app 하단의 최상위 루트지만, Route Group을 이용해서 별개의 레이아웃 구성할 수 있음.

Conditional Routes

  • 병렬 경로를 사용하면 인증 상태와 같은 특정 조건에 따라 조건부로 슬롯을 렌더링할 수도 있습니다. 예를 들어 사용자의 로그인 여부에 따라 /dashboard 또는 /login 페이지를 렌더링할 수 있습니다.
import { getUser } from '@/lib/auth'
 
export default function Layout({
  dashboard,
  login,
}: {
  dashboard: React.ReactNode
  login: React.ReactNode
}) {
  const isLoggedIn = getUser()
  return isLoggedIn ? dashboard : login
}

profile
누구나 같은 팀으로 되길 바라는 개발자가 되자

0개의 댓글