Next.js 기초

정소현·2024년 9월 25일

Next.js

목록 보기
1/5

React로 만들어진 FrameWork

Next.js로 만든 high-Quality 웹사이트
1. 성능

  • 서버 사이드 렌더링 및 정적 사이트 생성
  • 코드 스플리팅 및 이미지 최적화
  1. SEO
  • 서버 사이드 렌더링을 통해 웹페이지의 콘텐츠가 초기 로드 시 완전히 렌더링 되므로, 검색엔진이 콘텐츠를 쉽게 인덱싱할 수 있따.
  1. 개발자 경험
  • 자동 코드 분할, 핫 리로딩, 타입스크립트 지원 등 개발자가 생산성을 높일 수 있는 다양한 기능을 지원
  • API라우트 기능으로 백엔드와의 통합을 쉽게 하고, 환경 설정이 적은 개발 환경을 제공한다.
  1. 확장성
  • 서버리스 함수와 데이터 페칭 기능을 활용해 유연한 확장성을 제공
  1. 유연성
  • React와 완전히 호환되며 원하는 다양한 툴 및 라이브러리와 함께 사용할 수 있음

🌟 라이브러리와 프레임워크의 차이

  1. 프레임워크
  • 개발자가 기능 구현에만 집중할 수 있도록 필요한 모든 프로그래밍적 재원을 지원하는 기술의 조합
    ex) Vue.js, Angular.js : Javascript 기반 웹 프론트엔드 SPA프레임워크
  1. 라이브러리
  • 공통 기능의 모듈화가 이루어진 프로그램의 집합
    (모듈화: 기능단위로 분해하고 추상화 되어 재사용 가능한 수준으로 만들어진 단위)
  • React.js
  • react-router-dom
  • redux

Next.js에서 Routing을 구현하기 위해서는?

라우팅을 위한 제어를 미리 해주고 있기 때문에 사용방법에 맞추어 사용하기만 하면 된다.


Next.js의 기법

  1. 렌더링 방식
  • 기본 SPA라이브러리에서 사용하던 CSR에서 벗어나 SSR, ISR, SSG등을 가능하게 함
  1. 코드 스플리팅(Code splitting)
  • 코드 스플리팅 지원으로 초기 웹 페이지 로딩이 빠르다.
    일반적인 웹 사이트는 방문하지 않는 페이지까지 한번에 다운로드 받아서 처리하기 때문에 최초 view 가 오래걸린다 => TTV(Time To View)
  • TTV 향상되고, 특정 페이지에 오류가 있어도 나머지는 작동한다.

단점

  • 복잡한 백엔드 로직은 아직 구현 불가 :WebSocket, WebRTC
  • FE로직과는 종속성, 백엔드 로직만 변경해서 배포해야하면 프론트엔드도 함께 배포해야함
  • 프론트엔드 서버의 부하 증가

☘️ App Router와 Pages Router의 차이

: Next.js는 폴더구조를 기반으로 한 라우팅

  1. pages router pages/:pages폴더에 원하는 페이지의 파일 이름을 둔다.
  2. ap Router app/ : app폴더 밑에 폴더명을 기반으로 자동 라우팅이 된다.

Next.js 실습(폴더기반 라우팅)

Next.js 설치

npx create-next-app@latest
  1. Tree
  • 계층 구조를 시각적으로 잘 보기 위한 규칙
  1. Subtree
  • tree의 한 부분
  • root부터 시작해서 leaf 들에 이르기까지의 범위
  1. Root
  • Tree또는 Subtree의 첫 번쨰 노드
  • root layout 처러 가장 첫번째에 있는 노드, 요소를 말한다.

URL관련 용어정리

ex)
acme.com / dashboard / settings
Domain/segment/segment
Domain/Path

  • URL Segment
  • / 로 분류된 URL path의 한부분
  • URL Path, Pathname
  • 도메인 이후 따라오는 URL부분을 뜻한다.

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

  1. Link
  • link태그는 기본 HTML의 a태그를 확장한 개념이다. 아래 두 가지 역할 때문에 a보다는 link태그를 이용해야한다.
    - Link 태그는 prefetching을 지원한다
    prefetching: 뷰포트에 링크가 나타나는 순간 해당 코드의 데이터를 미리 가져온다.
    - Link 태그는 route 사이에 client-side navigation을 지원한다. 필요한 부분만 업데이트하는 형태로 navigation을 지원

import Link from "next/link";

export default function Home() {
  return (
    <div>
      안녕하세요 Next.js입니다.
      <Link href={"/test"}>Test로 이동하기</Link>
    </div>
  );
}
  1. Router(useRouter)
  • useRouter를 사용할 때는 항상 코드 최상단에 "use client"를 삽입해야한다.
"use client";

import { useRouter } from "next/navigation";

