🗺️

서비스의 Sitemap 개선해보자.

Created
7/12/2021, 4:59:00 AM
Tags
Backend
ETC
Frontend
Subtitle
SEO에 늪에 빠졌다.
서비스 운영에 있어 SEO는 중요한 요소임에도 불구하고, 경험상 개발자들에게는 흥미로운 주제는 아닌거 같다.
눈에 잘 띄는 것도 아닐 뿐더러 robot.txt ,sitemap.xml파일을 렌더링 하거나, 적절한 html attribute ( canonical, alt )등을 선언하는 것이 대부분의 일인데, 이것들이 어려운 일이 아니라서 그런 것 같다.
나도 SEO에 대해선 무관심 했는데, 얼마전부터 외부 업체에서 회사 서비스의 SEO에 대한 컨설팅을 해주면서 꽤 많은 것을 배웠다.
SEO를 위해 여러 가지 요청을 주시는데 이번엔 그 중 Sitemap에 대해 잠시 살펴보고자 한다.

좋은 Sitemap.xml 작성하기

기존 Sitemap은 어플리케이션단에서 정적으로 sitemap.xml 파일을 render 하고 있었다.
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://www.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://sub1.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>0.85</priority> </url> <url> <loc>https://sub2.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>0.85</priority> </url> <url> <loc>https://sub3.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>0.85</priority> </url> </urlset>
JavaScript
SEO 최적화를 위해 다음과 같은 수정을 요청받았다.
1.
메인 웹사이트 및 서브도메인 별 사이트맵을 제출해야 한다.
2.
메인 웹사이트 사이트맵에는 서브도메인을 제외하고, https://www.homepage.co.kr/ 의 서브폴더 형식의 모든 URL을 추가한다.
3.
인덱싱이 되는 페이지의 url만 적용한다. Sitemap은 매일 갱신하여 리다이렉션 된 페이지, 404 페이지는 제외 시킨다.
4.
사이트맵 URL은 25,000개 이하로 제한하는 것을 권장하며, 최대 50,000 URL을 포함해서는 안 된다.
5.
사이트맵 갯수 제한을 초과하는 경우 별도의 XML 사이트맵으로 분할하여 XML 사이트맵 인덱스와 함께 묶는다.

예제와 함께 살펴보자

1. 메인 웹사이트 및 서브도메인 별 사이트맵을 제출해야 한다.

메인 웹사이트 및 서브도메인 별 사이트맵을 제출해야 한다. 메인 sitemap.xml에 서브도메인을 포함해서는 안된다.

Bad Case

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://www.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://sub1.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>0.85</priority> </url> ... </urlset> // https://www.homepage.co.kr/sitemap.xml 에 대한 결과
JavaScript

Good Case

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://www.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> </urlset> // https://www.homepage.co.kr/sitemap.xml 에 대한 결과
JavaScript
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://sub1.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> </urlset> // https://sub1.homepage.co.kr/sitemap.xml 에 대한 결과
JavaScript

2. 메인 웹사이트 사이트맵에는 서브도메인을 제외하고, https://www.homepage.co.kr/ 의 서브폴더 형식의 모든 URL을 추가한다.

가능한 모든 Path에 대한 URL를 추가 해야한다.

Bad Case

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://www.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> </urlset> // https://www.homepage.co.kr/sitemap.xml 에 대한 결과
JavaScript

Good Case

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://www.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/contents</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/content/1</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/content/2</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/content/3</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> ... <url> <loc>https://www.homepage.co.kr/videos</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> </urlset> // https://www.homepage.co.kr/sitemap.xml 에 대한 결과
JavaScript
특히 컨텐츠 아이디 등 동적인 Path역시 추가해주어야 한다.

3. 인덱싱이 되는 페이지의 url만 적용한다. Sitemap은 매일 갱신하여 리다이렉션 된 페이지, 404 페이지는 제외 시킨다.

Bad Case

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://homepage.co.kr/</loc> # www.homepage.co.kr로 리다이렉트 한다. <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/contents</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/content/1</loc> <lastmod>2021-03-15T10:23:20+02:00</lastmod> </url> <url> <loc>https://www.homepage.co.kr/content/2</loc> <lastmod>2021-03-15T10:23:20+02:00</lastmod> </url> # content2번은 삭제되어 더 이상 존재하지 않는다. ... </urlset>
JavaScript

Good Case

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> # https://homepage.co.kr/로 접속 시에 # www.homepage.co.kr로 리다이렉트 하기 때문에 제거 한다. <url> <loc>https://www.homepage.co.kr/</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/contents</loc> <changefreq>always</changefreq> <priority>1.00</priority> </url> <url> <loc>https://www.homepage.co.kr/content/1</loc> <lastmod>2021-03-16T10:23:20+02:00</lastmod> </url> ID 2의 content가 삭제 되었기 때문에 https://www.homepage.co.kr/content/2 URL 역시 제거 되어야 한다. ... </urlset> // https://www.homepage.co.kr/sitemap.xml 에 대한 결과
JavaScript

