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(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을 사용하기 위해서는 "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 값이 출력되는 것을 확인할 수 있습니다. 또한, 네트워크 탭에서도 서버의 동작이 진행된 것을 확인할 수 있습니다.
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 함수는 특정 경로에 대해 필요에 따라 캐시된 데이터를 제거하고, 최신 데이터를 제공하기 위해 캐시된 데이터를 재검증하는 데 사용됩니다.
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를 확장합니다.
let response = NextResponse.next()
response.cookies.set('show-banner', 'false')
return response
// Response will have a `Set-Cookie:show-banner=false;path=/home` header
let response = NextResponse.next()
// { name: 'show-banner', value: 'false', Path: '/home' }
response.cookies.get('show-banner')
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()
let response = NextResponse.next()
// Returns true for deleted, false is nothing is deleted
response.cookies.delete('experiments')
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
return NextResponse.json(
{ error: 'Internal Server Error' }, { status: 500 }
)
}
import { NextResponse } from 'next/server'
return NextResponse.redirect(new URL('/new', request.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))
import { NextResponse } from 'next/server'
return NextResponse.next()
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.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는 React Hook의 일환으로, 폼 관련 데이터 및 상태 관리에 주로 활용되며, 입력 필드의 값 관리나 유효성 검사에 있어 매우 유용하게 쓰입니다. 이를 통해 React 양식에서의 데이터 처리가 한층 더 간결하고 효율적으로 이루어집니다.
이러한 특징들 덕분에 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'
,‘
,'
,’
. react/no-unescaped-entities 에러
next js 를 vercel에 빌드하려다 생긴 에러가 발생했습니다. 이런 이유를 error메세지를 확인 시 '를 그대로 넣어서 발생하였다고 했습니다. 그래서 콤마보다는 html entities를 사용하여 에러를 해결하였습니다.