export default function Test () {
	const router = useRouter();
	
	const handleButtonClick = () => {
		로직1();
		로직2();
		
		...
		
		router.push("/new_location");
	}

	return <button onClick={handleButtonClick}>클릭!</button>
}

🌟 Next Method

: 브라우저는 history stack을 차곡차곡 쌓아준다.
1. router.push

  • 새로운 URL을 히스토리 스택에 추가
  1. router.replace
  • 현재 URL을 히스토리 스택에서 새로운 URL로 대체한다.
  1. router.back
  • 사용자를 히스토리 스택에서 한 단계 뒤로 이동시킨다.
  1. router.reload
  • 현재 페이지를 새로고침한다.
  • 페이지의 데이터를 최신 상태로 업데이트하고 싶을 때 사용한다.

특별한 예약 파일들

  1. layout
  • 어떤 segment와 그의 자식 노드에 있는 요소들이 공통적으로 적용 받게 될 UI를 정의한다.
    동일 layout안에서 다른 경로를 계속해서 왔다갔다 할 때 re-rendering이 일어나지 않는다.
    ex) headers, footers, sidebars처럼 유저가 경로를 마음껏 탐색하고 다녀도 굳이 상관없는 것들에 적용
  • 만드는 방법
    1. 특정 segment이하의 route에서 적용 받을 layout UI를 해당 폴더 안에서 만듬
    1. children을 포함 시켜서 공통 UI를 만듬
      3. root에 있는 layout.tsx는 지우면 안됨

=> 원하는 스코프에 layout을 적용하기 위해 사용할 수 있다.