4. 사이트맵 URL은 25,000개 이하로 제한하는 것을 권장하며, 최대 50,000 URL을 포함해서는 안 된다. 사이트맵 갯수 제한을 초과하는 경우 별도의 XML 사이트맵으로 분할하여 XML 사이트맵 인덱스와 함께 묶는다.

Bad Case

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://homepage.co.kr/</loc> # www.homepage.co.kr로 리다이렉트 한다. <changefreq>always</changefreq> <priority>1.00</priority> </url> ... 대충 25000<url> <loc>https://www.homepage.co.kr/content/25001</loc> <lastmod>2021-03-15T10:23:20+02:00</lastmod> </url> ... </urlset> // https://www.homepage.co.kr/sitemap.xml 에 대한 결과
JavaScript

Good Case

<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <sitemap> <loc><https://www.homepage.co.kr/sitemap/default></loc> <lastmod>2021-03-14T18:23:17+00:00</lastmod> </sitemap> <sitemap> <loc><https://www.weport.co.kr/sitemap/content/1></loc> <lastmod>2021-03-15</lastmod> </sitemap> <sitemap> <loc><https://www.weport.co.kr/sitemap/content/2></loc> <lastmod>2021-03-15</lastmod> </sitemap> </sitemapindex> // https://www.homepage.co.kr/sitemap.xml 에 대한 결과
JavaScript
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://www.homepage.co.kr/content/12501</loc> <lastmod>2021-03-15T10:23:20+02:00</lastmod> </url> ... 대충 12500</urlset> // https://www.weport.co.kr/sitemap/content/2 에 대한 결과
JavaScript

실제 적용 사례

처음 설명을 들었을 땐 그래도 간단한 작업이라고 생각했는데 막상 작업을 시작하니 몇 가지 고민거리가 생겼다.
해결해야할 이슈는 아래와 같았다.
1.
몇몇 서브 도메인은 서브 도메인별로 어플리케이션이 있는 것이 아니라, 하나의 어플리케이션에 여러 서브 도메인을 사용하고 있었다.
# home/view.py def home(){ if req.host == sub1 : return sub1.home.html elif req.host == sub2 : return sub2.home.html return main.home.html } ## 뭐.. 대충 이런식인거죠.. ㅎㅎ;;
Python
2.
나머지 서브 도메인들에 대한 어플리케이션의 기술 스택이 모두 다르다. ( Next.js, Django, PHP 등...) 만약 모두 다른 방식으로 적용하기에는 작업량 및 관리 포인트가 많아 질 것 같았다.
3.
매번 Sitemap을 갱신해야 하고, 이들을 각각의 서브도메인에 전달해야 한다.
4.
Sitemap를 위해 별개의 Dependency 혹은 비즈니스 코드에 작업을 추가하고 싶지 않았다. ( 자칫 의도가 전달되기 어려운 코드가 만들어질것 같았다. )

하나의 어플리케이션에 여러 서브 도메인을 사용하는 경우 &어플리케이션의 기술 스택에 의존하지 않는 Sitemap 적용

Sitemap 적용 구조

