2024.03.11 TIL - Next js 강의 정리

Innes·2024년 3월 11일
0

TIL(Today I Learned)

목록 보기
86/147
post-thumbnail

Next js 강의 정리

CSR, SSR

  1. CSR : Client Side Rendering
  • 지금까지 공부한 리액트로 웹페이지 구축하기
  1. SSR : Server Side Rendering
  • 전통적 웹페이지 : SSR + MPA 구축

주요 렌더링 기법

  1. CSR(Client Side Rendering)
  • build -> Static Server -> Runtime(Web Browser)
  • 첫 페이지 로딩시간이 오래걸림(TTV : Time To View)
  • 서버 부하가 적음
  • SEO에 불리(검색 엔진 최적화) : js가 로딩되고 실행될때까지 페이지가 비어있기 때문
  1. SSG(Static Site Generation)
  • 정적인 렌더링 기법 (정적이다 === 더이상 변하지 않는다!)
  • 최초 빌드시에만 생성됨
  • 서버가 주는대로 클라이언트는 표기만 함
  • TTV 매우 짧음
  • SEO에 유리(웹페이지가 온전한 형태로 남아있어서)
  • CDN 캐싱 가능
    (Content Delivery Network - 컨텐츠를 중앙에서 관리하는 네트워크 서비스)
  • 정적인 데이터에만 사용할 수 있음(갱신되어야할지라도 절대 안변함)
  • 서버 부하가 상대적으로 큼
  • 마이페이지 같이 데이터에 의존하는 화면같은 경우 절대 불가 ㄷㄷ
  1. ISR(Incremental Static Regeneration)
  • 점직적으로 static을 다시 만들어내는 것
  • SSG의 확장 개념
  • SSG인데 가끔 다시만들어준다 정도
  • 설정한 주기만큼 페이지를 계속 생성해줌
  1. SSR(Server Side Rendering)
  • static server가 아닌 app server에서 만들어서 web에 전달
  • 빌드 시점에 모든 페이지 미리 생성하여 서버 부하 줄임
  • SSG, ISR처럼 주체가 서버!
  • 실시간 데이터가 필요할때 사용
  • 서버부하 큼
  • SSR의 두 과정
    • pre-rendering : 껍데기 미리 그려놓기(TTV를 위한 기초 골격) - TTI 안됨(interation)
    • hydration : js(리액트)파일을 hydration하는 과정을 통해 interation이 가능해진다!

(출처 : 내일배움캠프 Next.js 특강 노션)

next.js 프로젝트 생성하기

npx create-next-app@latest

Routing

  1. static routing : 정적으로 페이지 생성하기 (localhost3000/test)
  • app 바로 밑에 폴더 생성

import React from "react";

const TestPage = () => {
  return <div>TestPage</div>;
};

export default TestPage;
  1. dynamic routing : 동적으로 페이지 생성하기 (test/:id)
  • app 바로 밑에 폴더 생성

import React from "react";

// ⭐️⭐️⭐️ props로 params를 가져오는것 참고!!
const TestDetailPage = ({ params }: { params: { id: string } }) => {
  return <div>TestDetailPage : {params.id}</div>;
};

export default TestDetailPage;
  1. route groups : 그냥 라우팅 안되는 일반 폴더 만들고 싶을땐?
  • (폴더명) : 이 폴더는 경로로 포함하지 않겠다!
  • ❌ localhost3000/admin/about
  • ✅ localhost3000/about

import React from "react";

const AdminAboutPage = () => {
  return <div>AdminAboutPage</div>;
};

export default AdminAboutPage;

layout 설정하기

  1. 특정 segment 이하의 route에서 적용받을 layout 만들기
  • app > (marketing) > layout.tsx
  • marketing폴더 안에있는 route들에서 공통적으로 보이게 될 layout
  • layout안에서는 children을 props로 받아서 marketing 안의 route들을 보여주면 된다.

import React from "react";

// ⭐️ children props로 받기
const MarketingLayout = ({ children }: { children: React.ReactNode }) => {
  return (
    <div>
      // 공통적으로 보여지는 부분은 children 외 나머지 부분
      <p>여기는 마케팅과 관련된 페이지가 보이는 곳입니다!</p>
      
      // ⭐️ children 보여주기
      {children}
    </div>
  );
};

export default MarketingLayout;

ex 1) localhost3000/business

