NextJS

seunghye jo·2023년 4월 3일
0

React

목록 보기
6/6
post-thumbnail

NextJS💡

  • 리액트 프레임워크
  • 서버사이드렌더링 지원
    • 렌더링방식의 최적화
    • 사전렌더링 지원(build시에 미리 특정 페이지에 대한 HTML생성)
    • 빠른 로딩속도
  • 동적라우팅을 쉽게 사용할 수 있도록 지원
    • react-router-dom없이 자동으로 route 구성
  • api서버 구축 지원
  • 이미지, 폰트 최적화
    • 레이아웃 쉬프트가 일어나지 않도록 구성
    • layout shift- 이미지/컴포넌트가 뒤늦게 로드되어 레이아웃이 밀리는(변경되는) 현상
  • 타입스크립트 지원

🧡렌더링방식

✨CSR(Client Side Rendering)

  • 기본적으로 리액트와 같은 SPA(single page application)은 CSR(Client Side Rendering)방식으로 페이지 구현
  • 사용자에게 초기에 빈 HTML을 넘겨주고 사용자의 브라우저가 자바스크립트를 읽어내려가며 요소를을 채우게 됨

[장점]

  • 페이지 전환이 빠름
  • 새로고침 시 화면이 깜빡이는 Blinking lssue가 발생하지 않음
  • 서버에 부담이 적음

[단점]

  • 사용자가 첫 화면을 보기까지의 초기 로딩 시간이 오래걸림
    (자바스크립트가 실행되기 전까지 사용자는 빈화면을 봐야함)
  • html은 기본적으로 비어있는 상태이기때문에 검색엔진에 페이지의 정보가 원활하게 공급되지 않음

✨SSR(Server Side Rendering)

  • 사용자가 사이트에 접속하는 순간 서버는 HTML 요소를 채운 뒤 필요한 자바스크립트 코드와 함께 사용자에게 공급

[장점]

  • 페이지 초기 로딩이 빠름
  • 검색엔진 최적화

[단점]

  • 서버가 직접 요소를 그려서 사용자에게 공급해야하기 때문에 서버에 부하가 걸림
  • 사용자가 사이트를 볼 수 있는 순간과 해당 사이트와 인터렉션이 가능한 순간 사이의 시간차가 발생
    (요소가 그려진 html을 빠르게 받았지만, 자바스크립트가 실행되기 까지는 시간이 걸림)
  • 새로고침 시 다시 페이지를 서버에서 받아와야함 => 화면이 잠시 비워졌다가 다시 채워지는 Blinking Issue발생(화면 깜빡임 발생)

✨SSG(Static Site Generation): 정적 생성

  • Next.js의 핵심기능 중 하나
  • 사용자가 접속할 때 마다 서버가 직접 HTML을 그리는 것이 아닌, build 할 때 HTML을 미리 그린 뒤 저장해 두었다가 사용자 접속 시 빠르게 공급
  • 외부에서 데이터를 가져오지 않는 한 항상(기본적으로) SSG방식으로 사전렌더링을 진행

[장점]

  • CSR방식과 SSR방식의 장점을 흡수
  • build시에 HTML렌더링을 미리 진행했기 때문에 초기 로딩시간과 페이지전환이 모두 빠름
  • build시에 작성된 HTML문서 덕분에 기존 React의 CSR방식과 달리 검색엔진에 최적화되어있다

[단점]

  • build 시간이 오래걸림
  • build시에 사전 렌더링 된 데이터를 사용자에게 공급하기 때문에, 사용자가 오래 된 데이터를 보게 될 수 있음 (업데이트가 자주 발생하는 데이터라면, SSG보다는 SSR방식으로 렌더링 하도록 하는것이 좋음)

💡next.js앱을 yarn dev (npm run start)로 실행하는 경우 아직 build 되지 않은 상태의 앱이기때문에 SSG로 구현 된 코드라도 SSR방식으로 작동


🧡사용해보기

  • $ yarn create next-app (앱이름) --js

  • 프로젝트 생성 시 import alias가 기본적으로 설정되어, import 경로로 @(기본값)를 사용할 수 있다. ( @src/ 폴더를 뜻함 )

  • yarn dev로 프로젝트를 실행시키면 src/pages폴더 내부의 index.js가 실행 됨

  • index.js 는 실행 시 _app.js_document.js를 차례로 거친 뒤 실행

    • _app.js
      • 글로벌 스타일 혹은 앱에 공통적으로 사용하고자 하는 로직 등을 작성
    • _document.js
      • 전체 페이지에 걸쳐서 적용하고자 하는 Head 태그 (meta 태그, title 태그) 를 작성하거나, 웹사이트의 마크업 구조를 설정

