-
Next.js 정적, 동적 사이트맵 만들기Nextjs 2022. 6. 26. 23:57
갑작스레 전혀 모르는 프로젝트의 사이트맵을 만드는 업무를 하게 됐다. 처음에는 사이트맵이 무엇인지도 몰라서 급하게 리서치를 했다.
사이트맵은 사이트에 있는 페이지, 동영상 및 기타 파일과 그 관계에 관한 정보를 제공하는 파일입니다. (출처: Google 검색 센터)
검색엔진들은 사이트맵 파일을 읽고서 사이트를 크롤링하기 때문에, SEO(Search Engine Optimization, 검색최적화)를 위해 기본적으로 세팅해주어야한다고 한다. 사이트맵 파일이 없어도 검색엔진이 크롤링을 통해 사이트를 찾긴 하지만, 발견되지 않는 웹페이지가 없도록 직접 url들을 넣어주는 것이다.
아래 내용은 이 블로그를 많이 참고해서 작성했다. 덩달아 링크로 타고 들어간 이 글도 도움이 많이 되었다. (이 두 글이 없었다면 아마 절대 혼자 해결을 못했을 것 같다. 감사합니다..)
정적 사이트맵
기본적으로 존재하는 페이지들에 대해 사이트맵을 먼저 만들었다. 사실 이 부분은 굉장히 단순한데, 현재 프로젝트 구조에서 접근할 수 있는 URL 들을 쭉 나열해주기만 하면 됐다. script 폴더를 새로 만들고, sitemap-default.js 파일을 아래와 같이 작성했다.
const fs = require('fs'); const globby = require('globby'); const prettier = require('prettier'); const getDate = new Date().toISOString().slice(0, 10); const DOMAIN = '도메인명 입력'; const formatted = (sitemap) => prettier.format(sitemap, { parser: 'html' }); (async () => { const pages = await globby([ // include '../pages/**/*.tsx', '../pages/*.tsx', // exclude '!../pages/_*.tsx', '!../pages/admin/*.tsx', '!../pages/admin/**/*.tsx', '!../pages/my/*.tsx', '!../pages/my/**/*.tsx', '!../pages/**/[id].tsx', '!../pages/**/[id]/*.tsx', '!../pages/**/[id]/**/*.tsx', ]); const pagesSitemap = ` ${pages .map((page) => { const path = page .replace('../pages/', '') .replace('.tsx', '') .replace(/\/index/g, ''); const routePath = path === 'index' ? '' : path; return ` <url> <loc>${DOMAIN}/${routePath}</loc> <lastmod>${getDate}</lastmod> </url> `; }) .join('')} `; const generatedSitemap = ` <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" > ${pagesSitemap} </urlset> `; const formattedSitemap = formatted(generatedSitemap).toString(); fs.writeFileSync('../public/sitemap-default.xml', formattedSitemap, 'utf8'); })();
pages 안에 기본적인 페이지들을 포함하되, 어드민쪽 페이지나 로그인 이후 접근할 수 있는 개인페이지들은 제외했다.
위 파일을 실행하면 public 폴더 안에 아래와 같은 파일이 생긴다.
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://도메인명/</loc> <lastmod>2022-06-21</lastmod> </url> <url> <loc>https://도메인명/search</loc> <lastmod>2022-06-21</lastmod> </url> ...(중략)... <url> <loc>https://도메인명/videos</loc> <lastmod>2022-06-21</lastmod> </url> </urlset>
동적 사이트맵
정적 사이트맵을 작성하는 것까진 쉬웠으나 동적 사이트맵을 만드는 과정에서 많이 애를 먹었다.
카테고리 안의 하위 상세페이지 url을 찾아서 넣어야하다보니 서버에서 어떤 상세페이지들이 있는지 정보를 얻어와야했다. 그런데 이전에 graphql 을 다뤄본적이 없었고, 실제 프로젝트 개발한 분과 소통도 불가능했다(하려면 할 수 있었을 것도 같은데 좀 부담스러웠다). 억지로 데이터를 끌어다 쓰다보니 우아한 코드는 아니었지만 어찌저찌 해결은 됐다.
아래는 video 관련 상세페이지를 긁어와서 sitemap을 만드는 코드 예시이다.
const fs = require('fs'); const axios = require('axios'); const prettier = require('prettier'); const getDate = new Date().toISOString().slice(0, 10); const DOMAIN = '도메인명'; const formatted = (sitemap) => prettier.format(sitemap, { parser: 'html' }); const makeQuery = (offset) => { return `query getVideoList{ videos ( paging: { offset: ${offset}, limit: 50, } ) { nodes { id } pageInfo { hasNextPage } } } `; }; (async () => { let videoList = []; let offset = 0; while (true) { const response = await axios({ method: 'post', url: 'https://{api 서버주소}/graphql', data: { query: makeQuery(offset) }, }); const result = response.data.data.videos; const hasNextPage = result.pageInfo.hasNextPage; const videoIds = result.nodes; videoIds.map((data) => videoList.push(data.id)); if (hasNextPage) { offset = offset + 50; } else { break; } } const videoListSitemap = `${videoList .map((videoId) => { return ` <url> <loc>${DOMAIN}/video/${videoId}</loc> <lastmod>${getDate}</lastmod> </url> `; }) .join('')} `; const generatedSitemap = ` <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" > ${videoListSitemap} </urlset> `; const formattedSitemap = formatted(generatedSitemap).toString(); fs.writeFileSync('../public/sitemap-video.xml', formattedSitemap, 'utf8'); })();
50개씩 id를 긁어오면서, 만약 다음페이지가 존재할 경우 그 다음페이지 부터 다시 50개씩 긁어오는 식으로 코드를 구현했다.
id로 리스트를 만든 뒤에 하나씩 <loc> 안에 넣고, <lastmod> 값을 넣어준 뒤, join으로 전부 합쳤다. 결과는 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://도메인명/video/1857</loc> <lastmod>2022-06-21</lastmod> </url> <url> <loc>https://도메인명/video/1856</loc> <lastmod>2022-06-21</lastmod> </url> ...(중략)... <url> <loc>https://도메인명/video/152</loc> <lastmod>2022-06-21</lastmod> </url> </urlset>
사이트맵 인덱스
각각 만들어진 사이트맵을 하나로 묶어주기 위해 사이트맵 인덱스를 사용하였다. 사이트맵 인덱스 같은 경우는 생성된 사이트맵 파일을 각각 담아서 만들기만 하면 된다. (프로젝트가 크지 않아서 따로 압축은 하지 않았다)
const fs = require('fs'); const prettier = require('prettier'); const getDate = new Date().toISOString().slice(0, 10); const DOMAIN = '도메인명'; const SITEMAPS = [ 'sitemap-defualt', 'sitemap-video', ... ]; const formatted = (sitemap) => prettier.format(sitemap, { parser: 'html' }); (async () => { const pagesSitemap = ` ${SITEMAPS.map((path) => { return ` <sitemap> <loc>${DOMAIN}/${path}.xml</loc> <lastmod>${getDate}</lastmod> </sitemap> `; }).join('')} `; const generatedSitemap = ` <?xml version="1.0" encoding="UTF-8"?> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ${pagesSitemap} </sitemapindex> `; const formattedSitemap = formatted(generatedSitemap).toString(); fs.writeFileSync('../public/sitemap.xml', formattedSitemap, 'utf8'); })();
<url>, <urlset> 태그 대신 <sitemap> 과 <sitemapindex> 태그를 사용해서 감싸주면 된다. 실행 결과는 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?> <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <sitemap> <loc>https://where.review/sitemap-defualt.xml</loc> <lastmod>2022-06-21</lastmod> </sitemap> <sitemap> <loc>https://where.review/sitemap-video.xml</loc> <lastmod>2022-06-21</lastmod> </sitemap> ... </sitemapindex>
쉘 스크립트 작성하기
마지막으로 위 내용을 한번에 실행할 수 있게 하기 위해 generate-sitemap 이라는 쉘 스크립트를 작성하였다.
cd scripts # 정적 사이트맵 node ./sitemap-default.js # 동적 사이트맵 node ./sitemap-video.js # 사이트맵 인덱스 파일 생성 node ./sitemap.js echo "sitemap 생성 완료"
이제 가장 최상위 루트에서 generate-sitemap.sh 를 실행하면 사이트맵 파일이 주르륵 public 폴더 안에 생긴다.
생성 뒤엔 각 검색엔진에 사이트맵을 제출하면 된다. (ex. 구글 Search Console, 네이버 서치어드바이저 등)
후기
막상 결과물은 별거 없는 것 같은데 왜이리 어렵게 보였는지 모르겠다. 처음에 시작하는 것부터가 막막했는데 블로그 글들을 보며 도움을 정말 많이 받았다. 원래 포기하고 다른분 도움을 기다리고 있던 업무였는데 한번 해보자 하고 도전해서 어떻게든 결론을 내서 기분이 좋았다. 좀 코드를 막 구겨넣은 느낌은 나지만.. 혹시 잘못된 부분 지적해 주신다면 언제든지 환영합니다 :)
참고
주니어도 할 수 있는 Next.js SEO - robots.txt와 sitemap.xml 자동 생성하기
본 포스트는 개발과 배포에 대한 지식은 가지고 있지만 아직 조금 낯설고 어려운 주니어 개발자들을 대상으로 작성되었습니다. Circle CI, GitHub Actions같은 CI(Continuous Integration) 툴과 자동화 배포에
velog.io
https://developers.google.com/search/docs/advanced/sitemaps/overview?hl=ko
사이트맵이란 무엇인가요? | Google 검색 센터 | 문서 | Google Developers
사이트맵은 Google에서 사이트를 더 지능적으로 크롤링할 수 있도록 정보를 제공합니다. 사이트맵의 작동 방식을 알아보고 필요한지 결정하세요.
developers.google.com
Next.js를 위한 sitemap generator 만들기
사이트맵 생성부터, 검색엔진 색인 요청까지
medium.com
'Nextjs' 카테고리의 다른 글
[SEO] VideoObject 스키마 마크업을 통해 구글에 동영상 노출하기 (0) 2022.10.02 [SEO] Canonical tag(캐노니컬 태그, 대표URL)과 중복 콘텐츠 이슈 (0) 2022.09.04 Next.js 다이나믹 메타 태그 넣기 (0) 2022.07.10 Next.js 와의 첫 만남 (CSR vs SSR) (0) 2021.09.26