Next js 알아보기

zmin·2022년 8월 31일
0

https://nextjs.org/docs/getting-started

learn 부분도 간단하게 잘 되어있는 것 같아서 집 갈 때 해보면 좋을 듯 내가 다 맞춰줌

Pages와 Data Fetching

어디까지 데이터를 채워서 보낼 것인가?

root 또는 src안에 pages/ 로 각 페이지를 작성할 수 있다. 중첩된 구조로 routing도 가능하며 posts/[id] 와 같이 [] 이용하여 dynamic routing도 가능

ssr을 지원하는 만큼 next js는 각 페이지를 pre-rendering하여 client로 전송한다. pre-rendering에는 두 가지 방식이 있다.

  • Static Generation: build될 때 미리 다 만들어두고 요청이 들어오면 만들어둔 것을 전송
  • Server-Side Rendering: 요청이 들어올 때 html을 만들어서 응답으로 전송

ssr은 사실 pre-rendering의 한 종류

두 가지 다 사용할 수 있으나 서버의 성능을 고려한다면 static generation을 사용하는 것이 더 좋다. 요청이 들어왔을 때 html을 렌더링 하는 과정이 빠지게 되니 그동안 다른 작업을 더 할 수 있기 때문이다. 자주 html에 변경이 발생해야하는 작업등에 SSR이 필요하다면 부분적으로 사용하는 방법도 있다.

Static Generation

의 경우 미리 렌더링을 해둬야하는데 이때 미리 렌더링 하고 싶은 서버의 데이터가 있다면 처리할 수있다. getStaticProps/Path 를 정의하면 된다.(production에서는 build타임에서만, dev에서는 매 요청마다 호출) 물론 client단의 데이터 fetching도 가능하다.

data Fetcing section에서 더 자세히 알아볼테지만 전체 형태는 아래와 같다.

function Component({ data }: Data) {
	// data를 이용한 어떠한 렌더링
}

//////////// 이 함수는 build time과 data를 fetch 해줘야할 때 쓰인다 -> 정말 데이터를 props로 넣어주기 위한 것 ///////
export const getStaticProps : GetStaticProps = async () => {
	// 일반적으로 데이터를 받아오는 과정은 비동기적으로 발생한다.
	const res = await fetch(~~);
	..
	데이터 처리
	..
	return {
		props: { // 위에서 정의한 props들을 가지는 객체로 반환
			data
		}
	}
}

nextjs를 사용하지 않는 react 같으면 useEffect를 이용하여 parent컴포넌트에서 데이터를 받아와서 넣어주거나, 자체적으로 컴포넌트(페이지)가 로드 됐을 때 값을 받아오도록 할 수 있지만 결국 이 과정들은 client에서 실행되는 것들이기 때문에 값들이 서버에서 전송되는 html에는 포함되지 않는다.

next js에서 지원하는 dynamic path가 data를 가져오는데 쓰인다면(react-router-dom의 useParams()) getStaticPaths: GetStaticPaths를 이용해서 getStaticProps: GetStaticProps에 해당 dynamic path를 넘겨줄 수 있다. 이 때 paths 배열로 반환되는 모든 요소에 대해 SSG를 진행하게 되는데 반환되지 않은 값에 대한 페이지 요청이 들어왔을 때의 처리를 fallback 속성으로 정의해줄 수 있다.

위 두 함수는 모두 일반 컴포넌트가 아니라 pages에 독립적으로 작성되어야한다.

하지만 Static Generation의 특징을 잘 생각해봤을 때 이미 렌더링된 html을 받는 것이기 때문에 서버에서 이미 변경된 데이터에 대한 정보를 업데이트하지 못했을 수도 있다.

그래서 일반적으로 데이터의 변화가 빈번하게 일어나지 않는 부분에 대해서 Static Generation을 사용하게 되고 자주 변화가 생기느 부분은 client-side data fetching을 이용하게 된다.

