Next.js 페이지 예외 처리
로딩 페이지
loading 파일
- Next.js에서
loading.js
(타입스크립트 .tsx)로 만들면 자동으로 로딩 화면이 생성 - 모든 페이지에 동일한 로딩 화면을 표현하고 싶다면
(src/)app
영역에loading.js
를 생성 - 특정 라우터에 로딩 화면을 표현하고 싶다면 해당 경로 폴더에
loading.js
를 생성
import Link from "next/link";
export default function Home() {
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<h1>Home Page</h1>
<Link href="/info">Info 페이지로 이동</Link>
<Link href="/about">About 페이지로 이동</Link>
</div>
);
}
export default function Loading() {
return <>로딩 중...</>;
}
import Link from "next/link";
export default function InfoPage() {
return (
<>
<div>Info Page</div>
<Link href="info/myinfo">myInfo 페이지 이동</Link>
</>
);
}
export default function Loading() {
return <div>Info 로딩 중...</div>;
}
export default function MyInfo() {
return <div>MyInfo페이지</div>;
}
export default function AboutPage() {
return <div>About Page</div>;
}
위의 코드 구조이다.
src/app
├── page.js (Home 페이지)
├── loading.js (Home 페이지 로딩 화면)
├── info
│ ├── page.js (Info 페이지)
│ ├── loading.js (Info 페이지 로딩 화면)
│ └── myinfo
│ └── page.js (MyInfo 페이지)
└── about
└── page.js (About 페이지)
app의 page.js에서 Info 페이지로 이동
을 클릭 시, info경로의 page.js가 호출되는 동안 info의 loading.js화면을 표현한다
info의 page.js에서 myInfo 페이지 이동
을 클릭하게되면 myinfo경로의 page.js를 호출한다.
myinfo 폴더에는 loading.js파일이 존재하지 않지만, 상위 폴더인 info에 loading.js파일이 존재한다.
myinfo 페이지를 불러오게되면 info폴더의 loading.js파일을 호출하게 된다.
about페이지도 해당 경로에 loading.js파일이 존재하지 않는다.
about페이지도 상위 폴더를 탐색하여 (src/)app에 존재하는 loading.js파일을 호출하여 로딩화면으로 표현한다.
즉, loading.js파일을 이용하여 쉽게 로딩 화면이 표현 가능
또한 해당 경로에 loading.js파일이 존재하지 않는다면 해당 경로 기준 상단 제일 가까운 경로의 loading.js파일을 호출한다.
Suspense
- React에서 비동기적으로 데이터를 불러오는 동안 로딩 상태를 관리하는 기능
- Next.js에서도 Suspense를 이용하여 로딩 상태 관리가 가능
import Link from "next/link";
export default function Home() {
return (
<div>
<Link href="/about">페이지 이동</Link>
</div>
);
}
import { Suspense } from "react";
import { fetchTodoData } from "./fetchData";
async function getData() {
const { title } = await fetchTodoData();
return <h2>{title}</h2>;
}
export default function About() {
return (
<>
<h1>About 페이지</h1>
<Suspense fallback={<h2>Page Loading...</h2>}>{getData()}</Suspense>
</>
);
}
export const fetchTodoData = async () => {
await new Promise((resolve) => setTimeout(resolve, 5000));
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await response.json();
return data;
};
src/app
├── page.js (Home 페이지)
└── about
├── page.js (About 페이지)
└── fetchData.js (API 호출을 담당하는 함수)
app의 page.js에서 Link를 클릭하면 about페이지를 이동하여 API에 대한 데이터를 보여주는 코드이다.
about페이지에 진입 하면 5초 동안 Page Loading...
이 뜬 후, 해당 API에 대한 데이터가 뜬다.
Suspense를 이용하게 되면, 로딩을 처리하는 부분은 페이지 전체가 아니라 지정된 컴포넌트 영역으로 제한이 가능하다.
loading.js vs Suspense
-
loading.js
- 페이지 전환 시, 로딩 인디케이터로 보여주는 용도(라우터 이벤트)
- 화면 전체를 로딩 페이지로 전환
-
Suspense
- 주로 비동기 함수의 처리를 기다리기 위해 사용하는 용도
- 로딩을 보여주는 영역 설정이 가능
에러 페이지
- 서버 또는 클라이언트가에서 오류가 발생했을 때 사용자에게 보여지는 페이지
- Next.js에서
error.js
(타입스크립트 tsx)파일을 생성하면 에러 화면을 표현가능
import Link from "next/link";
export default function Home() {
return (
<div>
<Link href="/about">페이지 이동</Link>
</div>
);
}
"use client";
function Error({ statusCode }) {
return <p>{statusCode ? `에러 코드: ${statusCode}` : "에러 발생!!!"}</p>;
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Error;
"use client";
import { useEffect } from "react";
export default function About() {
useEffect(() => {
throw new Error("에러 발생!");
}, []);
return <>About 페이지</>;
}
위의 코드 구조이다.
src/app
├── page.js (Home 페이지)
├── error.js (에러 페이지)
└── about
└── page.js (About 페이지)
app페이지 에서 about 페이지로 이동하는 코드이다.
에러 화면을 표현하기 위해 about의 page에서 렌더링시, 에러를 발생시켰다.
app페이지에서 Link를 통해 진입하게 되면, 에러 발생!!!
문구를 확인할 수 있다.
- error.js 코드 분석
function Error({ statusCode }) {
return <p>{statusCode ? `에러 코드: ${statusCode}` : "에러 발생!!!"}</p>;
}
Error Component에서 statusCode값을 이용해 해당 값이 유효할 경우 에러 코드: 에러 번호
, 아닐 경우 에러 발생!!!
을 출력
그럼 statusCode는 어디서 받아오는 것일까?
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
getInitialProps는 서버에서 실행되어 초기 페이지 렌더링할 때, 데이터를 가져오는 Next.js의 메소드다
res는 서버 응답 객체이며, 오류페이지가 서버 측에서 렌더링되는 경우에 사용된다.
err는 클라이언트 측에서 오류가 발생한 경우의 오류 객체이다.
res와 err을 통해 서버의 오류인지 클라이언트의 오류인지 판별을 하여 statusCode로 반환한다.
즉, 페이지가 렌더링이 되면 getInitialProps
를 통해 서버 혹은 클라이언트의 에러를 판별하여 Error함수에게 전달한다.
404
: 클라이언트가 요청한 리소스를 서버가 찾을 수 없을 때 반환500
: 서버 내부에 오류가 발생하여 요청을 수행할 수 없을 때 반환
404, 500을 표현할 페이지를 직접 생성해서 구현해도 된다.
404 페이지를 구현할 경우, 404.js
를 생성
500 페이지를 구현할 경우, 500.js
를 생성
not found 페이지
- 사용자가 잘못된 경에 접근했을 때, 보여지는 화면
- Next.js에서
not-found.js
(타입스크립트 tsx)파일을 생성하여 잘못된 경로 진입 시, 보여지는 페이지
"use client";
import Link from "next/link";
import { useState } from "react";
export default function Home() {
const [value, setValue] = useState(0);
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<input onChange={(e) => setValue(e.target.value)} />
<Link href={`/${value}`}>페이지 이동</Link>
</div>
);
}
export default function NotFound() {
return <>존재하지 않는 페이지</>;
}
import { notFound } from "next/navigation";
export default function FindPage({ params }) {
if (typeof params.slug === "number") notFound();
return <>{params.slug} 페이지</>;
}
위의 코드 구조이다.
src/app
├── page.js (Home 페이지)
├── not-found.js (not-found 페이지)
└── [slug]
└── page.js (동적 페이지)
app의 page.js에서 입력값을 받은 후, 입력값에 대한 페이지로 이동하는 코드이다.
동적 페이지의 FindPage Component를 보면 notFound
함수를 볼 수 있다.
notFound를 통해 접근을 원치않는 페이지에 대해 예외처리가 가능하다.
위의 코드에서 숫자를 입력할 경우, not-found.js파일이 호출되어 존재하지 않는 페이지
를 표현
숫자 이외에 다른 값이 들어왔을 경우, 동적 페이지로 표현