Next js의 Asset 최적화, Route Handler(벡엔드 구현가능) & Server Action, #\Next.js 캐싱(Caching)

정소현·2024년 9월 26일

Next.js

목록 보기
2/5

🌟 Next js의 Asset 최적화, Route Handler(벡엔드 구현가능) & Server Action, #\Next.js 캐싱(Caching)

☘️ Next js의 Asset 최적화

  1. Next/Image를 이용한 이미지 최적화
  • 이미지는 일반적인 웹사이트 페이지 무게의 아주 큰 부분을 차지하며, 웹사이트의 LCP(Largest Contentful Paint) 성능에 큰 영향을 미친다.

자동 이미지 최적화 기능

  • 크기최적화 : 각 기기에 맞는 크기의 이미지를 자동으로 제공
  • 로컬 이미지 설정
import { Product } from "@/type/product";
import Image from "next/image";

const NewProductList = async () => {
  const res = await fetch("http://localhost:4000/products", {
    cache: "no-store",
  });
  const data: Product[] = await res.json();
  const newData = data.filter((p) => p.isNew);

  return (
    <div className="flex gap-2 oveflow-auto w-full">
      <div className="w-max flex gap-2">
        {newData.map((product) => (
          <div className="flex gap-2 w-[250px] border rounded-sm" key={product.id}>
            <Image
              className="rounded-sm object-scale-down"
              width={80}
              height={80}
              src={product.images}
              alt={product.title}
            />
            <div className="flex flex-col justify-between">
              <div>
                <h2 className="text-md font-medium">{product.title}</h2>
                <p className="mt-4 font-thin">{product.price.amount}$</p>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default NewProductList;
  • 원격이미지 설정
import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="<network-image>"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}
  • 원격 이미지 사용을 위한 추가 설정
    next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: false,
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "fakestoreapi.com",
        pathname: "/**",
      },
    ],
    formats: ["image/avif", "image/webp"],
  },
};
  • AVIF,WebP추가

Metadata를 통한 SEO최적화

Next.js가 제공하는 메타 데이터를 정의하기 위한 두 가지 방법
1. Config 기반 메타데이터 : layout.js또는 page.js파일에서 정적 메타데이터 객체 또는 동적 generateMetadata함수를 내보낸다.
2. 파일 기반 메타데이터 : 라우트 세그먼트에 정적 또는 동적으로 생성된 특별한 파일을 추가한다. 두 옵션 모두에서 Next.js는 페이지에 대한 관련 head요소를 자동으로 생성한다.

  1. 정적 메타데이터
  • 정적 메타데이터 정의시 layout.js또는 page.js파일에서 Metadata객체를 내보낸다.
// layout.tsx | page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}
  1. 동적 메타데이터
  • 동적 값이 필요한 메타데이터를 가져오려면 generateMetadata함수를 사용가능
// app/products/[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,
): Promise<Metadata> {
  // 경로 매개변수 읽기
  const id = params.id

  // 데이터 가져오기
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  return {
    title: product.title,
  }
}

export default function Page({ params, searchParams }: Props) {}

파일 기반 메타 데이터

  • favicon.ico, apple-icon.jpg, icon.jpg
  • opengraph-image.jpgtwitter-image.jpg
  • robots.txt
  • sitemap.xml

☘️ 데이터 적용

Next.js에서는 public 폴더를 사용하여 정적 파일을 쉽게 제공할 수 있다. public 폴더 내의 파일은 기본 URL(/)을 기준으로 참조할 수 있다.

ex1) Logo.png

import Image from 'next/image'
import Logo from "/public/assets/logo.png";

<Image height={40} src={Logo} alt="logo"></Image>

☘️ Font Optimization

  • next/font를 사용하면 폰트를 자동으로 최적화하고 외부 네트워크 요청을 제거하여 개인정보 보호와 성능을 향상시킬 수 있음
  • next/font는 모든 폰트 파일에 대해 자동으로 셀프 호스팅을 제공합니다. 레이아웃 시프팅(Layout Shifting) 없이 최적의 방식으로 웹 폰트를 로드할 수 있도록 도와줌

- Google Fonts

next/font/google에서 사용할 폰트를 가져와 함수로 사용

import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

- Multiple Fonts 사용

  • 여러 폰트를 사용해야 할 때는 폰트를 내보내고 필요한 곳에서 가져와서 사용
import { Inter, Roboto_Mono } from 'next/font/google'

export const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
})

export const roboto_mono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
})

import { inter } from './fonts'

export default function Layout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>
        <div>{children}</div>
      </body>
    </html>
  )
}

- Local Fonts사용

import localFont from 'next/font/local'
 
// Font files can be colocated inside of `app`
const myFont = localFont({
  src: './my-font.woff2',
  display: 'swap',
})
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className={myFont.className}>
      <body>{children}</body>
    </html>
  )
}

- Script사용

  • next/script를 사용하여 스크립트를 최적화할 수 있다.

사용법

