Next.js14 App Router 2편

HughKim·2024년 5월 26일
1

Nextjs14

목록 보기
2/6
post-thumbnail

Data Fetching

Next.js에서는 서버의 각 가져오기 요청에 대한 캐싱 및 재검증 동작을 구성할 수 있습니다.

  • 데이터 캐싱
const getData = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts", { cache: "force-cache" });
  if (!res.ok) {
    throw new Error("Something went wrong");
  }
  return res.json();
};

fetch 내에 있는 cache 옵션 중 force-cache를 사용하면, 기본적으로 Next.js가 반환된 값을 서버의 데이터 캐시에 자동으로 저장합니다. 이는 빌드 시간이나 요청 시간에 데이터를 불러오고, 캐시하여, 각각의 데이터 요청마다 재사용 가능하다는 것을 의미합니다. 데이터 캐시는 설정된 시간 동안 웹 서버로 들어오는 모든 요청에 대해 활성화됩니다. 이러한 메커니즘 덕분에 1000명의 사용자가 접속한다 해도 실제로 API로는 단 한 번의 요청만이 이루어져서 큰 효율성을 자랑합니다. 또한, 캐시되지 않은 데이터(예: { cache: 'no-store' })는 항상 데이터 소스에서 직접 가져와 저장됩니다.

  • 데이터 재검증
const getData = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts", { next: { revalidate: 1000 } });
  if (!res.ok) {
    throw new Error("Something went wrong");
  }
  return res.json();
};

데이터 재검증(Revalidating Data)은 데이터 캐시를 제거하고 최신 데이터를 다시 가져오는 과정입니다. 이는 데이터가 변경되어 최신 정보를 제공해야 할 때 유용합니다. 현재 next.revalidate를 1초로 설정해두었기 때문에, 1초 동안 1,000명의 사용자가 접속하더라도 실제 API 요청은 단 한 번만 전송됩니다.

SEO

SEO(Search Engine Optimization, 검색 엔진 최적화)는 구글과 같은 검색 엔진에 친화적인 사이트를 구축하여, 광고가 아닌 자연 검색 결과를 통해 트래픽의 양과 질을 극대화하는 작업을 의미합니다. 웹사이트의 기술적인 부분을 개선하여 검색 엔진이 웹사이트의 콘텐츠를 잘 이해하고 인덱싱할 수 있도록 하는 것도 최적화 작업의 중요한 부분입니다. 그러나 궁극적으로, SEO는 사용자들에게 유용하고 양질의 콘텐츠를 제공함으로써, 다양한 관련 키워드로 검색 결과 페이지에 노출되는 것을 목표로 하는 마케팅 전략입니다. 이를 통해 웹사이트의 온라인 가시성을 크게 개선할 수 있습니다.

export const metadata = {
  title: "FEDown | Introduction",
  description: "Frontend developer",
};

Next.js에서는 이러한 SEO를 간편하게 구성할 수 있습니다. 아래 코드는 정적 메타데이터를 설정하는 예입니다. SEO를 위해 페이지나 레이아웃에 메타데이터를 추가하고, 웹 페이지의 제목과 설명을 작성하면 됩니다.

metadata를 사용 시에는 server component로 구성된 공간에서만 사용이 가능합니다. 만약 client component가 되는 공간에는 아래와 같은 에러가 발생합니다.

Error:
× You are attempting to export "metadata" from a component marked with "use client", which is disallowed. Either remove the export, or the "use client" directive.

아울러 app의 layout.js파일에 아래와 같은 코드로 작성 시 반복적으로 처리 된 내용을 하나로 묶어서 진행 할 수도 있습니다.

export const metadata = {
  title: {
    default: "FEDowon | home",
    template: "%s | FEDowon",
  },
  description: "Frontend developer",
};

about page

export const metadata = {
  title: "About",
  description: "About Description",
};

동적 metadata

export const generateMetadata = async ({ params }) => {
  const { slug } = params;
  const post = await getPost(slug);
  return {
    title: post.title,
    description: post.desc,
  };
};

metadata를 동적으로 구성하기 위해서는 비동기적으로 데이터 처리를 진행하고 그 상태에서 title와 description을 작성하면 됩니다.

Server action

server action을 사용하기 위해서는 "use server"를 작성하여 진행합니다.

const ServerTestPage = () => {
  const actionComponent = async () => {
    "use server";
    console.log("it works back");
  };
  return (
    <div>
      <form action={actionComponent}>
        <button>Test me</button>
      </form>
    </div>
  );
};
export default ServerTestPage;

함께 server가 작성된 code입니다. lib안에 action.js를 만들고 form action에 actionComponent를 넣어도 동일하게 작동 됩니다.
일단 "use server"를 작성함으로서 server에서 동작할 수 있게 됩니다.

다음 사진과 같이 "Test me" 버튼을 클릭하면, 서버에서 반응하여 터미널에 console.log 값이 출력되는 것을 확인할 수 있습니다. 또한, 네트워크 탭에서도 서버의 동작이 진행된 것을 확인할 수 있습니다.

revalidatePath