🧡pages 폴더를 통한 라우팅

  • pages 폴더안에 파일이나 폴더를 만들면, 해당 파일/폴더의 이름을 따라 route가 자동생성
폴더경로Route비고
pages/post/index.jsx/postindex파일은 해당파일의 폴더명으로 경로가 연결됨
pages/post/detail.jsx/post/detailindex.jsx를 제외한 파일명은 폴더명의 루트 뒤에 연결 됨
pages/post/[id].jsx/post/${id}- 동적으로 경로 생성
- useRouter() 를 사용해서 경로의 id값을 가져올 수 있음
pages/post/[…params].jsx/post/*/*/*- 동적으로 경로 생성
- 경로의 깊이와 상관없이 모든 경로로 접근 가능
- useRouter()를 통해 각 경로를 배열형태로 받아올 수 있음

경로 우선순위
detail(경로명 직접명시) > [id](하나의 속성 명시) > [...params](나머지 경로)

🧡useRouter()

  • 경로와 관련한 데이터 또는 함수를 담고있는 객체를 반환
  import { useRouter } from 'next/router'

  const router = useRouter()

image

✨router.query

  • 경로에 담긴 데이터는 아래와 같이 꺼내 사용
// src/pages/post/[id].jsx
// 접근경로 '/post/1?name=hello'
import { useRouter } from 'next/router'

const router = useRouter()
const {id, name} = router.query

console.log(id) //'1'
console.log(name) //'hello'
  • [...params].jsx 를 활용해 만든 깊이가 있는 route에 접근하는 경우 해당 경로를 배열형식으로 반환
// src/pages/user/[...params].jsx
// 접근경로 '/user/123/abc'
import { useRouter } from 'next/router'

const router = useRouter()
const { params } = router.query

console.log(params) //['123', 'abc']

image

✨router.push()

  • <Link>태그를 사용하지 않고 함수내에서 페이지 전환을 하고 싶다면 router.push()사용
router.push(url)
  • 경로를 입력하거나, url객체를 사용할 수도 있음
<button onClick={() => router.push('/post/1')}>1번 글로가기</button>

<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 1 } })}>1번 글로가기</button>

✨router.isReady

  • 정적 최적화가 이루어진 페이지(외부데이터를 받아 getStaticProps() 를 사용한 페이지)의 경우 query가 빈 객체로 전달 된 뒤에 채워짐
  • 그런 경우 router.isReady를 사용하여, query가 준비 되면 그때 query 데이터에 접근 하도록 함
  • boolean값 반환
useEffect(() => {
  if (router.isReady) console.log(router.query)
}, [router.isReady])

🧡Link태그

  • 페이지 간 이동시에는 <a>태그 대신 <Link>태그를 사용해야 함
import Link from "next/link";
// 기본방식
<Link href='/post'>post로 이동</Link>

// 쿼리파라미터
<Link href='/post?name=hello'>post로 이동</Link>

// url객체
<Link href={{ pathname: '/post', query: { name: 'hello' } }}>post로 이동</Link>
  • Link태그는 해당 태그가 Viewport안에 들어오면 자동으로 해당 페이지를 미리 로드함(prefetch)
    • 정적생성방식(SSG)의 경우에 한함
    • SSR방식의 경우엔 Link태그를 클릭해야만 페이지가 로드 됨

🧡Image태그

  • 성능최적화를 위해 기존의 <img />태그가 아닌 <Image /> 태그를 사용

  • Image태그 특징

    • 자동으로 크기를 viewport에 맞춰줌
    • 사용자에게 보여질 때(viewport에 진입했을 때)에만 이미지를 로드하여 초기 로딩시간을 단축
    • 레이아웃 이동 최소화 (Cumulative Layout Shift 방지)
    • 디바이스의 종류에 따라 파일 크기를 조절하여 공급
import Image from 'next/image';

<Image
  src="/images/profile.jpg" // 이미지 주소, 경로
  height={200} // CLS 방지를 위해 사용됨
  width={200} // CLS 방지를 위해 사용됨
  alt="profile"