ex 2) localhost3000/contact

  1. 프로젝트 전체에서 노출되는 공통 layout
  • root에 있는 layout에 작성하면 됨
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 - 하위 경로들이 전부 보일 부분
        {children}
      </body>
    </html>
  );
}

📝 layout과 유사한 컴포넌트인 template.tsx!

  • layout : 경로 전반에 걸쳐서 상태가 유지됨 (최초 렌더링시 이후에는 바뀌지 x)
  • template : 동일한 Template을 공유하는 경로 사이를 왔다갔다 할 때 DOM 요소가 다시 생성됨
    • open animation : template이 적합
  • index.html 같은 곳에서 header 수정했다면, Next.js에서는 layout.tsx의 metadata의 title에서 수정할 수 있다. & 다른 경로마다 다른 metadata title을 지정할 수도 있다! (각 경로의 layout 혹은 각 컴포넌트마다 설정 가능)
export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};
  1. Link
  • prefetching 지원
  • client-side navigation 지원
  • a태그를 만들어내기 때문에 SEO에 유리
  • 즉시이동
  1. useRouter
  • "use client" 삽입 필요
  • onClick같은 이벤트 핸들러에서 사용 가능
    (user와 상호작용 할때 많이 사용)
  • SEO에는 불리(a태그 생성하지 않으니까)
  • 즉시이동은 아님
  • import할때 주의 ⚠️ : next/router, next/navigation 중 navigation 선택해야 함!!! ⭐️
const router = useRouter(); //⭐️ import는 next/navigation에서!

// 생략
// ⭐️ router.push("경로")
<li onClick={()=>{ router.push("/test/2") }}>테스트</li>

// 생략
  • router.push("") : history stack이 쌓임
  • router.replace("") : history stack이 없어짐(모든 history 초기화)
    (ex. 로그아웃 시는 replace)

tailwind css

https://tailwindcss.com/

  • 정해져있는 className를 활용할 수 있게 도와주는 패키지
  • className들은 띄어쓰기로 구분함
  • 사이트에서 원하는 css기능 검색해서 className 찾아 사용하면 끝!

Rendering - CSR, SSR, ISR, SSG

  1. 클라이언트 컴포넌트와 서버 컴포넌트
  • Next.js 버전 12까지 : 렌더링 방식을 '페이지 단위'로 규정

  • Next.js 버전 13 이후 : '컴포넌트 단위'로 규정

    • 서버 컴포넌트(서버상에서만 동작)
    • 클라이언트 컴포넌트(브라우저에서만 동작)
      -> 한 페이지 안에서도 여러 종류의 렌더링이 일어날 수 있게 됨!
      (컴포넌트 별로 다른 렌더링방식일 수 있음)
  • Next.js 13버전부터 컴포넌트들은 default로 서버 컴포넌트다!!

  • "use client"; 표시 없으면 전부 서버 컴포넌트라는 뜻!

    • ex1) console.log("hi") : vscode의 터미널에서 뜸!!! (node.js)
    • ex2) "use client"; console.log("hi") : 브라우저 개발자도구에서 뜸!! (브라우저 환경)

📝 어떨때 클라이언트 컴포넌트("use client")로 써야하고, 어떨때 서버 컴포넌트로 써야할까?

  • user와의 상호작용이 있는 경우 : 클라이언트 컴포넌트
    (ex. onClick, useState 등 사용할 때, counter 앱, 등등)
  • 그 외 : 서버 컴포넌트
  1. SSG(Static Site Generation)
  • fetch한 데이터는 영원히 변하지 않음!
  • 컴포넌트 갱신할 필요 X
  • 빌드타임때만 컴포넌트 생성, 후로는 변하지 않는 페이지에 적합
  • Next.js에서는 아무것도 추가해주지 않는 이상 SSG로 동작함
  • 🧡 아무리 새로고침해도 같은 데이터만 보게됨(데이터 업데이트 안됨)
// ✅ 예시 코드

import Image from "next/image";
import React from "react";

import type { RandomUser } from "@/types";

