- Published on
- ·16 min read
Next.js - 블로그 다국어(i18n)로 만들기 Part 3
개요
Next.js - 블로그 다국어(i18n)로 만들기 Part 2 포스팅에서 눈에 보이는 부분은 정상 동작하는 것을 확인했습니다.
내용이 길어져 Part 1 ~ 4 로 나누어서 작성합니다.
Part 3에서는 rss, sitemap 을 수정합니다.
- Part 1: next-i18next 설치와 사이트 다국어 처리
- Part 2: 등록된 글들에 대한 다국어 처리
- Part 3: Rss와 Sitemap 처리
- Part 4: SEO를 위한 lang, alternate 그리고 날짜 형식 처리
gitHub, i18n-tailwind-nextjs-starter-blog를 참고하여 만들었습니다.
Tailwind CSS Blog Starter 템플릿 사용을 결정하셨고, 프로젝트를 처음 시작한다면 위 프로젝트로 시작하는 것도 좋을 것 같습니다.
제 경우 이미 프로젝트가 진행 중이라서, 위 프로젝트를 참고하여 진행했습니다.
feed.xml
feed.xml
을 생성하는 부분을 언어별로 생성하도록 수정합니다.
lib/generate-rss.js
locale
파라미터를 추가하고,language
와description
을 locale에 맞게 수정합니다.
// import 추가
import { formatSlug } from '@/lib/mdx'
import { i18n } from '../next-i18next.config'
// locale 파라미터 추가 및 guid, link 수정
const generateRssItem = (post, locale = 'ko') => `
<item>
<guid>${siteMetadata.siteUrl}${locale === i18n.defaultLocale?'': '/' + locale}/blog/${formatSlug(post.slug, i18n.locales)}</guid>
<title>${escape(post.title)}</title>
<link>${siteMetadata.siteUrl}${locale === i18n.defaultLocale?'': '/' + locale}/blog/${formatSlug(post.slug, i18n.locales)}</link>
${post.summary && `<description>${escape(post.summary)}</description>`}
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
<author>${siteMetadata.email} (${siteMetadata.author})</author>
${post.tags && post.tags.map((t) => `<category>${t}</category>`).join('')}
</item>
`
// locale 파라미터 추가, description, language 수정, generateRssItem 호출 시 locale 추가
const generateRss = (posts, page = 'feed.xml', locale = 'ko') => `
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>${escape(siteMetadata.title)}</title>
<link>${siteMetadata.siteUrl}/blog</link>
<description>${escape(siteMetadata.description[locale])}</description>
<language>${locale}</language>
<managingEditor>${siteMetadata.email} (${siteMetadata.author})</managingEditor>
<webMaster>${siteMetadata.email} (${siteMetadata.author})</webMaster>
<lastBuildDate>${new Date(posts[0].date).toUTCString()}</lastBuildDate>
<atom:link href="${siteMetadata.siteUrl}/${page}" rel="self" type="application/rss+xml"/>
${posts.map((post) => generateRssItem(post, locale)).join('')}
</channel>
</rss>
`
export default generateRss
- 위 코드 중
siteMetadata.description[locale]
부분은 Part 4siteMetadata
부분에서 설명합니다. 하지만 아래와 같이siteMetadata.description
에ko
와en
을 추가하지 않으면 오류가 발생하므로 미리 추가합니다.
const siteMetadata = {
// ...
description: {
ko: '생활 정보, 개발 정보 등 여러가지 관심사를 다루는 블로그입니다.',
en: 'This is a blog that covers various interests such as lifestyle information, development updates, and more.',
},
// ...
}
pages/blog/[...slug].js
- 언어별 feed를 생성하도록 아래와 같이 코드를 수정합니다.
export async function getStaticProps({ params, locale, defaultLocale, locales }) {
// ...
// rss
if (allPosts.length > 0) {
const feedName = locale === defaultLocale ? 'feed.xml' : `feed.${locale}.xml`
const rss = generateRss(allPosts, feedName, locale)
fs.writeFileSync(`./public/${feedName}`, rss)
}
return { props: { post, authorDetails, prev, next, relatedPosts } }
}
pages/tags/[tag].js
- 언어별 feed를 생성하도록 아래와 같이 코드를 수정합니다.
export async function getStaticProps({ params, locale, defaultLocale, locales }) {
// ...
// rss
if (filteredPosts.length > 0) {
const feedName = locale === defaultLocale ? 'feed.xml' : `feed.${locale}.xml`
const rss = generateRss(filteredPosts, `tags/${params.tag}/${feedName}`, locale)
const rssPath = path.join(root, 'public', 'tags', params.tag)
fs.mkdirSync(rssPath, { recursive: true })
fs.writeFileSync(path.join(rssPath, feedName), rss)
}
return { props: { posts: filteredPosts, tag: params.tag } }
}
sitemap
Tailwind CSS Blog Starter 템플릿에는 배포시 자동으로 sitemap을 생성하는 코드가 있습니다.
2개 이상의 언어가 있는 페이지의 경우 alternate link를 추가하도록 수정하겠습니다.
scripts/generate-sitemap.js
아래 코드와 주석을 참고하세요.
const fs = require('fs')
const globby = require('globby')
const matter = require('gray-matter')
const prettier = require('prettier')
const siteMetadata = require('../data/siteMetadata')
// 추가: i18n 설정을 불러옵니다.
const { i18n } = require('../next-i18next.config')
;(async () => {
const prettierConfig = await prettier.resolveConfig('./.prettierrc.js')
const pages = await globby([
'pages/*.js',
'pages/*.tsx',
'data/blog/**/*.mdx',
'data/blog/**/*.md',
'public/tags/**/*.xml',
'!pages/_*.js',
'!pages/_*.tsx',
'!pages/api',
])
// 추가: i18n 설정에서 locale 목록과 기본 locale을 불러옵니다.
const { locales, defaultLocale } = i18n
// 추가: routeMap = { path: ['en', ''] } 형태, default locale은 ''로 설정
const routeMap = new Map()
// 기존 내용에서 필터링 하는 부분은 그대로 유지하고, routeMap에 locale을 추가하는 부분을 추가
pages.forEach((page) => {
// Exclude drafts from the sitemap
if (page.search('.md') >= 1 && fs.existsSync(page)) {
const source = fs.readFileSync(page, 'utf8')
const fm = matter(source)
if (fm.data.draft) {
return
}
if (fm.data.canonicalUrl) {
return
}
}
if (page.search('pages/404.') > -1 || page.search(`pages/blog/[...slug].`) > -1) {
return
}
const path = page
.replace('pages/', '/')
.replace('data/blog', '/blog')
.replace('public/', '/')
.replace('.js', '')
.replace('.tsx', '')
.replace('.mdx', '')
.replace('.md', '')
// feed의 locale을 찾기 위해 replace 부분 수정
.replace('/feed', '')
.replace('.xml', '')
const route = path === '/index' ? '' : path
// /blog, /tags 하위에 있는 동적인 콘텐츠는 locale 확인 후 추가
if (/^\/(blog|tags)\/[^/]+/.test(route)) {
// .{locale} 포함여부 확인 위한 regex
const regex = new RegExp(`\\.(${locales.join('|')})$`, 'i')
const findLocale = route.match(regex)
if (findLocale) {
const locale = findLocale[1]
const newRoute = route.replace(regex, '') // route에서 locale 제거
routeMap.set(
newRoute,
routeMap.has(newRoute) ? [...routeMap.get(newRoute), locale] : [locale] // 해당 path에 포함된 locale을 배열에 추가
)
} else {
routeMap.set(
route,
routeMap.has(route) ? [...routeMap.get(route), defaultLocale] : [defaultLocale]
)
}
}
// 동적 콘텐츠가 아닌 그 외의 페이지는 모든 locale 추가
else {
routeMap.set(
route,
locales.map((locale) => (locale === defaultLocale ? '' : locale))
)
}
})
// routeMap으로 url 목록 생성
// 포함된 locale이 2개 이상이면 xhtml:link 추가, 아닐 경우는 url만 추가
let sitemapUrls = ''
routeMap.forEach((value, key) => {
if (value.length > 1) {
sitemapUrls += `
<url>
<loc>${siteMetadata.siteUrl}${key}</loc>
${value
.map((locale) => {
return `<xhtml:link rel="alternate" hreflang="${
locale === '' ? defaultLocale : locale
}" href="${siteMetadata.siteUrl}${locale === '' ? '' : '/' + locale}${key}" />`
})
.join('')}
</url>`
} else {
sitemapUrls += `
<url>
<loc>${siteMetadata.siteUrl}${key}</loc>
</url>`
}
})
// sitemap 생성
const sitemap = `
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
${sitemapUrls}
</urlset>`
const formatted = prettier.format(sitemap, {
...prettierConfig,
parser: 'html',
})
// eslint-disable-next-line no-sync
fs.writeFileSync('public/sitemap.xml', formatted)
})()
sitemap 확인
- 명령 프롬프트에서
node ./scripts/generate-sitemap
명령어를 실행하면public/sitemap.xml
파일이 생성됩니다.
아래와 같이 생성된 것을 확인할 수 있습니다.
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
>
<url>
<loc>https://hlog.kr/about</loc>
<xhtml:link rel="alternate" hreflang="ko" href="https://hlog.kr/about" />
<xhtml:link rel="alternate" hreflang="en" href="https://hlog.kr/en/about" />
</url>
<url>
<loc>https://hlog.kr/blog</loc>
<xhtml:link rel="alternate" hreflang="ko" href="https://hlog.kr/blog" />
<xhtml:link rel="alternate" hreflang="en" href="https://hlog.kr/en/blog" />
</url>
<!-- 생략 -->
<url>
<loc>https://hlog.kr/blog/2023-09/javascript-date-format-function</loc>
</url>
<url>
<loc>https://hlog.kr/blog/2023-09/javascript-zero-fill-three-ways</loc>
</url>
<url>
<loc>https://hlog.kr/blog/2023-09/nextjs-blog-multi-language-part1</loc>
<xhtml:link
rel="alternate"
hreflang="en"
href="https://hlog.kr/en/blog/2023-09/nextjs-blog-multi-language-part1"
/>
<xhtml:link
rel="alternate"
hreflang="ko"
href="https://hlog.kr/ko/blog/2023-09/nextjs-blog-multi-language-part1"
/>
</url>
<url>
<loc>https://hlog.kr/blog/2023-09/nextjs-blog-multi-language-part2</loc>
<xhtml:link
rel="alternate"
hreflang="en"
href="https://hlog.kr/en/blog/2023-09/nextjs-blog-multi-language-part2"
/>
<xhtml:link
rel="alternate"
hreflang="ko"
href="https://hlog.kr/ko/blog/2023-09/nextjs-blog-multi-language-part2"
/>
</url>
<url>
<loc>https://hlog.kr/tags/nextjs</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://hlog.kr/en/tags/nextjs" />
<xhtml:link rel="alternate" hreflang="ko" href="https://hlog.kr/ko/tags/nextjs" />
</url>
<url>
<loc>https://hlog.kr/tags/tip</loc>
</url>
<!-- 생략 -->
</urlset>
정리
rss, sitemap을 다국어로 만드는 방법을 알아보았습니다.
Next.js - 블로그 다국어(i18n)로 만들기 Part 4 에서 html lang 설정 및 alternate link 태그를 추가하는 방법을 알아보겠습니다.
코드
변경된 코드는 commit history 를 참고해주세요.
태그와 연관된 글
2023. 09. 27.
Next.js - 블로그 다국어(i18n)로 만들기 Part 42023. 09. 25.
Next.js - 블로그 다국어(i18n)로 만들기 Part 22023. 09. 24.
Next.js - 블로그 다국어(i18n)로 만들기 Part 12023. 09. 19.
Next.js - 다국어(i18n) 설정 및 static html로 내보내기 Part 22023. 09. 19.
Next.js - 다국어(i18n) 설정 및 static html로 내보내기 Part 1