export const addPsot = async (formData) => {
  "use server";
  const { title, desc, slug, userId } = Object.fromEntries(formData);
  try {
    connectToDb();
    const newPost = new Post({ title, desc, slug, userId });
    await newPost.save();
    revalidatePath("/blog");
  } catch (err) {
    console.log(err);
    return { err: "Something went wrong" };
  }
};

revalidatePath 함수는 특정 경로에 대해 필요에 따라 캐시된 데이터를 제거하고, 최신 데이터를 제공하기 위해 캐시된 데이터를 재검증하는 데 사용됩니다.

API Route


URL에서 확인할 수 있듯이, 'api/blog/post1' 같은 경로 구성이 가능합니다. Next.js에서는 앱 내부에 'api'라는 폴더를 만들어 이와 유사한 방식으로 진행할 수 있습니다.

예시코드는 mongodb를 이용하여 만든 route.js입니다.

import { Post } from "@/lib/models";
import { connectToDb } from "@/lib/utils";
import { NextResponse } from "next/server";

export const GET = async (req) => {
  try {
    connectToDb();
    const posts = await Post.find();
    return NextResponse.json(posts);
  } catch (err) {
    console.error("Failed to fetch posts");
  }
};

작성된 코드를 보면 NextResponse이 있습니다. NextResponse는 더 많은 편의 기능을 제공하는 웹 응답 API를 확장합니다.

NextResponse option

  • cookies : 응답의 Set-Cookie 헤더를 읽거나 수정할 수 있습니다.
  • set(name, value) : 주어진 이름에 대한 값을 사용하여 응답에 쿠키를 설정합니다.
let response = NextResponse.next()
response.cookies.set('show-banner', 'false')
return response
// Response will have a `Set-Cookie:show-banner=false;path=/home` header
  • get(name) : 쿠키 이름을 제공하면 해당 쿠키의 값을 반환합니다. 쿠키를 찾을 수 없는 경우 undefined가 반환됩니다. 여러 개의 쿠키가 발견되면 첫 번째 쿠키가 반환됩니다.
let response = NextResponse.next()
// { name: 'show-banner', value: 'false', Path: '/home' }
response.cookies.get('show-banner')
  • getAll() : 쿠키 이름을 제공하면 해당 쿠키의 값들을 반환합니다. 이름이 제공되지 않은 경우 응답에 있는 모든 쿠키를 반환합니다.
let response = NextResponse.next()
// [
//   { name: 'experiments', value: 'new-pricing-page', Path: '/home' },
//   { name: 'experiments', value: 'winter-launch', Path: '/home' },
// ]
response.cookies.getAll('experiments')
// Alternatively, get all cookies for the response
response.cookies.getAll()
  • delete(name) : 쿠키 이름이 주어진 경우, 해당 쿠키를 응답에서 삭제합니다.
let response = NextResponse.next()
// Returns true for deleted, false is nothing is deleted
response.cookies.delete('experiments')
  • json() : 주어진 JSON 데이터를 포함한 응답을 생성합니다.
import { NextResponse } from 'next/server'
 
export async function GET(request: Request) {
  return NextResponse.json(
    { error: 'Internal Server Error' }, { status: 500 }
  )
}
  • redirect() : URL을 생성하여 수정한 후 NextResponse.redirect() 메서드에서 사용할 수 있습니다. 예를 들어, request.nextUrl 속성을 사용하여 현재 URL을 가져와서 다른 URL로 리다이렉션할 수 있습니다.
import { NextResponse } from 'next/server'
 
return NextResponse.redirect(new URL('/new', request.url))
  • rewrite() : 제공된 URL을 유지하면서 다시 작성(프록시)하는 응답을 생성합니다.
import { NextResponse } from 'next/server'
 
// Incoming request: /about, browser shows /about
// Rewritten request: /proxy, browser shows /about
return NextResponse.rewrite(new URL('/proxy', request.url))
  • next() : 메서드를 사용하면 일찍 반환할 수 있고 라우팅을 계속할 수 있습니다. 미들웨어에 유용합니다.
import { NextResponse } from 'next/server'
 
return NextResponse.next()

Auth.js

Auth.js API Reference
auth.js는 Next.js를 포함한 다양한 웹 프레임워크를 지원하며, 인증 관련 개발을 더욱 쉽고 다양하게 진행할 수 있게 도와줍니다. 예를 들어, 카카오, 네이버, 구글, 깃헙 등의 로그인을 손쉽게 구현할 수 있습니다.

사진보다 더 많은 provider을 지원하여 쉽게 auth를 개발할 수 있습니다.
예시) 깃헙일 경우

import NextAuth from "next-auth";
import Github from "next-auth/providers/github";
export const {
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
} = NextAuth({
  providers: [Github({ clientId: process.env.GITHUB_ID,
  clientSecret: process.env.GITHUB_SECRET })],
});

login page

import { signIn } from "@/lib/auth";
import React from "react";

const LoginPage = () => {
  const handleGithubLogin = async () => {
    "use server";
    await signIn("github");
  };
  return (
    <div>
      <form action={handleGithubLogin}>
        <button>Login with Github</button>
      </form>
    </div>
  );
};