// SSG TEST : 아무것도 하지 않으면 SSG!
// ⭐️ 리액트 컴포넌트인데도 async 부여 가능! (Next.js니까!)
// 클라이언트 컴포넌트에서는 async 못붙여줌 (use client 붙이면 오류남)
const SSG = async () => {
  // (1) 첫 번째 방법 : fetch에 아무 옵션도 부여 x
  const response = await fetch(`https://randomuser.me/api`);
  const { results } = await response.json();
  const user: RandomUser = results[0];

  return (
    <div className="mt-8">
      <div className="border p-4 my-4">
        <div className="flex gap-8">
          {/* 유저 기본정보 */}
          <div>
            <Image
              src={user.picture.large}
              alt={user.name.first}
              width={200}
              height={200}
            />
            <h2 className="text-xl font-bold">
              {user.name.title}. {user.name.first} {user.name.last}
            </h2>
            <p className="text-gray-600">{user.email}</p>

            <div className="mt-4">
              <p>
                <span className="font-bold">전화번호 : </span>
                {user.phone}
              </p>
              <p>
                <span className="font-bold">휴대전화번호 : </span>
                {user.cell}
              </p>
              <p>
                <span className="font-bold">사는 곳 : </span>
                {user.location.city}, {user.location.country}
              </p>
              <p>
                <span className="font-bold">등록일자 : </span>
                {new Date(user.registered.date).toLocaleDateString()}
              </p>

              <p>
                <span className="font-bold">생년월일 : </span>
                {new Date(user.dob.date).toLocaleDateString()}
              </p>
            </div>
          </div>

          {/* 지도영역 */}
          <iframe
            src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
            height="450"
            width="600"
          ></iframe>
        </div>
      </div>
    </div>
  );
};

export default SSG;
  1. ISR(Incremental Site Regeneration)
  • fetch한 데이터가 가끔 변함
  • 일정 주기마다 가끔씩만 컴포넌트 갱신
  • 빌드타임때 초기 생성, 이후 일정 주기마다 변화 적용
  • 🧡 몇 초에 한번씩 데이터 업데이트됨
// ✅ 예시 코드 1. fetch에 옵션주기 - ISR.tsx에서

import Image from "next/image";
import React from "react";

import type { RandomUser } from "@/types";

const ISR = async () => {
  const response = await fetch(`https://randomuser.me/api`, {
    // ⭐️ 5초에 한번씩 데이터 업데이트하라고 옵션 주기!
    next: {
      revalidate: 5,
    },
  });
  const { results } = await response.json();
  const user: RandomUser = results[0];

  return (
    <div className="mt-8">
      <div className="border p-4 my-4">
        <div className="flex gap-8">
          {/* 유저 기본정보 */}
          <div>
            <Image
              src={user.picture.large}
              alt={user.name.first}
              width={200}
              height={200}
            />
            <h2 className="text-xl font-bold">
              {user.name.title}. {user.name.first} {user.name.last}
            </h2>
            <p className="text-gray-600">{user.email}</p>

            <div className="mt-4">
              <p>
                <span className="font-bold">전화번호 : </span>
                {user.phone}
              </p>
              <p>
                <span className="font-bold">휴대전화번호 : </span>
                {user.cell}
              </p>
              <p>
                <span className="font-bold">사는 곳 : </span>
                {user.location.city}, {user.location.country}
              </p>
              <p>
                <span className="font-bold">등록일자 : </span>
                {new Date(user.registered.date).toLocaleDateString()}
              </p>

              <p>
                <span className="font-bold">생년월일 : </span>
                {new Date(user.dob.date).toLocaleDateString()}
              </p>
            </div>
          </div>

          {/* 지도영역 */}
          <iframe
            src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
            height="450"
            width="600"
          ></iframe>
        </div>
      </div>
    </div>
  );
};

export default ISR;
// ✅ 예시 코드 2. page.tsx 컴포넌트에 revalidate 추가하기 - page.tsx에서
// (ISR.tsx에서 줬던 옵션은 지우고)

// 페이지에 포함된 모든 데이터가 5초마다 업데이트됨
// 페이지 전체가 업데이트되어야 할 때 유용

// src>app>rendering>page.tsx

import ISR from "@/components/rendering/ISR";
import React from "react";

export const revalidate = 5;

const RenderingTestPage = () => {
  return (
    <div>
      <h1>4가지 렌더링 방식을 테스트합니다.</h1>
      <ISR />
    </div>
  );
};

export default RenderingTestPage;
  1. SSR(Server Sider Rendering)
  • fetch한 데이터가 실시간으로 계속 바뀜
  • 컴포넌트 요청이 있을 때마다 데이터를 갱신
    -> 가장 최신 데이터만 user에게 제공
  • 🧡 데이터를 매번 갱신해서 최신 데이터만 보여줌!
// ✅ 예시 코드 - fetch에 'no-cache' 옵션주기!