import Script from 'next/script'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
      <Script src="https://example.com/script.js" />
    </html>
  )
}

스크립트 로딩 전략을 세부적으로 조정할 수 있다:

  • beforeInteractive: Next.js 코드 및 페이지 하이드레이션 전에 스크립트 로드
  • afterInteractive: (기본값) 페이지 하이드레이션 후 스크립트 로드
  • lazyOnload: 브라우저 유휴 시간에 스크립트 로드
  • worker: (실험적) 웹 워커에서 스크립트 로드

🌟 Route Handler(벡엔드 구현가능) & Server Action

1. Router handler

api>apiTest를 만든 후 하위에 route.ts파일 생성

export async function GET(request: Request) {
  console.log("GET /api/test");
}

export async function POST(request: Request) {
  console.log("POST /api/test");
}

export async function PUT(request: Request) {
  console.log("PUT /api/test");
}

export async function DELETE(request: Request) {
  console.log("DELETE /api/test");
}

export async function PATCH(request: Request) {
  console.log("PATCH /api/test");
}

Thunder Client로 테스트
POST / http://localhost:3000/api/test

2. Server Action

  • 서버에서 실행되는 비동기 함수, 폼 제출과 같은 데이터를 처리할 수 있게 함
  • 사용시 "use server"지시어 사용
  • 주로 CRUD작업수행하는데 사용
"use server";
import { Product } from "@/type/product";
import { BASE_URL } from "@/constants/api";

export async function getProducts() {
  const res = await fetch(`${BASE_URL}/products`, {
    cache: "no-store",
  });
  const data: Product[] = await res.json();

  return { data };
}

ex) cart같은 곳 값 넣을 때

'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function addItemToCart(formData: FormData) {
  const { itemId, quantity } = formData.get('itemId'), formData.get('quantity');
  const cart = await fetch({...});

  revalidatePath('/cart');
  redirect('/cart');
}

Next.js 캐싱(Caching)

Next.js는 대부분의 영역에서 fetch함수를 기반으로 캐싱을 한다.
Next.js의 fetch API를 기반으로 Next.js에서 확장한 새로운 fetch API이기 떄문이다.

  1. 정적페이지일때
    (fetch option이나 cache:force-cache)
  • build시 요청시 캐싱을 진행함
  1. Data Cache
  • fetch함수를 기반으로 데이터를 캐싱한다.
  • 동적라우팅일 때 특정 fetch만 caching 사용
    cache: "no-store"

server-actions.ts 설정

// server-action.ts
export async function rePath() {
  revalidatePath("/");
}

export async function reTag() {
  revalidateTag("products");
}

Parellel & Intercepting Routes

  1. Parellel Routes
  • 동일한 레이아웃 내에서 여러 페이지를 동시에 또는 조건부로 렌더링할 수 있게 해준다. 대시보드나 소셜 사이트의 피드와 같은 동적인 섹션에 유용하다.
  1. Intercepting Routes
  • 사용자가 페이지를 이동하지 않고도 특정 컨텐츠를 오버레이로 표시할 수 있게 해주는 라우팅 방식, 예슬 들어서, ㅍ드ㅔ서 사진을 클릭하면 해당 상품 사진이 모달로 표시된다. 하지만 URL을 직접 입력하거나 페이지를 새로 고침할 때는 전체 페이지기 다시 렌더링된다. 즉 소프트 네비게이션을 할 때에만 특정 라우트를 가로채서 다른 UI를 보여준다. 보편적으로는 Parellel Route와 함께 사용되는 고급 라우팅 기법이다.

라우팅 규칙

  • (.): 동일한 레벨의 세그먼트와 매칭
  • (..): 한 레벨 위의 세그먼트와 매칭
  • (..)(..): 두 레벨 위의 세그먼트와 매칭
  • (...): 루트 디렉토리에서 시작하는 모든 세그먼트와 매칭
import { Product } from "@/type/product";
import Image from "next/image";

type Props = {
  params: {
    id: string;
  };
};

const ProductModal = async ({ params }: Props) => {
  const id = parseInt(params.id, 10);
  const res = await fetch(`http:localhost:4000/products/${id}`, {
    cache: "no-store",
  });
  const data: Product = await res.json();

  return (
    <div className="fixed rounded-md text-black bg-white/90 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[300px] h-[300px]">
      <div className="flex flex-col p-5 gap-2 items-center">
        <Image
          src={data.images}
          alt={data.title}
          width={100}
          height={100}
          className="w-[100px] h-[100px] object-cover"
        ></Image>
        <div className="text-lg font-bold ">{data.title}</div>
        <div className="line-clamp-3">{data.description}</div>
        <button className="bg-gray-800 text-white px-4 py-2 rounded-md">
          View Detail
        </button>
      </div>
    </div>
  );
};

export default ProductModal;
profile
기술을 넘어 제품의 가치를 만드는 프론트엔드 엔지니어를 지향합니다.

0개의 댓글