리액트는 자바스크립트로 HTML을 그리는 방식, 즉 클라이언트 사이드 렌더링 CSR이기때문에 초기에 웹페이지를 그리는데 필요한 자바스크립트의 번들이 크다.
초기 성능 저하
SEO에 좋지않음. -> 자스가 html을 나중에 그리니까~
요러한 문제를 해결하기 위해 나타난 Next.js - SSR 서버사이드 렌더링
Next.js 13에서는 app이라는 디렉토리로 만드는 라우팅, App Router가 사용된다.
App Router는 React 18의 React Sever Component(RSC), Suspense를 기본적으로 염두한 방식!
Suspense
를 사용해 하이드레이션을 컴포넌트 단위로 수행 = Streaming
서버 컴포넌트가 필요한 이유?
React는 서버에서 렌더링 할 수 있는 컴포넌트인 RSC(React Server Component)
를 제공함으로써 개발자가 원하는 곳에서 컴포넌트를 렌더링 할 수 있는 선택지를 제공
기존의 Server Side Rendering(SSR)과 다른 점?
SSR은 서버에서 페이지 단위로 정적인 리소스를 생성하지만
RSC는 컴포넌트 단위로 정적인 리소스를 생성
=> 클라이언트로 내려보내는 JavaScript 번들 크기를 줄일 수 있게 되는 것
이러한 장점 덕분에 Next.js에서는 RSC를 기본 컴포넌트 렌더링 방식으로 채택🥂
하지만 모든 컴포넌트를 RSC로 만들 순 없다고한다.
브라우저 API를 사용하거나 useState, useEffect 등의 훅을 이용해야하는 경우 => 클라이언트 컴포넌트
파일 가장 첫 줄에 'use client'라고 명시!
Next.js에선 useState, useEffect 같은 훅을 사용하는 클라이언트 컴포넌트를 최대한 말단(Leaves)으로 내려보내길 권합니다.
서버 컴포넌트를 제일 위에!!
경로를 지정하는 라우팅 App router
경로 = URL Path
app 디렉토리(폴더) 아래에 정의
Next.js에선 파일 시스템 기반으로 경로를 지정
폴더구조를 어떻게 구성하냐에 따라, 여러 방식으로 라우팅 가능
app
이라는 폴더를 만들고 그 아래 dashboard
, settings
라는 폴더를 만들면 URL path가 폴더 이름과 동일한 순서를 따라 만들어진다.
경로의 이름으로 사용되는 폴더 = segment
위에 dashboard 세그먼트를 예시를 들면, 세그먼트에는 파일이 필요하다. Next.js에서는 상황에 맞는 UI를 정의할 때 쓰는 파일명이 미리 정해져있음
밑 사진처럼 파일 이름을 약속된대로 지정해서 세그먼트 안에 정의하면, Next.js가 알아서 React 컴포넌트를 배치해준다.
settings
안에 page.js
가 정상적으로 로딩됐을때 나오는 페이지이다.
위의 url처럼, 고유한 id를 가진 블로그 상세페이지 글 url path는 어떻게 만드냐면요
Next.js에서는 Dynamic Routes라는 기능을 사용하면됩니다!
Dynamic Routes는 파일 이름을 대괄호로 묶어주기
대괄호 안에 지정한 이름을 Page 컴포넌트의 인자로 받을 수 있다function Page({ params }: { params: { id: string } }) { return <div>My Post: {params.id}</div> }
<Link>
컴포넌트는 HTML <a>
태그와 비슷하다
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
a태그를 쓰기 어려운 경우에는 useRouter
를 사용하면 된다!
BUT✅ useRouter는 클라이언트 컴포넌트에서만 사용할 수 있다
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
많은 스타일링 방법들이있지만, CSS Modules에 대해 알아보도록 하자
CSS Modules
는 클래스 이름 중복을 피할 수 있는 지역 스코프의 스타일링 방식
.module.css
이름을 붙여 스타일을 정의 -> 컴포넌트에서 가져와 사용
styles.module.css
styles.module.css
.dashboard {
padding: 24px;
}
layout.tsx
import styles from './styles.module.css'
export default function DashboardLayout({
children,
}) {
return <section className={styles.dashboard}>{children}</section>
}
전역 스타일은 app
디렉토리 안에 있는 어떤 layout, page, component 파일에도 선언할 수 있다!
app/global.css
body {
padding: 20px 20px 60px;
max-width: 680px;
margin: 0 auto;
}
app/layout.tsx
// 이렇게 적용된 스타일은 어플리케이션의 모든 경로에 적용됩니다.
import './global.css'
export default function RootLayout({
children,
}) {
return (
<html>
<body>{children}</body>
</html>
)
}
Next.js에선 Web API에서 제공되는 fetch API의 확장된 버전을 제공!
덕분에 Next.js에선 fetch로 각각의 페치 요청에 대한캐싱(Caching)
과재검증(Revalidating)
동작을 설정할 수 있다.
우리는 fetch를 비동기 서버 컴포넌트나 라우트 핸들러(Route Handlers) 등에서 사용할 수 있습니다.
클라이언트에서도 서버에서와 같은 방식으로 데이터를 페칭할 수 있습니다. 하지만 Next.js에선 보안과 성능상의 이점이 많기 때문에 서버에서의 페칭을 권장합니다.
async function getData() {
const res = await fetch('https://api.example.com/...')
// 직렬화되지 않기 때문에 데이터 타입을 바로 사용할 수 있습니다.
if (!res.ok) {
// 에러를 던지면 가장 가까이 있는 error.tsx 파일에 걸립니다.
throw new Error('Failed to fetch data')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
Fetch API 는 Next.js에서 확장된 버전을 제공하는 것이기 때문에
데이터를 캐싱(저장)
하고있다.
캐시란 데이터에 빠르게 접근하기 위해 자주 사용되는 데이터나 값을 미리 복사해 놓는 임시 장소를 의미한다.
fetch는 webAPI와 다르게 계속 값을 캐시하고 있기때문에 캐시된 값을 풀어주기위해선 재검증이 필요합니다
최신의 데이터를 원한다면 재검증을 통해 캐싱된 데이터를 무효화할 수 있습니다. 캐싱된 데이터를 재검증하는 방법은 두 가지!
특정시간이 지나면 이 데이터는 새로 가져와야해!
특정 시간이 지난 이후에 자동으로 재검증되도록 설정한다.
시간이 지난 캐시 데이터는 무효화된다. 신선하지 않은 데이터로 판별될 뿐 삭제되진 않는다. 재요청이 있을 때 신선한 데이터를 다시 불러옵니다.
데이터가 자주 변경되지 않고 신선도가 중요하지 않는 경우에 유용합니다.
// 3600초(60분) 동안 유효한 fetch
fetch('https://...', { next: { revalidate: 3600 } })
===> 3600초, 60분
위에 사진 세번째 request를 보면, 처음에 stale 되고 데이터를 보여준뒤 재검증을 통해 새로운 데이터를 보여준다.
이는 즉, 60초 이후 첫번째 호출에선, 60초 이전에 가진 데이터랑 동일한 데이터를 보게되고, 60초 이후 두번째 호출부터 신선한 데이터를 보게된다.
수동으로 재검증할 수 있다
온디맨드 재검증은
태그(tag)
혹은경로(path)
를 기반으로 특정 데이터 그룹을 일괄 재검증할 수 있습니다.
이때 재검증된 캐시 데이터는 삭제된다
즉각적으로 최신 데이터를 확보해야 하는 경우에 유용!
// 데이터에 태그를 달아둡니다.
fetch('https://...', { next: { tags: ['collection'] } })
👉 이 데이터는 이런 태그를 가진 데이터야!
// 태그된 데이터를 재검증합니다.
revalidateTag('collection')
purge: 정리(삭제)
revalidateTag('collection')로 재검증이 이루어지며 캐시데이터가 정리되고, 다음 호출부터 바로 최신 데이터 내려줌.
메타데이터는 페이지를 설명하는 데이터다.
Next.js에서는 메타데이터(Metadata)를 설정하는 두 가지 방법이 있다.
설정 기반 메타데이터(Config-based Metadata)
파일 기반 메타데이터(File-based Metadata)
설정 기반 메타데이터를 알아보자
설정 기반 메타데이터 방식에는 정적 방식(Static Metadata)과 동적 방식(Dynamic Metadata)이 있다.
정적 방식은 Metadata 객체를 정의하는 방식
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '...',
description: '...',
}
export default function Page() {}
적 방식은 generateMetadata라는 함수를 이용해 동적으로 Metadata 객체를 생성하는 방식
app/blog/[id]/page.tsx
---------------------------
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: { id: string }
searchParams: { [key: string]: string | string[] | undefined }
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent?: ResolvingMetadata
): Promise<Metadata> {
const id = params.id
const post = await fetch(`https://.../${id}`).then((res) => res.json())
const previousImages = (await parent).openGraph?.images || []
return {
title: post.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }: Props) {}