// src>components>rendering>SSR.tsx
import Image from "next/image";
import React from "react";

import type { RandomUser } from "@/types";

const SSR = async () => {
  const response = await fetch(`https://randomuser.me/api`, {
    cache: "no-cache",
  });
  const { results } = await response.json();
  const user: RandomUser = results[0];

  return (
    <div className="mt-8">
      <div className="border p-4 my-4">
        <div className="flex gap-8">
          {/* 유저 기본정보 */}
          <div>
            <Image
              src={user.picture.large}
              alt={user.name.first}
              width={200}
              height={200}
            />
            <h2 className="text-xl font-bold">
              {user.name.title}. {user.name.first} {user.name.last}
            </h2>
            <p className="text-gray-600">{user.email}</p>

            <div className="mt-4">
              <p>
                <span className="font-bold">전화번호 : </span>
                {user.phone}
              </p>
              <p>
                <span className="font-bold">휴대전화번호 : </span>
                {user.cell}
              </p>
              <p>
                <span className="font-bold">사는 곳 : </span>
                {user.location.city}, {user.location.country}
              </p>
              <p>
                <span className="font-bold">등록일자 : </span>
                {new Date(user.registered.date).toLocaleDateString()}
              </p>

              <p>
                <span className="font-bold">생년월일 : </span>
                {new Date(user.dob.date).toLocaleDateString()}
              </p>
            </div>
          </div>

          {/* 지도영역 */}
          <iframe
            src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
            height="450"
            width="600"
          ></iframe>
        </div>
      </div>
    </div>
  );
};

export default SSR;
  1. CSR(Client Side Rendering)
  • fetch한 데이터가 실시간으로 계속 바뀜 22
  • 컴포넌트 요청이 있을 때마다 데이터를 갱신 22
  • But!!
    • 빌드타임에 컴포넌트를 초기 생성하지 않음
    • js로 이루어진 리액트 파일을 다운로드받고, 그제서야 화면에 그려짐
  • 별도의 설정 필요("use client")
  • 🧡 데이터를 매번 갱신해서 최신 데이터만 보여줌!
// ✅ 예시 코드 - "use client" 옵션 주기

"use client";

import Image from "next/image";
import React, { useEffect, useState } from "react";

import type { RandomUser } from "@/types";

const CSR = () => {
  const [user, setUser] = useState<RandomUser | null>(null);

  useEffect(() => {
    const fetchUser = async () => {
      const response = await fetch(`https://randomuser.me/api`);
      const { results } = await response.json();
      setUser(results[0]);
    };

    fetchUser();
  }, []);

  if (!user) {
    return <div>로딩중...</div>;
  }

  return (
    <div className="mt-8">
      <div className="border p-4 my-4">
        <div className="flex gap-8">
          {/* 유저 기본정보 */}
          <div>
            <Image
              src={user.picture.large}
              alt={user.name.first}
              width={200}
              height={200}
            />
            <h2 className="text-xl font-bold">
              {user.name.title}. {user.name.first} {user.name.last}
            </h2>
            <p className="text-gray-600">{user.email}</p>

            <div className="mt-4">
              <p>
                <span className="font-bold">전화번호 : </span>
                {user.phone}
              </p>
              <p>
                <span className="font-bold">휴대전화번호 : </span>
                {user.cell}
              </p>
              <p>
                <span className="font-bold">사는 곳 : </span>
                {user.location.city}, {user.location.country}
              </p>
              <p>
                <span className="font-bold">등록일자 : </span>
                {new Date(user.registered.date).toLocaleDateString()}
              </p>

              <p>
                <span className="font-bold">생년월일 : </span>
                {new Date(user.dob.date).toLocaleDateString()}
              </p>
            </div>
          </div>

          {/* 지도영역 */}
          <iframe
            src={`https://maps.google.com/maps?q=${user.location.coordinates.longitude},${user.location.coordinates.latitude}&z=15&output=embed`}
            height="450"
            width="600"
          ></iframe>
        </div>
      </div>
    </div>
  );
};

export default CSR;

Rendering 패넌 4가지 구현할 때, dev(개발서버)에서 하면 안됨!
(dev모드에서는 SSG로 해놔도 원활한 테스팅을 위해 SSR처럼 동작하기 때문)
-> build 한 다음 start 해서 열어야 제대로 볼 수 있음

profile
꾸준히 성장하는 우상향 개발자

0개의 댓글