가수면
Next.js 기본 본문
npm run dev보다 npm run build 후 npm start하면 더 빠름
pakage.json에 있는 스크립트의 dev를 "next dev --turbo"로 수정해 터보팩을 적용하면 프로젝트 규모가 클 수록 더 빠른 업데이트를 체감할 수 있다. (현재 개발 환경에서만 사용 가능)
설정
tsconfig.json에 compilerOptions의 target을 ES5에서 ES6로 수정해서 설정해주면 최적화 측면에서 좀 더 좋음
라우팅 기본 방식
app안에 폴더를 만들고 page 파일 생성
Route Groups
라우팅에 간섭하는 기능은 없음.
그러나 상위 세그먼트의 레이아웃 등에 더해 추가적으로 그룹 내에서 독립적으로 설정할 수 있는 기능이 있음
다이나믹 라우팅
상대 경로가 어떻게 되었든 해당 컴포넌트를 보여주고 싶을 때 []를 사용 ([]안의 텍스트는 아무거나)
type FoodProps = {
params: {
slug: string; // slug 대신 다른 이름 사용했으면 그거
};
};
const Foods = ({ params }: FoodProps) => {
return <div>{params.slug}</div>;
};
export default Foods;
중첩이 계속될 경우
중첩이 있어도 되고 없어도 될 경우
특정 정적 페이지 추가
generateStaticParams을 사용해 동적 페이지를 정적 페이지로 만들면 레이아웃 및 페이지에서 동일한 인수를 사용하는 가져오기 요청이 한 번만 이루어지므로 빌드 시간이 단축됨
type FoodProps = {
params: {
slug: string;
};
};
const Foods = ({ params }: FoodProps) => {
return <div>{params.slug}</div>;
};
export default Foods;
export async function generateStaticParams() {
const foods = await fetch('https://.../foods').then((res) => res.json());
return foods.map((food) => ({ slug: food }));
}
404
최상위 app 경로 내에 not-found.tsx를 만들면 not-found.tsx가 나옴.
만들지 않으면 next 에서 기본적으로 제공해주는 404가 나옴
페이지마다 개별 not-found.tsx를 설정해줄 수 있음.(없으면 app경로의 not-found.tsx > 없으면 기본 404 순서)
export default function NotFound() {
return <div>페이지 없음</div>;
}
레이아웃
import Link from "next/link";
import styles from "./layout.module.css";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "넥스트 연습",
description: "넥스트 연습중.",
icons: {
icon: "/src/app/favicon.ico",
},
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<header className={styles.header}>
<h1>헤더 연습</h1>
<nav className={styles.nav}>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/food">Food</Link>
</nav>
</header>
{children}
</body>
</html>
);
}
페이지 레이아웃의 경우도 같은 방식. 이름만 'RootLayout'에서 'FoodLayout'처럼 바꿔주면 됨
app에 사용될 최상위 레이아웃의 경우에는 body태그가 반드시 필요!
특정 경로에서 레이아웃 보이지 않게 하려면 경로에 대한 분기 처리를 하거나 '레이아웃 그룹'을 활용하면 될듯?
Link
사용자가 앱 내에서 이동할 때, 라우터는 이전에 가져온 세그먼트 및 미리 가져온 세그먼트의 페이로드를 캐시에 저장한다. (링크에 마우스 호버되는 순간 프리패치함)
이는 특정 경우에는 서버에 새로운 요청을 보내지 않고 캐시를 재사용할 수 있으므로, 데이터를 다시 가져오고 컴포넌트를 불필요하게 다시 렌더링하는 것을 피하여 성능을 향상시킨다.
※ 13.4.12 버전 기준 변경 사항
호버 시 프리 패칭 => 뷰 포트 노출 시 프리 패칭 발생
개발 환경에서도 프리 패칭 => 프로덕션 환경에서만 프리 패칭 발생
replace - true인 경우 브라우저의 기록 스택에 새 URL을 추가하는 대신 현재 기록 상태를 대체
prefetch - true인 경우 백그라운드에서 페이지(href로 표시됨)를 프리페치. 이는 클라이언트 측 탐색 성능을 향상시키는 데 유용(기본값 true)
로딩
레이아웃의 children 부분에 suspense로 자동 적용
로딩 최적화
하지만 페이지 내 컴포넌트들을 잘개 쪼개서 보여줄 수 있는 경우라면 loading.tsx를 사용하는 것보다 개별적으로 suspense들을 먹여주는 것이 UX를 고려했을 때 더 좋은 방법이다.
loading.tsx를 사용하면 전체를 묶어서 내부에 중첩 suspense를 할 수 없기 때문.
추가로 데이터 요청할 때 병렬적으로 처리하는 것이 좋음.
// async await를 사용하지 않고 병렬적으로 처리
const artistData = getArtist(username);
const albumData = getArtistAlbums(username);
// 사용자가 의미있는 데이터를 먼저 보는 사이 나머지가 로딩되도록 최적화
const artist = await artistData;
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<Albums promise={albumData} />
</Suspense>
</>
);
}
에러
로딩과 동일함. error.tsx를 만들 수도 있고, 개별적으로 ErrorBoundary를 사용할 수 있음.
같은 레벨의 레이아웃과 template는 에러 처리하지 못함. 하위 레벨만 에러 처리할 수 있음.
주의할 것은 에러는 클라이언트 컴포넌트라는 것.
props로 error와 reset을 받는다는 것.
"use client";
import { useEffect } from "react";
type ErrorProps = {
error: Error;
reset: () => void;
};
export default function Error({ error, reset }: ErrorProps) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
이미지
넥스트에서 제공하는 Image 태그를 사용하면 너비, 높이를 지정해주지 않아도 알아서 스크린에 맞게 사이즈 최적화까지 시켜줌...;
외부 url을 사용할 경우
1. 너비와 높이를 지정해줘야 함.
<Image src="~~" alt="이미지" width={400} height={400} priority />
next.config.js에 'images' 설정을 추가해줘야 함.
config 수정한 경우 재실행해야 적용 됨.
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com" // 사용하고자하는 도메인 url
}
]
}
}
우선 순위
중요도 높은 이미지의 경우 priority를 설정할 수 있음
{posts.map((post, index) => (
<li key={post.id} className="mb-4">
<PostListCard post={post} priority={index < 2} />
</li>
))}
type PostListCardProps = {
post: SimplePost;
priority?: boolean;
};
export default function PostListCard({ post, priority }: PostListCardProps) {
.
.
.
<Image
.
.
.
priority={priority}
/>
폰트
기본적으로 구글 폰트는 next 자체에서 제공함
최상위 레이아웃 html 태그에 className={sans.className}와 같은 형식으로 사용하면 됨.
개별적으로 지정할 경우 적용할 태그에 입력.
import { Open_Sans } from "next/font/google";
import { Nanum_Gothic } from "next/font/google";
const sans = Open_Sans({ subsets: ["latin"] });
// variable 폰트가 아닐 경우 굵기 지정해줘야 함.
const gothic = Nanum_Gothic({
weight: "700",
subsets: ["latin"],
});
SEO최적화 (metadata)
metadata를 생성함.
page에 만들 경우 해당 페이지에만 적용
layout에 만들게 되면 해당 레이아웃이 적용되는 모든 곳에 적용
import Link from "next/link";
import styles from "./layout.module.css";
import { Metadata } from "next";
export const metadata: Metadata = {
title: {
default: '넥스트 연습중',
template: '넥스트 연습중 | %s', // 레이아웃의 %s는 다른 페이지 Metadata의 title 값으로 대체 됨
},
description: "넥스트 연습중.",
icons: {
icon: "/src/app/favicon.ico",
},
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<header className={styles.header}>
<h1>헤더 연습</h1>
<nav className={styles.nav}>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/food">Food</Link>
</nav>
</header>
{children}
</html>
);
}
다이나믹 라우팅의 경우
서버로부터 데이터를 받아오는 경우 비동기 추가해주면 됨.
type FoodProps = {
params: {
slug: string;
};
};
export const generateMetadata = ({ params }: FoodProps) => {
return {
title: `음식 이름: ${params.slug}`,
};
};
const Foods = ({ params }: FoodProps) => {
return <div>{params.slug}</div>;
};
export default Foods;
export const generateStaticParams = () => {
const foods = ["rice", "pizza"];
return foods.map((food) => ({ slug: food }));
};
Redirect & 미들웨어 & Rewrite
Redirect
1. 주소가 바뀌었을 경우 (거의 영구적인 주소 변경 등)
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com"
}
]
},
async redirects() {
return [
{
source: '/food/rice', // 해당 경로로 오면
destination: '/food', // 여기로 보냄
permanent: true // 영원히 캐시. false의 경우 일시적이며 캐시되지 않는 307 상태 코드를 사용
}
]
}
}
module.exports = nextConfig
2. 특정 페이지로 이동시킬 때 (ex. 비인가 시 로그인 페이지로 이동)
if (비인가) {
redirect('/login');
}
3. navigate해야 할 경우 useRouter()로 대체
"use client";
import { useRouter } from "next/navigation";
import React from "react";
const Button = () => {
const router = useRouter();
return <button onClick={() => router.push("/food")}>Button</button>;
};
export default Button;
middleware
비인가에 대한 axios 인터셉터 처리처럼 리다이렉트를 자동화할 수 있음.
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("foods/rice")) {
return NextResponse.redirect(new URL("/foods", request.url)); // (보낼 곳, base url)
}
}
// 특정 페이지만 검사하고 싶을 경우
export const config = {
matcher: ['/foods/:path+']
}
Rewrite
주소를 다른 주소로 바꿀 수 있음.
민감한 데이터가 드러나는 경로이거나 실제 경로를 다르게 사용하고 싶은 경우 사용.
그러나 대체는 아니고 원래 주소 역시 접속 가능함
async rewrites() {
return [
{
source: '/food/', // 여기로 덮어씌움
destination: '/food/api/key', // 원래 주소
}
]
}
타입스크립트 오류
현재 버전에서 async/await를 사용한 컴포넌트를 page나 layout에서 가져오는 경우 아래와 같은 오류가 발생한다.
~~은(는) JSX 구성 요소로 사용할 수 없습니다.
해당 반환 형식 'Promise<Element>'은(는) 유효한 JSX 요소가 아닙니다.
'Promise<Element>' 형식에 'ReactElement<any, any>' 형식의 type, props, key 속성이 없습니다.
이 경우 문제가 해결된 버전이 나올 때까지 {/* @ts-expect-error Server Component */}를 추가해주는 식으로 해결해야 함
예시)
import FeaturedPosts from "@/components/FeaturedPosts";
import Profile from "@/components/Profile";
export default function Home() {
return (
<main>
<Profile />
{/* @ts-expect-error Server Component */}
<FeaturedPosts />
</main>
);
}
기타
process.env.NODE_ENV
아무런 설정을 해주지 않아도 Next.js에서 자체적으로 제공하는 기능
개발 환경에선 "development" 값이고 배포 환경에선 "production"값으로 알아서 스위칭된다.
'React > Next.js' 카테고리의 다른 글
Next.js 심화 (0) | 2023.06.20 |
---|---|
라우팅 심화 (0) | 2023.06.12 |
리액트에서 사용했던 기능들 (0) | 2023.06.07 |
API 백엔드 로직 (0) | 2023.06.06 |
SSR, SSG, ISR, CSR 방법 (0) | 2023.06.06 |