export default LoginPage;

위에 코드로 구성하면 이제 우리가 깃헙으로 로그인하던 모습이 나타납니다.

개발하실 때 주의사항은 async, await같은 경우 client가 아닌 server component에서 진행하는 것이 좋습니다.

const Navbar = async () => {
  const session = await auth();

  return (
    <div className={styles.container}>
      <Link href="/" className={styles.logo}>
        GOGUMA
      </Link>
      <div>
        <Links session={session} />
      </div>
    </div>
  );
};

Middleware

Middleware
미들웨어를 사용하면 요청이 완료되기 전에 코드를 실행하여 다양한 작업을 수행할 수 있습니다. 이는 들어오는 요청에 따라 요청이나 응답 헤더를 재작성하고, 리디렉션을 수행하며, 요청을 수정하거나 직접 응답을 제공하여 응답을 조정할 수 있습니다. 구체적으로 다음과 같은 기능을 제공합니다:

  • 인증 및 권한 부여: 특정 페이지나 API 경로에 접근하기 전에 사용자의 신원을 확인하고 세션 쿠키를 검사하여 적절한 권한을 부여합니다.
  • 서버 측 리디렉션: 특정 조건에 따라 서버에서 사용자를 다른 경로로 리디렉션합니다.
  • 경로 재작성: 요청 속성을 기반으로 API 경로나 페이지에 대한 경로를 동적으로 재작성하여 A/B 테스트, 기능 출시, 레거시 경로 지원 등을 구현할 수 있습니다.
  • 로깅 및 분석: 요청을 페이지나 API에서 처리하기 전에 데이터를 캡처하고 분석하여 유용한 통찰을 얻습니다.
  • 기능 플래그 지정: 기능을 동적으로 활성화하거나 비활성화하여 기능 출시나 테스트를 원활하게 수행합니다.

이러한 기능을 통해 미들웨어는 웹 애플리케이션의 유연성과 관리성을 크게 향상시킵니다.

middleware.js파일은 src 디렉토리 경로에 있어야 합니다.

import NextAuth from "next-auth";
import { authConfig } from "./lib/auth.config";

export default NextAuth(authConfig).auth;

export const config = {
  matcher: ["/((?!api|static|.*\\..*|_next).*)"],
};

useFormState

useFormState는 React Hook의 일환으로, 폼 관련 데이터 및 상태 관리에 주로 활용되며, 입력 필드의 값 관리나 유효성 검사에 있어 매우 유용하게 쓰입니다. 이를 통해 React 양식에서의 데이터 처리가 한층 더 간결하고 효율적으로 이루어집니다.

  • 상태 관리의 간소화: React Hook을 통해 폼 상태를 손쉽게 관리할 수 있어, 코드의 복잡성을 줄이고 개발 과정을 더욱 신속하게 진행할 수 있습니다.
  • 강력한 유효성 검사 및 오류 처리 기능: React Hook Form과 같은 라이브러리를 함께 사용함으로써, 정교한 유효성 검사와 오류 처리가 가능해져, 사용자 경험을 크게 향상시킬 수 있습니다.
  • 성능 최적화의 이점: React Hook을 활용함으로써, 필요한 경우에만 컴포넌트가 리렌더링되도록 하여 불필요한 리렌더링을 방지할 수 있으며, 이는 애플리케이션의 전반적인 성능 최적화에 기여합니다.

이러한 특징들 덕분에 useFormState는 React 기반의 웹 애플리케이션 개발 시 폼 관리를 위한 강력한 도구로 자리매김하고 있습니다.

"use client";

import { addPost } from "@/lib/action";
import styles from "./adminPostForm.module.css";
import { useFormState } from "react-dom";

const AdminPostForm = ({ userId }) => {
  const [state, formAction] = useFormState(addPost, undefined);

  return (
    <form action={formAction} className={styles.container}>
      <h1>Add New Post</h1>
      <input type="hidden" name="userId" value={userId} />
      <input type="text" name="title" placeholder="Title" />
      <input type="text" name="slug" placeholder="slug" />
      <input type="text" name="img" placeholder="img" />
      <textarea type="text" name="desc" placeholder="desc" rows={10} />
      <button>Add</button>
      {state?.error}
    </form>
  );
};

export default AdminPostForm;

Next.js 14버전의 기본적인 사항을 학습하셨으니, 이제 GitHub에서 다양한 코드 예제를 살펴보며 자신만의 포트폴리오를 만들어 보세요. Next.js를 적용해 복습하고 실습하면서 더욱 깊이 있게 익혀보시기 바랍니다.
Nextjs14버전 project github
next14로 제작된 portfolio project

Error: ' can be escaped with &apos;, &lsquo;, &#39;, &rsquo;. react/no-unescaped-entities 에러

next js 를 vercel에 빌드하려다 생긴 에러가 발생했습니다. 이런 이유를 error메세지를 확인 시 '를 그대로 넣어서 발생하였다고 했습니다. 그래서 콤마보다는 html entities를 사용하여 에러를 해결하였습니다.

profile
성장에 미쳐버린 Frontend Developer

0개의 댓글