/>
  • width와 height를 직접 명시해주어야 함
    (이미지가 로드되기 전 해당 이미지의 크기를 먼저 계산하여 자리를 마련 => Layout Shift 방지)

✨fill

  • 이미지 크기를 모르는 경우 fill속성을 사용하면 부모요소의 크기에 맞춰짐
  • fill 속성을 사용하려면 부모요소는 position: relativedisplay: block속성을 가지고 있어야 함
<Image src="/images/profile.jpg" alt="profile" fill />

✨sizes

  • 기본적으로 Image태그를 사용하면 페이지에 접속하는 디바이스의 화면 너비에 따라 이미지의 크기를 다르게 조절하여 공급
  • sizes속성을 사용하면 디바이스 크기 별 이미지의 사이즈를 직접 지정해 줄 수 있음
<Image src={dogImage} alt="dog" fill sizes="(max-width: 768px) 33vw, (max-width: 1200px) 50vw, 100vw"/>7

/* 화면 크기가 768px 보다 작다면 뷰포트 가로길이의 33% 정도의 크기를 가진 이미지를 공급해주고, 
1200px 보다 작다면 뷰포트 가로길이의 50% 정도의 크기를 가진 이미지를 공급해주고, 
그것보다 크다면 100% 크기를 가진 이미지를 공급하도록 설정*/
  • 이미지를 외부에서 가져오는 경우 next.config.js설정을 수정해주어야 함
  • 이미지를 불러 올 도메인을 추가하여 허용
//next.config.js
images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'via.placeholder.com', // 전부 허용하고자 할 경우에는 **
        port: '',
        pathname: '**', // 전부 허용하고자 할 경우에는 **
      },
    ],
  },

🧡styled-components를 사용한 스타일 적용

  • 기존과 같이 styled-component를 적용하는 경우 Prop ‘className’ did not match라는 경고가 발생하며 스타일이 적용되지 않음.
import styled from 'styled-components'

export const Container = styled.div`
    display: flex;
    flex-direction: column;
`

function Post() {
  return (
    <Container>
      <h1>Post</h1>
    </Container>
  )
}

export default Post

// ERROR! Prop ‘className’ did not match
  • styled-components의 경우 사용자에게 페이지를 공급 할 때 마다 새로 고유한 classname을 설정하는데, next.js에서 build시에 미리 렌더링 한 정적 페이지의 classname과 서버에서 사용자에게 공급하며 렌더링 된 페이지간에 classname이 일치하지 않기때문

  • next.config.js에 compiler추가

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    styledComponents: true
  }
}

module.exports = nextConfig
  • styledComponents: true 설정 시 최초 빌드할 때와 서버/클라이언트에서 렌더링할 때의 className 을 일치시킴

🧡정적사이트 생성 (SSG)

  • 외부에서 데이터를 가져오지 않는 경우 Next.js는 기본적으로 SSG방식으로 HTML을 그림
  • 외부에서 데이터를 가져와야하는 경우 SSG방식으로 렌더링을 하기 위해서는 별도의 설정을 해주어야 함

✨getStaticProps()

  1. getStaticProps()함수를 만들어 필요한 데이터를 호출한 뒤, props에 객체형태로 담아 return
  2. getStaticProps()에서 return한 데이터는 컴포넌트에서 props로 받을 수 있다
//기본형태
export async function getStaticProps() {
  // 데이터 호출
  return { props: { 데이터 } }
}

function 페이지컴포넌트({ 데이터 }) {
  return <div></div>
}
  • 예시코드
import axios from 'axios'

export async function getStaticProps() {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
    const posts = response.data

    return { props: { posts } }
}

function Post({ posts }) {
  return (
    <div>
        {posts.map((post) => (
          <div key={post.id}>
              <h1>{post.title}</h1>
              <p>{post.body}</p>
          </div>
        ))}
      </div>
  )
}

export default Post

revalidate옵션

revalidate: 10 // 단위: 초(s)
  • 정적페이지의 경우 빌드 시 불러온 데이터를 사용해 사용자가 오래 된 데이터를 사용하게 되는 단점이 있음
  • revalidate옵션을 사용하면 설정한 시간이 지날 때 마다 주기적으로 다시 build하여 데이터를 새로 불러옴
  • ISR(Incremental Static Regeneration) 방식이라 함
