스타일 적용 방법
- CSS 파일 임포트
- 모듈
- 테일윈드
- CSS in JS
폰트
- next/font/google
- next/font/local
- @font-face
라우팅
앱 라우팅
- 라우팅은 app 폴더 아래에 폴더를 생성하면, 해당 폴더의 이름이 경로로 지정됨
- 폴더 안에는 page 컴포넌트를 넣어 페이지를 렌더링
- app 폴더 아래에는 라우팅을 담당하는 컴포넌트 및 폴더들만 넣어야 함
- app 폴더 바로 아래의 page가 메인 페이지 담당
Next.js는 다양한 시스템 파일을 이용해서 라우팅 및 에러를 제어한다
page
라우팅 결과를 보여주는 페이지
layout
html 구조를 잡아줌
→ 하나의 레이아웃을 여러 페이지에서 사용 가능
not fount
not-found.tsx 파일을 통해 잘못된 라우팅 경로 핸들링
최상단에 작성되어야 함
loading
loading.tsx 파일을 통해 에러 핸들링
error
에러 발생시 나타나는 페이지
에러가 발생한 컴포넌트와 가장 가까운 error.tsx가 실행됨
다이나믹 라우팅
폴더명(경로) 뒤에 [id]를 붙여주면 id에 따라 동적으로 변동되는 페이지 생성
데이터 통신
기존의 fetch.api는 웹 브라우저에서 제공하는 클라이언트 api
→ SSR은 서버에서 실행되므로, 클라이언트 api와는 잘 맞지 않음
→ next.js 팀에서 fetch.api를 개선
- 클라이언트
"use client";
import { useEffect, useState } from "react";
function ClientFetch() {
const [data, setData] = useState(null);
const fetchData = async () => {
const res = await fetch("<https://jsonplaceholder.typicode.com/todos>");
const data = await res.json();
setData(data);
};
useEffect(() => {
fetchData();
}, []);
return (
<>
클라이언트 페치
);
}
export default ClientFetch;
- 클라이언트 렌더링 과정에서 서버 통신이 발생 → 지연 발생
- useState, useEffect 등 hook을 사용 가능
- 서버 컴포넌트
async function ServerFetch() {
const res = await fetch("<https://jsonplaceholder.typicode.com/todos>");
const data = await res.json();
return (
<>
서버 페치
);
}
export default ServerFetch;
- 서버에서 이미 데이터를 받아와서 사용하기 때문에 지연이 없음
- React Hook을 사용 할 필요 없이 바로 변수에 할당 가능
로딩 및 에러
클라이언트에서는 직접 로딩 및 에러 처리를 해줘야 함
"use client";
import { useEffect, useState } from "react";
function ClientFetch() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const fetchData = async () => {
setIsLoading(true);
await new Promise((res) => setTimeout(res, 3000));
const res = await fetch("<https://jsonplaceholder.typiasdascode.com/todos>");
if (!res.ok) setIsError(true);
const data = await res.json();
setData(data);
setIsLoading(false);
};
useEffect(() => {
fetchData();
}, []);
if (isLoading) return
로딩중입니다....
;
if (isError) return
에러 발생!!
;
return (
<>
클라이언트 페치
);
}
export default ClientFetch;
서버 컴포넌트에서는 라우터 폴더 아래 loading.tsx 컴포넌트를 만들어 로딩 처리
// loading.tsx
function loading() {
return <div>서버 로딩중...</div>;
}
export default loading;
// error.tsx
"use client";
function error() {
return <div>에러 발생!</div>;
}
export default error;
멀티 스레드
promise.all()을 사용하면 api 호출을 병렬로 처리 가능
const getPosts = async () => {
try {
await new Promise((res) => setTimeout(res, 3000));
const res = await fetch("<https://jsonplaceholder.typicode.com/todos>");
if (!res.ok) throw new Error("에러 발생");
console.log(res);
return await res.json();
} catch {
throw new Error("에러 발생");
}
};
const getTodos = async () => {
try {
await new Promise((res) => setTimeout(res, 3000));
const res = await fetch("<https://jsonplaceholder.typicode.com/todos>");
if (!res.ok) throw new Error("에러 발생");
return await res.json();
} catch {
throw new Error("에러 발생");
}
};
async function ServerFetch() {
const [data1, data2] = await Promise.all([getPosts(), getTodos()]);
return (
<>
서버 페치
서버 페치2
);
}
export default ServerFetch;
시간이 걸리는(로딩) 컴포넌트들은 Suspense 컴포넌트로 감싸서 병렬 처리 가능
→ fallback props 안에 로딩 컴포넌트를 넣어줌
import Post from "@/app/server-fetch/post";
import Todo from "@/app/server-fetch/todo";
import { Suspense } from "react";
async function ServerFetch() {
return (
<>
Post 로딩중}>
Todo 로딩중}>
);
}
export default ServerFetch;
// Post.tsx
const getPosts = async () => {
try {
await new Promise((res) => setTimeout(res, 3000));
const res = await fetch("<https://jsonplaceholder.typicode.com/todos/1>");
if (!res.ok) throw new Error("에러 발생");
return await res.json();
} catch {
throw new Error("에러 발생");
}
};
async function Post() {
const data = await getPosts();
return (
Post
{JSON.stringify(data, null, 2)}
); } export default Post; // Todo.tsx const getTodos = async () => { try { // await new Promise((res) => setTimeout(res, 5000)); const res = await fetch("<https://jsonplaceholder.typicode.com/todos/1>"); if (!res.ok) throw new Error("에러 발생"); return await res.json(); } catch { throw new Error("에러 발생"); } }; async function Todo() { const data = await getTodos(); return (
Todo
{JSON.stringify(data, null, 2)}
); } export default Todo;
캐싱
Next.js는 데이터를 받아오거나 연산을 했을 때 ‘캐싱’을 실행
→ 데이터를 다시 받아올 때 캐싱 된 데이터를 활용함으로써 빠르게 받아 올 수 있음
캐싱 매커니즘
- fetch 요청이 발생할 경우, 라우트 캐시에서 먼저 캐싱 된 데이터를 찾음
- 캐싱 된 데이터가 있을 경우 (HIT) 해당 데이터 사용
- 캐싱 된 데이터가 없을 경우 (MISS), 데이터를 캐싱 후 (SET) 풀 라우트 캐시 또는 리퀘스트 메모이제이션에 요청을 전달
- 같은 요청이 들어오면 SET된 데이터를 사용
- 풀 라우트 캐시에서 데이터를 찾음 (빌드시에만)
- 캐싱 된 데이터가 있을 경우 (HIT) 해당 데이터 사용
- 캐싱 된 데이터가 없을 경우 (MISS), 데이터를 캐싱한 뒤 리퀘스트 메모이제이션에 요청을 전달
- 빌드 과정에서만 사용 → 지속시간이 영구적 → 라우트 캐시가 초기화 되어도 풀 라우트 캐시가 삭제되지 않는다면 계속해서 캐싱된 데이터를 받게 됨
- 정적인 페이지는 Full-Route-Cache의 적용 대상이 됨
- 반대로 동적인 페이지는 적용 안됨 → 라우트 캐시만 사용
- 상황에 따라 정적/동적 페이지를 적절히 활용하여 컴포넌트 구성
👏 페이지 동적으로 변경하는 방법
→export const revalidate = 0
- 리퀘스트 메모이제이션⇒ 여러 컴포넌트에서 fetch 함수를 호출한다 하더라도 내부적으로는 메모이제이션을 통해 한번만 요청을 함으로써 동일한 데이터를 가져올 수 있음
- 캐싱 된 데이터가 있을 경우 (HIT) 해당 데이터 사용
- 캐싱 된 데이터가 없을 경우 (MISS), 데이터 캐시에 데이터 요청
- → 동일한 URL과 옵션을 가진 요청을 자동으로 메모이제이션하여 fetch API 를 확장
- 데이터 개시
- 캐싱 된 데이터가 있을 경우 (HIT) 해당 데이터 사용
- 캐싱 된 데이터가 없을 경우 (MISS), 비로소 외부 데이터 소스로부터 데이터를 받아 옴
각 단계에 캐싱된 데이터가 있으면, 후속 단계는 아예 실행 되지 않음
캐시 무효화
캐시는 데이터를 저장해 둠으로써 화면을 빠르게 렌더링 할 수 있다는 장점이 있지만, 반대로 동적인 페이지에서는 데이터가 업데이트 되지 않는 오류가 발생 할 수 있음
- 시간 기반 재검증
- 정해져 있는 시간동안 저장된 캐시를 사용
- 지정한 시간이 지나도 자동으로 캐시가 비워지고 새로운 캐시가 저장되진 않음
- export const revalidate = 0 ⇒ 캐시를 사용하지 않겠다는 의미
next: { revalidate: 60 }
// 60초동안만 캐시를 사용
- 명령어 기반 재검증
- 명령어를 입력하여 캐시를 삭제
- cache: no-store
- 주문형 재검증
- revalidatePath("/")
- 바로 데이터를 세팅하는 것이 아니라, 캐시를 초기화
- 초기화 한 후에 받아온 데이터를 캐시에 저장
const revalidate = async () => {
"use server";
// "/"(홈)의 레이아웃 아래에 있는 모든 컴포넌트의 캐시 삭제
// = 사실상 모든 컴포넌트의 캐시를 삭제하는 방법
await revalidatePath("/", "layout");
}}
본 게시글의 예제 코드는 수코딩(https://www.sucoding.kr)을 참고했습니다
'Frontend Study' 카테고리의 다른 글
[유데미x스나이퍼팩토리] Next.js 3기 - 스와이프 기능 추가 (0) | 2024.10.24 |
---|---|
[유데미x스나이퍼팩토리] Next.js 3기 - 프로젝트 폴더 구조 설계 (0) | 2024.10.08 |
[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 6일차 (1) | 2024.10.08 |
[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 5일차 (1) | 2024.09.27 |
[유데미x스나이퍼팩토리] Next.js 3기 - 사전직무교육 4일차 (0) | 2024.09.26 |