κΈ°μ‘΄ Vite + Monorepo λ‘ Blog μ Portfolio νμ΄μ§λ₯Ό λ§λλ νλ‘μ νΈλ₯Ό μ§ννμλλ° , ν΄λΉ Monorepo κ° Vite λ‘ μ΄λ£¨μ΄μ Έ μμ΄μ κ²μ λ±μμ λΆλ¦¬ν λΆλΆμ΄ μμλ€. ( Blog μΈλ° κ²μμ΄ μλλ€λ©΄ ... )
λ°λΌμ μ΄λ₯Ό ν΄κ²°νκ³ , λ μΆκ°λ‘ Nextjs λ₯Ό μ μ©ν΄λ³΄κ³ μ νλ λ§μμ΄ μμ΄μ NextJs + Turborepo λ₯Ό μ¬μ©ν΄μ λ§μ΄κ·Έλ μ΄μ νκΈ°λ‘ νλ€ .
Vite + Monorepo νλ‘μ νΈλ κΈ°λ³Έμ μΌλ‘ CSR μ μ§μνκ³ μμκ³ , λ³κ²½λ νλ‘μ νΈλ κΈ°λ³Έμ μΌλ‘ SSR μ μ§μνλλ‘ μ€μ νκ³ μ νλ€. κ·Έ μ μ , CSR κ³Ό SSR μ μ°¨μ΄λ 무μμΈμ§ κ°λ¨νκ² μ 리νλ €κ³ νλ€.
CSR μ κΈ°λ³Έμ μΌλ‘ μΉ νμ΄μ§μ μ μνμ λ μ€μ μ½ν μΈ κ° κ±°μ μλ μνμ λλ€. μ΄ λ μλ²λ‘λΆν° κΈ°λ³Έμ μΈ HTML λΌλμ JS νμΌλ€μ λ°μ΅λλ€. νμν λ°μ΄ν°λ₯Ό μλ²μ API μμ²μ ν΅ν΄μ λΉλκΈ°μ μΌλ‘ κ°μ Έμ¨ ν JS νμΌλ€μ μ€νν΄μ DOM ( Document Object Model ) μ μμ± λ° μ‘°μν΄μ λ λλ§λ μΉ νμ΄μ§κ° μ 곡λ©λλ€.
CSR μ μ₯μ μΌλ‘λ μ΅μ΄ λ‘λ© μ΄νμ νμν λ°μ΄ν°λ§ μλ²μμ λ°μμμ ν΄λΌμ΄μΈνΈμμ μ λ°μ΄νΈ νκΈ° λλ¬Έμ λΉ λ₯΄κ² μ λ°μ΄νΈλκ³ , μ¬μ©μ κ²½νμ΄ λΆλλ½μ΅λλ€. λν μλ² λΆνκ° μ κ³ , λμ μ΄κ³ μΈν°λν°λΈν UI ꡬνμ μ 리ν©λλ€.
λ€λ§ λ¨μ μΌλ‘λ μ΄κΈ°μ λ§μ λ°μ΄ν°λ₯Ό λ°μμ€λ€λ³΄λ μ΄κΈ° λ‘λ©μ΄ λλ¦° μ μ΄ μμ΅λλ€. λν μ΄κΈ° HTML μ μ½ν μΈ κ° κ±°μ μκΈ° λλ¬Έμ κ²μμ λΆλ¦¬νκ² λκ³ λ°λΌμ SEO ( Searce Engine Optimization ) μ λΆλ¦¬ν©λλ€. λν μμ¦μ κ±°μ μκΈ΄ νμ§λ§ , JS κ° μ€νλμ§ μλ νκ²½μ΄λΌλ©΄ μ½ν μΈ λ₯Ό λ³Ό μ μμ΅λλ€.
SSR μ κΈ°λ³Έμ μΌλ‘ μΉ νμ΄μ§μ μ μνμ λ μλ²λ‘λΆν° μμ ν λ λλ§λ HTML κ³Ό νμν JS νμΌ μ μ λ¬λ°μ΅λλ€. λΈλΌμ°μ λ μ΄ HTML μ μ¦μ νλ©΄μ νμν©λλ€. κ·Έ ν , JS νμΌμ μ€νν΄μ HTML μ μΆκ°λ‘ λΆμ°©ν΄ κΈ°λ₯μ νμ±νν©λλ€. ( Hydration )
SSR μ μ₯μ μΌλ‘λ μλ²μμ μμ ν λ λλ§λ HTML μ λ°κΈ° λλ¬Έμ μ΄κΈ° λ‘λ©μ΄ λΉ¨λΌμ μ¬μ©μκ° μ½ν μΈ λ₯Ό λΉ λ₯΄κ² νμΈν μ μμ΅λλ€. λν κ²μ μμ§ ν¬λ‘€λ¬κ° μ΄λ―Έ λ λλ§λ HTML μ νμΈν μ μκΈ° λλ¬Έμ SEO μ μ 리ν©λλ€. λ JS κ° μ€νλμ§ μλ νκ²½μμλ HTML μ΄ μ‘΄μ¬νκΈ° λλ¬Έμ μ½ν μΈ λ₯Ό νμΈν μ μμ΅λλ€.
λ€λ§ λ¨μ μΌλ‘λ νμ΄μ§κ° μ νλ λ λ§λ€ μλ²μμ HTML μ λ°μμΌ νκΈ° λλ¬Έμ μ ν μλκ° λ릴 μ μμ΅λλ€. λν μλ²κ° λͺ¨λ λ λλ§μ μννκΈ° λλ¬Έμ μλ² λΆνκ° μκ³ , ν΄λΌμ΄μΈνΈμ μλ²μΈ‘ μ½λλ₯Ό λλμ΄μ κ΄λ¦¬ν΄μΌ ν΄μ 볡μ‘ν΄μ§ μλ μμ΅λλ€.
κ²°κ΅ λΈλ‘κ·Έλ μΈλΆμμ κ²μκΈμ΄ κ²μμ΄ μ λμ΄μΌ νκΈ° λλ¬Έμ SSR μ μ¬μ©νλ κ²μ΄ μ 리ν©λλ€. λ°λΌμ SSR μ μ¬μ©νκΈ° μν΄ Next + monorepo ννλ‘ λ§μ΄κ·Έλ μ΄μ νκΈ°λ‘ κ²°μ νμ΅λλ€.
Nextjs λ₯Ό μ μ©νλ€κ³ ν΄μ λ°λ‘ μ€μ μ΄ μλ£λλ κ²μ΄ μλλΌ, κ²μμ΄ μ λκ² νκΈ° μν΄μ λ°λ‘ μ¬λ¬κ°μ§ μ€μ μ μΆκ°ν΄μΌ ν©λλ€.
Nextjs 13+ App Router μμλΆν°λ layout.js λλ page.js νμΌμ metadata κ°μ²΄λ₯Ό μ μΈν΄μ λ©νλ°μ΄ν°λ₯Ό μ€μ ν μ μμ΅λλ€. λν generateMetadata ν¨μλ₯Ό ν΅ν΄μ λμ λΌμ°ν
μΌλ‘ μμ±λλ νμ΄μ§μμ λμ μΌλ‘ λ©νλ°μ΄ν°λ₯Ό μ€μ ν μ μμ΅λλ€.
μ΄ λ©νλ°μ΄ν°λ κ²μ μμ§ ν¬λ‘€λ¬μκ² μ 보λ₯Ό μ 곡ν΄μ μ΄λ€ μ¬μ΄νΈμΈμ§ , μ΄λ€ λ΄μ©μ λ΄κ³ μλμ§λ₯Ό μλ €μ£Όλ μν μ ν©λλ€. κ·Έλμ κ²μ κ²°κ³Ό νλ©΄μμ ν΄λΉ λ°μ΄ν°λ₯Ό νμΆν΄ μ€λλ€. λ°λΌμ μ¬μ©μκ° κ²μ μμ§μ μ¬μ©ν λ μνλ μ 보λ₯Ό λΉ λ₯΄κ³ μ ννκ² μ°Ύμ μ μλλ‘ λμμ€λλ€.
js
// μ μ νλ©΄ μμ
export const metadata: Metadata = {
title: "κΉμμ°¬'s Blog",
description: "κΉμμ°¬'s Blog",
};
// λμ νλ©΄ μμ
export async function generateMetadata({ params }: { params: BlogPostParams }) {
const { postKey } = await params;
const { data } = await prefetchPostData(postKey);
if (!data || !data.post) {
return {
title: "κ²μκΈμ μ°Ύμ μ μμ΅λλ€.",
description: "μμ²νμ κ²μκΈμ μ°Ύμ μ μμ΅λλ€.",
};
}
const postData = data.post;
return {
title: `${postData.title}`,
openGraph: {
title: `${postData.title}`,
type: "article",
},
};
}
μ¬μ΄νΈλ§΅μ μΉ μ¬μ΄νΈ λ΄μ λͺ¨λ νμ΄μ§λ₯Ό λμ΄ν΄ λμ νμΌλ‘, κ²μ μμ§μ΄ ν¨μ¨μ μΌλ‘ ν¬λ‘€λ§νλλ‘ λλ μν μ ν©λλ€. μΉμ¬μ΄νΈμ λͺ¨λ νμ΄μ§λ₯Ό μλ €μ€ λΏλ§ μλλΌ λΆνμν νμ΄μ§λ₯Ό λ°©λΆνλ κ²μ μ€μ΄κ³ , μ λ°μ΄νΈλ κ²μ νμΈν΄μ λ€μ ν¬λ‘€λ§νλλ‘ μ λν©λλ€.
NextJS μμ App Router λ₯Ό μ¬μ©νλ κ²½μ° , app λλ ν 리 λ΄λΆμ sitemap.ts νμΌμ μ§μ μ μΌλ‘ μμ±ν΄μ μ μ©ν μ μμ΅λλ€ .
js
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// λμ νμ΄μ§
const { data } = await prefetchHomeData();
const dynamicUrls: MetadataRoute.Sitemap = data.posts.map(({ postKey }) => ({
url: `${BASE_URL}/blog/${postKey}`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.7,
}));
// μ μ νμ΄μ§
const staticUrls: MetadataRoute.Sitemap = [
{
url: BASE_URL,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1,
},
{
url: `${BASE_URL}/blog`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.8,
},
];
[...staticUrls, ...dynamicUrls];
}
κΈ°λ³Έμ μΌλ‘ μμ±λμ΄ μλ μ μ νμ΄μ§μ keyμ λ°λΌμ λ€λ₯΄κ² μμ±λλ λμ νμ΄μ§ λͺ¨λ sitemap μ€μ μ΄ κ°λ₯ν©λλ€. ν΄λΉ νμΌλ‘ μμ±λ sitemap μ www.BASE_URL/sitemap.xml μμ νμΈν μ μμ΅λλ€.
robot.txt νμΌμ κ²μ μμ§ λ΄μκ² μ¬μ΄νΈμμ μ΄λ λΆλΆμ μ κ·Όν΄μΌ νλμ§, μ΄λ λΆλΆμ μ κ·Όνλ©΄ μλλμ§λ₯Ό μλ €μ£Όλ μν μ ν©λλ€. μ΄λ μΉμ¬μ΄νΈμ νΉμ νμ΄μ§κ° λ
ΈμΆλλ κ²μ μ μ΄νλ μν μ ν©λλ€.
NextJS μμ App Router λ₯Ό μ¬μ©νλ κ²½μ° , app λλ ν 리 λ΄λΆμ robots.ts νμΌμ μ§μ μ μΌλ‘ μμ±ν΄μ μ μ©ν μ μμ΅λλ€ .
js
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: "*",
allow: "/",
},
],
sitemap: BASE_URL,
};
}
userAgent : μ΄λ κ²μ μμ§ λ΄μ μ μ©ν μ§λ₯Ό μ νν©λλ€. * μ κ²½μ°, λͺ¨λ λ΄μ μ μ©λ©λλ€.allow : μ΄λ€ νμ΄μ§λ₯Ό νμ©ν μ§ μ νν©λλ€. / μ κ²½μ°, λͺ¨λ νμ΄μ§μ ν¬λ‘€λ§μ νμ©ν©λλ€.disallow : μ΄λ€ νμ΄μ§λ₯Ό μ κ·Ό λΆκ°νκ² ν μ§ μ νν©λλ€.sitemap : sitemap μ url μ λͺ
μν΄ μλ €μ€λλ€.μ΄μ λ§λ μ¬μ΄νΈλ₯Ό κ²μ μμ§μ λ±λ‘ν΄μ κ²μλ μ μλλ‘ ν΄μΌ ν©λλ€. λνμ μΈ κ²μ μμ§μΌλ‘λ ꡬκΈμ Google Search Console κ³Ό λ€μ΄λ²μ Naver Search Adviser κ° μμ΅λλ€. ν΄λΉ νμ΄μ§μμ μ€μ μ μΆκ°ν΄μ κ²μ μμ§ λ΄μμ μ΄λ° μ¬μ΄νΈκ° μμΌλ κ²μμ λμ¬ μ μλλ‘ ν΄λ¬λΌκ³ μμ²νλ κ³Όμ μ λλ€. ν΄λΉ κ³Όμ μ μΆνμ κ²μκΈλ‘ μΆκ°νλλ‘ νκ² μ΅λλ€.
Next λ₯Ό μ¬μ©νκΈ°λ‘ λ§μλ¨Ήκ³ κ°μ₯ κΈ°λνκ³ κ΄μ¬μκ² μ°Ύμλ³Έ λΆλΆμ΄ λ°λ‘ μ΄ SEO μ€μ μ λλ€. μ΄λ»κ² νλ©΄ μ΄κ² κ²μλκ³ μ¬λλ€μκ² λ³΄μ¬μ€ μ μλμ§ μ€μ νλ©΄μ λ§μ μ¬λ―Έλ₯Ό λκΌκ³ μ¦κ²κ² μμ νλ κ² κ°μ΅λλ€. λ€μ κ²μκΈλ‘ μΉμ¬μ΄νΈ κ²μμμ§ μ€μ , μ½λμ λ§μ΄κ·Έλ μ΄μ λ±λ±μ μμ±ν΄ 보λλ‘ νκ² μ΅λλ€.