Next.js
Next.js 서버 액션이 SEO에 영향을 미치지 않도록 하기
동적 콘텐츠를 생성하는 서버 액션을 사용할 때 검색 엔진 최적화와 메타 태그 관리 방법.
서버 액션과 SEO의 충돌
Next.js 13+ 서버 액션은 정말 편하지만, 잘못 쓰면 SEO에 나쁜 영향을 미칠 수 있다. 특히 클라이언트 컴포넌트에서 useTransition으로 서버 액션을 호출할 때, 검색 봇이 동적으로 바뀐 콘텐츠를 제대로 인덱싱하지 못한다.
검색 결과에 "유사한 페이지 여러 개"라고 뜨거나 메타 태그가 이상하게 나타나면, 서버 액션과 메타 태그 관리가 제대로 되지 않았을 가능성이 높다.
메타 태그 확인하기
배포 전에 실제 페이지의 HTML을 확인해야 한다.
curl -s https://example.com/product/123 | head -50
출력에서 이런 부분이 있는지 확인:
<title>상품명 - 회사명</title>
<meta name="description" content="상품 설명...">
<link rel="canonical" href="https://example.com/product/123">
<meta property="og:title" content="상품명">
<meta property="og:description" content="상품 설명...">
<meta property="og:image" content="https://example.com/image.jpg">
없다면 메타 태그 설정이 제대로 되지 않은 거다.
올바른 패턴
페이지 컴포넌트에서 메타 태그 생성 (서버 컴포넌트):
// app/product/[id]/page.tsx
export async function generateMetadata({
params: { id }
}: {
params: { id: string }
}): Promise<Metadata> {
const product = await getProduct(id);
return {
title: product.name,
description: product.description,
openGraph: {
title: product.name,
description: product.description,
images: [{ url: product.image }],
},
};
}
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id);
return <ProductDetail product={product} />;
}
서버 액션은 데이터만 가져오기 (UI 업데이트는 클라이언트에서):
// 서버 액션 (app/actions.ts)
'use server'
export async function getProductReviews(productId: string) {
return await fetchReviews(productId);
}
// 클라이언트 컴포넌트 (app/product/[id]/reviews.tsx)
'use client'
export function ProductReviews({ productId }: { productId: string }) {
const [reviews, setReviews] = useState([]);
const [isPending, startTransition] = useTransition();
useEffect(() => {
startTransition(async () => {
const data = await getProductReviews(productId);
setReviews(data);
});
}, [productId]);
return <ReviewList reviews={reviews} />;
}
Sitemap과 RSS 확인
검색 봇이 모든 페이지를 찾을 수 있도록 sitemap과 robots.txt를 설정해야 한다.
// app/sitemap.ts
export default async function sitemap() {
const products = await getAllProducts();
return products.map(product => ({
url: `https://example.com/product/${product.id}`,
lastModified: product.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.8,
}));
}
# sitemap이 제대로 생성되는지 확인
curl https://example.com/sitemap.xml
배포 전 SEO 체크리스트
- 메인 페이지의 title과 description이 설정되어 있는가?
- 동적 페이지의 메타 데이터가
generateMetadata로 설정되는가? -
canonical태그가 모든 페이지에 있는가? - 서버 액션으로 업데이트되는 부분은 클라이언트 컴포넌트인가?
- sitemap.xml이 생성되고 모든 페이지를 포함하는가?
- robots.txt가 있고 sitemap을 참조하는가?
이 항목들을 확인한 후 배포하면 검색 엔진에서 제대로 인덱싱될 가능성이 훨씬 높아진다.