const AdminLayout = ({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) => {
  return (
    <>
      <h1>admin페이지 입니다.</h1>
      {children}
    </>
  );
};

export default AdminLayout;

  1. templete
    templete은 layout과 비슷하게 작동하지만 layout은 navigation할 때 불필요한 렌더링을 하지않지만 templete은 페이지의 모든 부분을 재렌더링함

Not found 404 page

Next.js는 기본적으로 제공해준다.
1. 직접 만들고 싶다면 src>app 에서 not-found.tsx 파일을 만들어줌
2. 내부를 커스텀


Metadata

  1. 정적인 메타데이터 적용
export const metaData: Metadata = {
  title: "안녕하세요 About Page입니다.",
  description: "Generated by create next app",
};

page.tsx에서도 각각의 페이지에 맞게끔 똑같이 사용가능

  1. 동적인 메타데이터 사용
import { Metadata } from "next";
import Link from "next/link";

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

export const metaData: Metadata = ({ params }: Props) => {
  return {
    title: "안녕하세요 About Page입니다.",
    description: "Generated by create next app",
  };
};

🌟 Tailwind CSS

기본 단위인 1이 0.25rem(4px)을 의미
1rem = 16px


React Server Component & Client Component

useEffect 등 client에서 실행되어야 하는 훅들을 사용할 때는
use client를 꼭 작성해줘야 사용할 수 있다.
use client를 사용하게 되면 dom tree 로 아래에 적혀있는 것들이 모두 적용되게 된다.

"use client"; // 꼭 작성해줘야 함

import { useEffect } from "react";

export default function Home() {
  useEffect(() => {}, []);
  return <div>안녕하세요 홈입니다.</div>;
}

따라서 page.tsx에는

import ClientExample from "./component/ClientExample";

export default function Home() {
  console.log("안녕 난 서버 컴포넌트야");
  return (
    <div>
      안녕하세요 홈입니다.
      <ClientExample />
    </div>
  );
}

내부로직에 있는 ClientExample 컴포넌트에

"use client";
import React, { useEffect } from "react";

const ClientExample = () => {
  useEffect(() => {
    console.log("안녕, 난 클라이언트 컴포넌트여");
  }, []);
  return <div>ClientExample</div>;
};

export default ClientExample;

넣어주게 된다면 page.tsx의 console.log는 vscode terminal(server)에서 ClientExample의 console.log는 client에서 실행된다.


Next.js에서는 SPA에서 모든 페이지를 렌더링 해오기전까지 빈화면이 나타나는 현상을 보완하기 위해

🌟 4가지 주요 렌더링 기법을 제시했다.

  1. CSR
  • 브라우저에서 JavaScript를 이용해 동적으로 페이지를 렌더링하는 방식
  • 렌더링의 주체 : 클라이언트
    <장점>
  • (최초로드 후)사용자와의 상호작용이 빠르고 부드러움
  • 서버에게 추가적인 요청을 보낼 필요가 없기 때문에 사용자 경험이 좋음
  • 서버 부하가 적음
    <단점>
  • 첫 페이지 로딩 시간이 길 수 있다.
  • JavaScript가 로딩되고 실행될 떄마다 페이지가 비어있어 검색 엔진 최적화에 불리하다.
  1. SSG (정적 렌더링)
  • 서버에서 페이지를 렌더링하여 클라이언트에게 HTML을 전달하는 방식
  • 최초 빌드시에만 생성이 됨
    <장점>
  • 첫 페이지 로딩 시간이 매우 짧아 사용자가 빠르게 페이지를 볼 수 있다.
  • CDN캐싱 가능
    <단점>
  • 정적인 데이터에만 사용가능
  • 사용자와의 상호작용이 서버와의 통신에 의존함
  1. ISR
  • SSG처럼 정적 페이지를 제공
  • 설정한 주기만큼 페이지를 계속 생성해 줌
  • 정적 페이지를 먼저 보여주고, 필요에 따라 서버에서 페이지를 재생성하는 방식
    <장점>
  • 정적 페이지를 먼저 제공하므로 사용자 경험이 좋으며, 콘텐츠가 변경되었을 때 서버에서 페이지를 재생성하므로 최신 상태를 유지 가능
    <단점>
  • 동적인 컨텐츠를 다루기에 한계가 있을 수 있다.
  • 마이페이지 처럼 데이터에 의존하여 화면을 그려주는 경우 사용 불가
  1. SSR (server side Rendering)
  • 렌더링의 주체가 서버
  • 클라이언트의 요청 시 렌더링
    <장점>
  • 빠른 로딩 속소 (TTV)
  • SEO최적화 좋음
  • 실시간 데이터를 사용
    <단점>
  • 사이트의 콘텐츠가 변경되면 전체 사이트를 다시 빌드해야 하는데, 이 과정이 오래 걸릴 수도 있음 => 서버 과부하
  • 요청할 떄마다 페이지를 만들어야 함

SSG로 변경하는 법

// SSG

export default async function Home() {
  const res = await fetch("http://localhost:4000/products", {
    cache: "force-cache", 
  });

SSR로 변경하는 법

export default async function Home() {
  const res = await fetch("http://localhost:4000/products", {
    cache: "no-store",  
  });

client rendering

"use client";
import { useEffect, useState } from "react";
import { Product } from "../page";

const fetchData = async () => {
  const res = await fetch("http://localhost:4000/products");
  const data: Product[] = await res.json();

  return data;
};
const ProductList = () => {
  const [data, setData] = useState<Product[]>([]);

  useEffect(() => {
    console.log("render");
    fetchData().then(setData);
  }, []);
  return (

ISR로 변경하는 법

export default async function Home() {
  const res = await fetch("http://localhost:4000/products", {
    next: {
      revalidate: 3,
    },
  });
  const data: Product[] = await res.json();

Suspense, Loading UI

  1. loading UI

src>app에서
loading.tsx생성

export default function Loading() {
  return <>Loading...</>;
}
  1. Streaming SSR
  • 데이터를 부분적으로 스트리밍하여 사용자가 페이지를 더 빨리 볼 수 있게 된다.
  • suspense로 컴포넌트를 감싸주고 fallback에 만든 loading페이지를 넣어주어 로딩 상황때 화면이 뜨게 각각 설정해준다.
import { Suspense } from "react";
import NewProductList from "./component/newProductList";
import ProductList from "./component/ProductList";
import Loading from "./loading";

export default async function Home() {
  return (
    <div>
      <Suspense fallback={<Loading />}>
        <NewProductList />
      </Suspense>
      <Suspense fallback={<Loading />}>
        <ProductList />
      </Suspense>
    </div>
  );
}
  1. Error
  • 사용하고자 하는곳에서 error.tsx 파일생성
  • "use client"로 클라이언트 모드로 변경시켜서 로직 작성
"use client";

import { useEffect } from "react";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

☘️ 에러가 클라이언트 컴포넌트여야하는 이유!
class컴포넌트에서 제공하는 라이프 사이클을 사용하기 위해서!

  • 가까운 상위 에러바운더리로 오류가 전달된다.
  • 즉, erros.js파일은 동일한 라우트 세그먼트의 layout.js또는 template.js컴포넌트에서 발생하는 오류를 처리해주지 않는다. 이러한 오류를 처리하기 위해 레이아웃의 부모 세그먼트에 error.js파일을 추가해야한다.
  • global-error.js를 사용하여 처리
  • src>app에 global-error.tsx 설정
  • global-error는 dev모드에서 보이지 않는다. build에서만 가능
"use client";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  );
}

error발생후 다시 성공 UI불러올 수 있게끔

"use client";

import { useRouter } from "next/router";
import { startTransition, useEffect } from "react";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  const { refresh } = useRouter();

  return (
    <div>
      <h2>Something went wrong!</h2>
      <button
        onClick={() =>
          startTransition(() => {
            refresh();
            reset();
          })
        }
      >
        Try again
      </button>
    </div>
  );
}
profile
기술을 넘어 제품의 가치를 만드는 프론트엔드 엔지니어를 지향합니다.

0개의 댓글