// 예시
export async function getStaticProps() {
  const response = await axios.get(
    "https://jsonplaceholder.typicode.com/posts"
  );
  const posts = response.data;

  return { props: { posts }, revalidate: 10 }; //10초에 한번씩 다시 build
}

✨getStaticPaths()

  • 동적인 경로를 사용하는 페이지에서, next.js는 사전에 어떤 페이지 경로를 미리 렌더링 해두어야 하는지 알 수 없음

  • getStaticPaths()는 동적인 경로를 사용하는 페이지에서 특정한 경로를 미리 렌더링 해둘 수 있도록 지정

  • getStaticPaths()의 return 값을 통해 지정이 가능하며, 배열형식으로 경로를 담아 설정

  • 예를들어 /posts/[id]의 동적경로로 접근하는 페이지가 있을 때, 아래와 같이 코드를 작성하면, posts/1, posts/2, posts/3 페이지를 build시에 정적으로 생성

    export async function getStaticPaths() {
      return { 
        paths: [
          {params: {id: '1'}}, {params: {id: '2'}}, {params: {id: '3'}}...
        ]
      }
    }

fallback옵션

  • 코드에서 명시하지 않은 경로로 사용자가 접근하는 경우 처리
export async function getStaticPaths() {
  const response = await axios.get(
    "https://jsonplaceholder.typicode.com/posts"
  );
  const posts = response.data;
  const paths = posts.map((post) => ({ params: { id: `${post.id}` } }));

  return { paths, fallback: false };
}
  • 1~100번글만 있는 상황에 사용자가 101번 게시글에 접근하는 경우

    • fallback: false => 404페이지로 이동
    • fallback: true => 그제서야 해당 id로 요청을 보내고 응답을 가져옴 (로딩화면 설정가능)
      • 데이터가 너무 많은경우 해당방식을 사용
    • fallback: blocking => 그제서야 해당 id로 요청을 보내고 응답을 가져옴 (로딩화면 없이, 로딩이 완료되어야 페이지를 보여줌)
  • trueblocking로 옵션값을 설정하는 경우 사용자가 한번 접근한 페이지는 캐싱되어 재사용

  • true설정시 보여 줄 로딩페이지는 router.isFallback으로 boolean값을 받아와 아래처럼 작성

const router = useRouter()

if (router.isFallback) {
    return <div>로딩 중..</div>
}

✨getStaticProps()와 getStaticPaths() 함께 사용

  • getStaticPaths()로 생성하게 되는 페이지에서 외부데이터를 사용해야 하는 경우 두 메소드를 함께 사용한다
  • getStaticPaths()에서 return한 경로데이터를 getStaticProps()로 받아서 사용 할 수 있다
  • getStaticPaths()에서 return한 paths배열의 길이만큼 getStaticProps()가 실행

코드 흐름:

getStaticPaths() > paths전달 > getStaticProps() > props전달 => 페이지컴포넌트()

  • 예시코드
import axios from "axios";

// getStaticPaths => 지금 접근 할 아이디 번호 명시
export async function getStaticPaths() {
  const response = await axios.get(
    "https://jsonplaceholder.typicode.com/posts"
  );
  const posts = response.data;
  const paths = posts.map((post) => ({ params: { id: `${post.id}` } })); 
  // paths = [{params: {id: 1}}, {params: {id: 2}}, {params: {id: 3}} ...]

  return { paths };
}

// getStaticProps => getStaticPaths가 리턴한 번호를 토대로 요청을 보내고, 데이터를 저장
export async function getStaticProps({ params }) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/posts/${params.id}`
  );
  const post = response.data;

  return { props: { post } };
}

// 페이지컴포넌트 => getStaticProps가 리턴한 데이터를 props로 받아서 사용
function PostDetail({ post }) {
  return (
    <div>
      <span>{post.id}</span>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
}

export default PostDetail;

🧡서버사이드렌더링 구현 (SSR)

  • getServerSideProps()함수를 사용하며 사용 방식은 getStaticProps()와 같음
import axios from 'axios'

export async function getServerSideProps() {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
    const posts = response.data
    return { props: { posts } }
}

function Post({ posts }) {
  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>
          <h1>{post.title}</h1>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  )
}

export default Post

💡정말 업데이트가 잦은 경우가 아니라면, SSR 방식 보다는 SSG 방식에 revalidate 옵션을 명시하여 사용하는것을 추천

profile
프론트엔드 개발자 성장일기 💭

0개의 댓글