가수면

Next.js 기본 본문

React/Next.js

Next.js 기본

니비앙 2023. 6. 2. 17:40

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
Comments