앞선 포스팅에선 SEO 최적화하는 방법을 알아보았다.
이젠 실제로 구현해볼 것이다!
요샌 회사든 개인 플젝이든 next.js로 작업하고 있어서 next.js 14버전으로 해보장
터미널에 npx create-next-app seo를 입력하여
프로젝트 이름 seo로 next 를 설치해주었다.
제일 먼저 해볼 것은 metadata 설정해보기!
사실 cra 하면 layout.tsx에 metadata가 설정된 채로 나온다.
하지만 이 root layout 페이지는 제일 최상단에 위치하므로 우선순위가 제일 낮음
우리 프로젝트의 목적을 넣어준다
지금은 SEO가 목적이니 title에 SEO 적어주고 설명도 아무거나 적어주면 끝!
title과 description이 잘 적용된 모습이다!
이건 정적 상황에 적용된 title, description이고 이번엔 동적 상황에도 적용해보자.
동적의 상황의 예시로는 서버에서 값을 불러오는 방식이 있다.
[id]안에 page.tsx로 페이지 하나파주고
api 폴더 아래에 route.ts로 서버 코드도 하나 파준다.
route.ts
import { NextResponse } from 'next/server';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const name = searchParams.get('name');
const productName = `Product ${name}`;
return NextResponse.json(productName);
}
쿼리스트링으로 name 값을 넣어주면, Product {name} 으로 반환하는 코드.
page.tsx
import type { Metadata, ResolvingMetadata } from 'next';
type Props = {
params: { id: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const id = params.id;
const product = await fetch(`http://localhost:3000/api?name=${id}`).then(
(res) => res.json()
);
return {
title: {product}
};
}
export default function Page() {
return <div></div>;
}
title에 '상품정보 :' 적어주고 product를 동적으로 받아온다.
open graph protocol을 적용하는 방법은 openGraph 객체를 넣어주면 된다!
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const id = params.id;
const baseURL = 'https://seo-fawn.vercel.app';
const product = await fetch(`${baseURL}/api?name=${id}`).then((res) =>
res.json()
);
const previousImages = (await parent).openGraph?.images || [];
return {
metadataBase: new URL(baseURL),
title: `${product}`,
openGraph: {
title: '저랑 공부하실래용?',
description: '챕터 1. SEO',
images: [
{
url: '/opengraph-image.png',
alt: '이미지 설명',
},
],
},
twitter: {
title: '트위터 하이!',
description: '챕터 1. SEO',
images: [
{
url: '/twitter-image.png',
alt: '이미지 설명',
},
],
},
};
}
배포를 한 후 소스를 보면, 잘 적용된 모습을 볼 수 있다.
meetadataBase는 baseURL을 정해주는 역할이다! 본인의 url을 넣어주면 된다.
실제 트위터에 넣어봤더니 잘 적용된 모습
근데 카톡은 왜인지 적용이 안 된당...
찾아보니, 카카오톡에 og가 캐시되어 있어서 그랬던 것!
카카오 developer 링크에 들어가서 로그인 후 og 캐시 지우기를 클릭하면 위의 사진과 같이 잘 나온다.
위는 캐시 지우기 전, 아래는 캐시 지운 후.
하나하나 내가 게시글 해보는 수밖에 없나 싶었는데, 역시 할 수 있는 방법이 있었다!
실험 사이트 에 들어가서 링크를 입력하면 각 페이지들에 어떻게 보이는지 확인할 수 있다.
페이스북, 링크드인, 디스코드 모두 잘 나오는 것을 확인 :D
다 설정이 완료되었으면, 검색엔진에 사이트를 등록해줘야 검색엔진이 크롤링을 할 수 있다.
검색엔진에 사이트 등록 글을 보며 참고하여 진행하였다.
소유권 확인을 하는 부분에서 엄청 헤맸다.. 한 2-3시간정도 폭풍 구글링한듯...ㅠㅠ
verification: {
google: [google-code],
other: {
'naver-site-verification':[naver-code],
},
},
결과적으로는 metadata 넣는 곳에 이렇게 넣으니 소유권 확인을 할 수 있었다..!
next.js의 특성 때문에 조금 헤맸었던 것 같다..
아무튼 성공!
하루, 이틀정도 걸린다는데 내일 검색해봐야겠당
robots.txt 파일도 next.js에서는 설정하는 방법이 따로있다
공식문서 참고하여 만들어주었다.
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
};
}
잘 적용된 모습도 확인해준다.
metadata의 robots로도 index 여부, follow 여부를 설정해줄 수 있다.
robots: {
index: true,
follow: true,
},
이것또한 next.js에서는 metadata에 객체로 적어준다.
all : 색인 및 크롤링에 제한이 없는 기본값. index, follow를 동시에 적용한 것과 동일
index : 페이지가 색인되어 검색 결과에 표시될 수 있음
noindex : 검색 결과에 페이지를 표시하지 않음
follow : 페이지를 포함해 링크된 페이지를 검색할 수 있음
nofollow : 페이지를 포함해 링크된 페이지를 수집하지 않음
none : noindex, nofollow를 동시에 적용한것과 동일
noarchive : 비표준, 페이지를 캐시하지 않음
nosnippet : 비표준, 검색 결과에 페이지에 대한 스니핏 혹은 미리보기를 지원하지 않음
출처 : Spemer(Hyouk Seo)
적용 결과 확인 :D
공식문서 보면서 설정해보자!
const Sitemap = async (): Promise<MetadataRoute.Sitemap> => {
return [
{
url: BASE_URL,
lastModified: new Date(),
changeFrequency: 'always',
priority: 0.5,
},
];
};
export default Sitemap;
url엔 BASE_URL 상수화 해뒀던 거 적어주고,
lastModified에는 수정날짜가 들어가도록 해준다.
changeFrequency는 얼마나 이 페이지가 자주 바뀌는지 여부!
-> 로봇이 참고만 하고 그대로 적용하지는 않는다고 한다.
priority는 이 페이지의 우선순위이다.
이러면 또 해봐야 할 고민이 있다.
우리는 /water, /paper 과 같이 상품 페이지를 만들 것인데, 동적인 경우에는 어떻게 해줄 수 있을까?
import { MetadataRoute } from 'next';
import { BASE_URL } from './constant';
export const getProducts = () => {
return fetch(`${BASE_URL}/api/all`)
.then((res) => {
if (!res.ok) {
return Promise.reject();
}
return res.json();
})
.catch(() => {
return [];
});
};
const Sitemap = async (): Promise<MetadataRoute.Sitemap> => {
const products: string[] = await getProducts();
const blogPosts = products.map((post) => ({
url: `${BASE_URL}/${post}`,
lastModified: new Date(),
changeFrequency: 'always' as 'always',
priority: 1,
}));
return [
{
url: BASE_URL,
lastModified: new Date(),
changeFrequency: 'always',
priority: 0.5,
},
...blogPosts,
];
};
export default Sitemap;
API를 불러와서 동적으로 이와 같이 처리가 가능하다!
상품 페이지가 중요도가 더 높으므로 priority를 1로 설정해준다.
결과!
--
+) 추가
이동할 때는 Link 태그 사용하기
+) 추가
성공해따!!!