그게 아니라면 SSR을 이용하면 된다. → 페이지의 로딩 자체가 좀 느려지지만 데이터의 변화는 잘 반영될 것이다.

Server Side Rendering

위와 똑같이 data를 fetching해줘야하는 경우 getServerSideProps를 사용한다. (방법은 getStaticProps 와 같다. getSPerverSidePaths가 없는 이유는 이미 페이지를 요청할 때 정보가 포함되어있기 때문)

→ 이렇게 렌더링 된 html은 cache-control header를 정의해주는 경우에만 caching. cache를 이용하는 경우도 data updating을 신경써서 해줘야한다. 함수의 context파라미터의 res 객체(client로 페이지를 담아서 보내려고 준비해둔 응답객체)의 header를 정의할 수 있다.

export async function getServerSideProps({ req, res }) {
  res.setHeader(
    'Cache-Control',
    '응답으로 보낸 페이지를 어떻게 caching하면 될지에 대한 조건'
  )
...

Incremental Static Regeneration

SSG에서 이미 render된 페이지를 다시 빌드하지 않고도 update할 수 있게한다. getStaticProps의 반환값에 revalidate: number를 추가하면 입력된 초마다 해당 페이지만 regeneration하도록 Trigger.

이는 이미 client로 새로 만들어진 페이지를 알아서 잘 전송해준다는 것은 아니다.
새로 들어오는 페이지 요청에 데이터가 업데이트된 페이지를 보낼 수 있게 하는 것

만약 CDN에서 자동 caching하게 만들었다면 CDN입장에서는 이미 cache 된 old html이 있으니 계속 그것만 보내주게 될테니 useCDN: false 로 지정해주어야 업데이트가 제대로 일어난다.

Client-side data Fetching

useEffect를 이용하거나 nextjs에서 data fetching을 위해 자체적으로 제공하는 SWR hook을 사용해도 된다(추천)

https://swr.vercel.app/docs/getting-started 와 무려 한국어도 있다!

Image

next js에서는 img 태그 대신 사용할 Image 태그를 자체적으로 제공한다. img 태그를 확장한 것으로 이미지를 다룰 수 있는 추가적인 프로퍼티도 제공하며 최적화도 해준다.

  • 성능 향상
  • cumulative layout shift현상을 막아 안정적인 뷰를 제공한다
  • 빠른 로드, 뷰포트 안에 들어올때 이미지를 로드함 lazy
  • 이미지 리사이징 → 최적화

사용하는 방식은 img 태그와 같다. 내부 static 파일을 import 하여 사용할 수도 있고 외부 주소를 이용하여 가져올 수도 있다. → 외부 주소를 사용할 경우에는 next.config.js에 해당 도메인을 등록해줘야 CORS할 수 있다. nextjs의 이미지의 로더는 불러온 이미지를 위한 url을 여러개 만드는데 각 url 마다 이미지 사이즈가 다양하여 얘를 srcset 안에 넣어주게 된다. 사용자가 자신에게 맞는 사진을 사용할 수 있도록 하는 것이다.

각 페이지 단위로 Priority를 설정해줄 수 있다. priority 프로퍼티를 설정하여 LCP가 빨리 발생하도록 한다.

이미지를 불러오는 과정이 느리면 자주 발생하는 것이 layout shifting, 말 그대로 없었던 이미지가 로드가 끝나 화면에 추가되면서 레이아웃이 밀리는 것이다. 이미지에 의한 layout shifting을 막기 위해서는 크기를 지정해주는 것이 제일 좋다. widht, heigth 로 값을 지정해주거나 layout 프로퍼티에 원하는 값을 할당하여 사용할 수 있다. 그래서 Image 태그를 이용하려면 필수적으로 값을 지정해줘야한다..(약간불편한디)
→ 특히 fill의 경우는 objectFit과 objectPosition 프로퍼티를 이용해 위치를 조절할 수 있다.

이외에도 img 태그에서 사용할 수 있는 속성들도 사용할 수 있다.

https://nextjs.org/docs/api-reference/next/image

그리고 static한 이미지들을 public 폴더에서 관리해야한다.

Image 컴포넌트를 거치지않고 그냥 img 태그를 이용하는 등의 방법으로 이미지를 로드하려면 사진 파일을 public 폴더 아래에 둬야 위치를 인지한다.
하지만 굳이 그러지 않는 이유는 next.js가 최적화도 잘 되고 이미지를 잘 다룰 수 있도록 컴포넌트를 만들어 놨기 때문이다. 만들어진 것을 알맞게 잘 사용하는 것도 중요.

image 크기 조정

https://nextjs.org/docs/api-reference/next/image

image에서 src를 설정할 수 있는 방법은 두 가지가 있다.

  1. statically imported image사용하기
  2. path string 사용하기 → 이 경우에는 외부 url도 사용할 수 있는데 보안 문제로 next.config.js에 domain을 등록해야한다
images : {
	domains: ['도메인'],
}

어떤 주소를 입력하든 상관 없지만 두 방법의 차이는 1의 경우 사이즈를 꼭 지정해주지 않아도 사용가능하지만

2의 경우는 무조건 width+height 를 지정해주거나 layout=’fill’을 설정해줘야한다.

viewport라고 말하는게 브라우저의 전체 viewport가 아니라 이미지 기준이라는 것을 생각하면서 docs를 읽으면 좋을 것 같다. 그러니까 이미지의 parent container. 왜냐면 내가 처음 읽을 때 헷갈렸기 때문…

width & height

그동안 사용해온 속성과 다르지않다.

  • 1에서 둘 중 하나만 입력하게되면 입력하지 않은 다른 값은 원본 사진의 값을 가져온다.
  • layout=’fill’인 경우: 영향 없음
  • layout=’responsive’인 경우 : width는 parent의 width에 맞추어지므로 의미가 없고 height만 적용된다 → 입력하지 않으면 원본 비율 유지

layout

  • intrinsic: 기본값, 이미지보다 parents가 커지면 자신의 크기를 유지하고 그게 아니라면 parent에 맞게 줄어든다
  • fixed: 지정한 크기를 무조건 유지 → 원래의 img 와 동일한 속성, width & height는 둘다 입력해주지 않으면 비율이 깨진다. parent의 크기와 상관없이 줏대있게 본인 크기를 유지한다.
  • responsive: display:block인 parent의 width에 딱 맞추고 원본 비율 유지 → 이때 width & height를 입력해주게되면 해당 값은 원본 이미지의 크기에 반영된다. 무슨말이냐면 만약 300*500 인 이미지가 있고 600* 100 인 parent(height는 이미지 크기에 영향을 주진 않는다)가 있다고 하자. 이때 이미지의 width를 600으로 설정해주면 원본 이미지의 크기가 가로로 늘어나서 600500가 돼서 실제 화면에 표시되는 크기도 600500이 된다 원본 사이즈의 비율이 바뀌게 된다.
  • fill: position:relative를 가지는 parent를 꽉채운다. Image 태그가 absolute 성질을 갖게 되는 듯
    • objectFit과 objectPosition(css 속성)을 이용하여 이미지를 채우는 방식과 위치를 조정할 수 있다.
    • objectFit의 경우 비율을 유지하기 위해서는 contain과 cover를 사용한다.

그래서 반응형 웹을 구성할 때 이미지를 적절히 이용하고 싶다면 layout을 이용해야 유연하게 이미지 사이즈를 조절할 수 있을 것이다. 하지만 layout을 이용하게 되면 parent값에 영향을 받게 돼서 결국 parent도 매번 설정 해줘야할 수도 있다. 그래서 아예 Image와 Image 컴포넌트를 div로 한 번 감싼 컴포넌트를 만들어서 이 div에 대한 크기를 조절하는 것이 좋을 것 같다. 물론 이렇게 이용하고 싶다면 static imported image를 이용해야한다.

sizes

미디어 쿼리를 이용하고 싶을때 유용하며 next/image가 생성하는 다양한 크기의 image srcset에서 해당 크기를 찾아올 수 있다. 이는 성능 향상에도 도움을 주게 되는데 원래같으면 실제 보여지는 크기와 상관없이 원본 이미지를 받아와야했다면 필요한 크기만을 가진 파일을 요청할 수 있으니 전송되는 파일 크기를 줄일 수 있는 것이다.

sizes="(min-width: 75em) 33vw,
       (min-width: 48em) 50vw,
       100vw"

위와 같이 표현할 수 있다.

개발 도구

eslint: https://nextjs.org/docs/basic-features/eslint

typescript: https://nextjs.org/docs/basic-features/typescript

npx create-next-app@latest --ts
# or
yarn create next-app --typescript

React 에서 타입을 제공하듯이 Next.js에서도 타입을 제공한다. → 잘 활용하면 좋음

Routing

pages/ 내부에 nested한 파일 구조를 그대로 routing 해주는 기능이 있다.

  • pages/index.js → /
  • pages/blog/index.js → /blog
  • pages/blog/my.js → /blog/my

dynamic routing

bracket을 이용하여 나타낼 수 있다. react-router-dom의 :id 와같은 역할

  • pages/blog/[id].js → /blog/1 , /blog/2 …
  • pages/blog/[id]/edit.js → /blog/1/edit , /blog/2/edit …

컴포넌트 내부에서 dynamic routing된 path에 접근하고 싶을 때는 useRouter()를 사용하여 query를 받아올 수 있다. → 그니까 꼭 routing params 뿐만 아니라 쿼리로 전달된 query params들도 알아서 파악해서 같이 전달해준다.

// 'pages/post/[postId].js', '/post/1?user=my'

...
const router = useRouter();
console.log(router.query); // { postId: '1', user: 'my' }
...

이 값은 서버 렌더링시에는 비어있고 hydrate되면 값이 생긴다.

<Link> 를 이용해서 클라이언트에서 라우팅을 이용할 수도 있고 useRouter()를 이용해서 script속에서도 라우팅할 수 있다.

shallow routing

서버에서 data fetching을 하는 함수를 호출하지 않고 페이지를 라우팅 하는 것.
url query등을 이용하여 값을 전달하고자 할 수도 있지만 그 값이 페이지에 반영되지 않는다.

하지만 같은 페이지 내에서만 작동 → query string을 다루기 위한 routing이라고 생각할 수 있다.

페이지를 새로고침하지 않고 route의 상태만 바꾼다.

API Route

실제 데이터베이스와 api통신을 할 수 있는 것처럼 next js 자체에서 API 라우팅을 이용한다. pages/api/ 폴더 내부에 스크립트들을 위치시키면 그 경로가 엔드포인트가 되고 요청 주소로 이용할 수 있다.

백엔드 서버와의 통신을 위한 테스트인가 했는데 아니다. next export(SSG)에서는 지원하지 않지만
클라이언트에서 직접 외부 데이터베이스에 바로 요청을 넣었던 것을 한번 거치게 만드는 작업 → 보안상의 이유도 있고 클라이언트에서 좀 더 입맛에 맞게 사용할 수도 있고 데이터를 중간 단계에 한 번 저장하는 작업을 진행할 수 있다. → 미들웨어?

기본적으로 same origin only가 기본 값이며 CORS를 해야한다면 추가적으로 헤더를 설정해줘야한다.

// api/ 에 위치하게되는 파일들은 이런식으로 요청에 대한 처리와 응답 객체를 작성할 수 있다.
export default function handler(req, res) { .... }

Path를 Dynamic하게 사용한 것 처럼 API Route도 Dynamic하게 사용할 수 있다.

Production

next js에서 정의하는 production을 위해 해야할일을 정의해둔 것이 많지만 정리하면 아래와 같다.

  • 가능한 곳은 caching을 이용해라
  • logging과 error handling을 할 수 있도록 해라
  • 성능 측정을 계속해서 해라
  • static한 파일들의 최적화를 진행하여 성능을 향상시켜라. → lazy loading도 적극적으로 활용하기
  • 오류가 발생했을 때의 페이지도 정의하기

→ next js뿐만이 아니라 어떤 것들이라도 production을 할 것이라면 확인해야할 필요가 있다.


https://nextjs.org/docs/advanced-features/compiler

전부를 정리하기엔 지금 당장 필요한건지 모르겠어서(그리고 다른 공부하고 싶은 것도 많고..) 일단 읽고 ..

  • React가 default 컴파일러로 바벨을 쓰는 것처럼 next js는 default 컴파일러로 SWC를 쓰고 있다.
    • SWC는 컴파일러이자 번들러이이고 더 확장해나갈 수도 있다고 말한다. fast refresh에서 3배 빠르고 빌드 과정이 5배 빠르다고 하며 따로 라이브러리를 설치하고 구성할 필요 없이 next js에 포함되어있다. built-in 되어있는 컴파일러도 있고 외부 컴파일러를 이용할 수도 있는데 이 경우에는 next.config.js를 이용하여 설정해줄 수 있다. 대표적으로 styled-components 를 사용하기 위해선 babel-plugin-styled-component를 이용하여 컴파일하게 된다. jest, relay 등도 지원하며 여러가지 컴파일 옵션들을 지정할 수 있다(console.~~ 구문 포함하지 않기 등등)
  • dynamic import를 지원한다 → 이 말은…lazy loading이 가능하고 react suspense를 사용할 수 있다는 것
    const Dynamic = dynamie(() => import('어떤모듈path'), {
    	suspense: true; //를 쓸거라면 여기
    });
    ...
    <Suspense fallback={<Loading/>}>
    	<Dynamic/>
    </Suspense>
    ...
    
    const Dynamic = dynamie(() => import('어떤모듈path'), {
    	loading: () => <서스펜스를 쓸 수 없다면 loading 프로퍼티에../>
    });
    named exports된 모듈을 사용하고 싶다면 import가 반환하는 promise를 받아서 속성값처럼 접근 가능
  • 자동으로 SSR과 SSG를 판단하여 제공 → getServerSidePropgetInitialProps 를 가지고 있다면 SSR로, 그렇지 않다면 SSG로 생각하고 optimize
  • Error boundary는 리액트에서 정의하는 것처럼 사용가능
  • _app.tsx
    • global style sheet나 페이지를 로드할 때 전반적으로 적용하고 싶은 코드, 즉 전역 데이터 관리와 같은 코드를 작성하여 적용할 수 있음
    • 각 페이지가 렌더링 될 때 이 _app.js 가 매번 불러와짐
      공통으로 들어가게 되는 Layout의 경우 이 _app.tsx에 표시할 수 있다.
    • getInitialProps를 이용하여 모든 페이지에 공통인 초기 props를 전달해줄 수도 있다.
  • _document.tsx
    • 서버에서만 실행되기 때문에 이벤트 핸들러등을 부착할 수 없음
    • 또한 next/document의 Html, Head, Main, NextScript 등을 이용하여 메타데이터를 설정할 수 있다.
    • 이는 모든 페이지에 공통으로 적용되는 것이고 해당 페이지마다 다르게 설정하고 싶다면 renderPage등을 이용하여 값을 또 처리해주어야한다.
    • 정적 메소드인 getInitialProps를 오버라이딩할 때 내부에서 renderPage에 함수를 할당하여 해당 페이지가 렌더링 될 때 하고싶은 작업을 설정해줄 수 있다(style 정보 밖으로 빼내기 등)
      → getInitialProps로 부모요소의 intialprops를 받아와 기본적으로 반환한다.(원한다면 추가할 수도 있을 것) → typescript코드를 보면 알겠지만 DocumentInitialProps를 제네릭으로 가지는 Promise를 반환한다.
profile
308 Permanent Redirect

0개의 댓글