특정 기술스택에 agnostic한 적용을 위해서, 공통된 부분을 찾을 필요가 있었다.
다행히 같은 프록시 서버를 사용하고 있었기 때문에 nginx에서 처리 할 수 있었다.
location /sitemap { if ($host ~ "(www.homepage.co.kr)|(^homepage.co.kr)" ) { rewrite ^/ /sitemap/www/sitemap.xml; break; } if ($host ~ "sub1.homepage.co.kr" ) { rewrite ^/ /sitemap/sub1/sitemap.xml; break; } if ($host ~ "(sub2.homepage.co.kr)|(sub2.com)" ) { rewrite ^/ /sitemap/sub2/sitemap.xml; break; } if ($host ~ "sub3.homepage.co.kr" ) { rewrite ^/ /sitemap/sub3/sitemap.xml; break; } if ($host ~ "sub4.homepage.co.kr" ) { rewrite ^/ /sitemap/sub4/sitemap.xml; break; } proxy_pass https://cdn.homepage.co.kr; }
JavaScript
nginx에서 내장된 변수들과 정규식을 이용하여, 서브도메인에 따른 proxy pass를 적용하였다.
또한 이렇게 적용함으로써, 하나의 어플리케이션에 여러 서브 도메인을 사용하고 있는 경우도 해결 할 수 있었다.
프록시를 적용할 호스팅 사이트가 필요했는데 이는 S3와 Cloudfront를 이용하였다.
하루에 한 번만 변경되는 등 다이나믹하지 않은 성질과 Cralwer Budget 에 조금이라도 도움이 될까 싶어 CloudFront로 Optimize할 수 있음을 고려한 선택이였다.

매번 Sitemap을 갱신해야 하고 이들을 각각의 서브도메인에 전달해야 한다.

S3에 업로드 하겠다고 결정하니 당연하게 Lambda로 업로드하는 것이 가장 편할 것이라 생각했다.
다만 Lambda를 사용하려다 보니 히스토리 관리 개발환경 등의 이슈가 있었고 Serverless Framework를 이용하여 업로드 하였다. ( 더 자세한 내용은 다음 포스트에 작성 해두었다. )
Lambda는 필요한 URL을 가져와서 상황에 맞는 Sitemap을 생성한 후 업로드 한다.
import AWS from "aws-sdk"; export const uploadToS3 = async (content: string, filePath: string) => { const s3 = new AWS.S3({ accessKeyId: process.env.SERVERLESS_AWS_ACCESS_KEY_ID, secretAccessKey: process.env.SERVERLESS_AWS_SECRET_ACCESS_KEY, }); const s3Options = { Body: content, Bucket: COMMON_CDN_S3_BUCKET, ContentType: "text/xml", Key: `${filePath}`, ACL: "public-read", }; return await new Promise<void>((resolve, reject) => { s3.upload(s3Options, function (err) { if (err) { reject(err); return; } resolve(); }); } } uploadToS3(createSitemap(), /path...);
JavaScript
이 때 다음처럼 람다에서는 aws-sdk 를 활용할 수 있다.

마무리 그리고, Sitemap을 최적화 함으로써...

지금까지 Sitemap을 최적화했다.
우리가 한 일이 검색엔진에게 어떻게 적용되는지 잠시 살펴보는 것으로 마무리 하자.

결국은 crawl budget를 최적화하기 위함

번역하면 크롤링 예산 정도 되겠다.
말 그대로 크롤러가 우리 서비스를 크롤링하는데 필요한 비용이다.
만약 budget이 저렴하다면 많이 크롤링 할 것이고 많이 노출 될 수 있다.
크롤링 예산은 특정 날짜에 Google이 사이트에서 크롤링할 페이지 수입니다. 이 수치는 날마다 조금씩 다르지만 전반적으로 그 차이가 크지 않습니다. Google은 사이트에서 매일 6페이지, 5,000페이지, 심지어 매일 4,000,000페이지를 크롤링할 수 있습니다. Google에서 크롤링하는 페이지 수, 즉 '예산'은 일반적으로 사이트 크기, 사이트의 '상태'(Google에서 발생하는 오류 수) 및 사이트 링크 수에 따라 결정됩니다. 이러한 요소 중 일부는 귀하가 영향을 줄 수 있는 것입니다. 잠시 후에 이에 대해 알아보겠습니다.

1. 사이트 크기의 최적화

예를 들어 하루에 1,000 페이지를 크롤링 할 수 있다고 가정하자.
그렇다면 중요한 페이지만 크롤링하길 원할 것이고, 불필요하거나 중복된 페이지를 크롤링하는 것은 낭비일 것이다.
대표적으로 PC와 모바일이 분리된 경우이다. 컨텐츠의 내용&이미지 는 같기 때문에 두 페이지 중 하나만 크롤링한다면 극단적으로 크롤링 비용을 절반으로 줄일 수도 있다.
https://www.naver.com/https://m.naver.com/ 는 디바이스별로 리다이렉트 할 뿐, 컨텐츠만 봤을 때 다른 페이지가 아니다.
이 때 canonical attribute등을 활용하여 이러한 중복을 제거할 수 있다.
( canonical 은 어떤 페이지가 대표 페이지인지 아닌지를 검색엔진에 알려주어, 필요한 크롤링만 하게 돕는다 .)

2. 사이트의 "상태"

SEO는 서비스의 40x 혹은 50x 페이지의 수에 따라서 사이트에 대한 평가를 내린다. 이는 검색엔진에게 얼마만큼 크롤링에 예산을 부여할 지 결정하기 때문에, 사이트의 오류를 줄이거나 오류시에 메인으로 리다이렉트하는 UX를 반영하는 것이 SEO에 도움이 된다.
만약 그럴 상황이 안된다면 Sitemap 최적화라도 힘쓰자. 검색엔진이 최대한 에러페이지를 접근하지 않는 것이 중요하기 때문에 Sitemap에 에러 페이지가 노출되어 있는것은 나쁜 영향을 준다.

3. 결국 빨라야 한다.

크롤러가 우리 서비스에 투자하는 시간은 정해저 있다. 서비스가 느리다면 크롤링 또한 많이 실행할 수 없다.