
이 블로그 만들기 #4
이 블로그의 SEO 이야기
SEO, 별도의 노력이 필요한가?
검색 엔진 최적화(Search Engine Optimization, SEO)는 그간 들어온 프론트엔드 강의들에서 특별히 언급된 적이 없었다. 또, SEO에 대한 개념이 없을때 만들었던 브레멘 웹사이트가 검색이 잘 되었기에, 이후 개념을 접했을 때도 '그래? 근데 그게 꼭 필요한거 맞아? 시맨틱 HTML로 작성하면 잘만 검색되는것 같은데?'라며 무시했었다.
하지만 이번 블로그를 만들고, 열심히 작성한 포스팅이 검색 엔진에 전혀 노출이 되지 않고 있다는 사실을 알게되자 마음이 바뀌었다. 금빛 땅의 유산 포스팅을 열심히 쓰고 이런저런 키워드로 모두 검색해봤지만 구글 검색 첫 페이지부터 끝까지 나오지 않았던 것이다.

어디가 잘못된 걸까? 이후 SEO의 원리에 대해 공부하고, 관련된 내용들을 적용하면서 느꼈지만 웹개발자로써 SEO에 대한 이해는 필수적이다. 웹개발이라면 서비스를 잘 만드는것 만큼이나 검색 엔진에 잘 노출하는 것도 중요한 능력이기 때문인것도 있고, 좀 더 웹에 대해 깊이있는 이해를 할 수 있는 개념들이 긴밀하게 연결되어 있기 때문이기도 하다.
SEO의 기본 원리
SEO를 한 마디로 표현하라면 관련 키워드들로 검색했을 때 상단의 결과로 얼마나 잘 노출되는지의 정도이다. SEO가 잘 되었다면 내가 웹에 올린 문서들이 그것을 필요로 하는 사람들이 검색했을 때 잘 노출된다. 반면, SEO가 잘 안되었다면 내가 올린 문서들은 검색되지 않고, 관심있어할 만한 사람들에게 읽히지 않는다.
따라서 SEO는 검색 엔진이 내 문서들을 어떻게 인식하고 있는지에 달려있다. 퀄리티가 좋은 문서고 이러저러한 키워드와 높은 관련성이 있는 문서라고 생각한다면, 검색 엔진은 해당 키워드가 검색되었을 때 내 문서를 상단에 띄운다.
검색 엔진에 대해서는 <Understanding the Digital World>를 읽고 글로 정리하며 배워볼 기회가 있었다. 간략하게만 복습해보면, 웹이 너무나도 방대한지라 검색 엔진은 그때그때 웹을 다 뒤져가며 결과를 내놓을 수는 없으므로 주기적으로 크롤링을 한다. 그리고, 이렇게 해서 얻은 정보들을 분석하고 간단하게 저장하는데, 이를 인덱싱이라고 한다. 또, 검색어 쿼리가 들어오면 사용자의 필요에 맞게 인덱싱된 문서들을 랭킹하여 사용자가 좋아할만한 순서대로(는 현실에서는 광고가 우선된다) 보여준다.
SEO 과정은 이 모든 단계에서 검색 엔진이 내 페이지를 더 잘 이해하고, 더 높게 평가하도록 하는 모든 관련된 작업들이다. 접근성이 UI/UX의 중요한 이슈 중 하나인데, 개인적으로 SEO는 사람이 아닌, 검색 엔진의 접근성을 높이는 과정이라고도 생각해본다.
기본: HTML 잘 작성하기
웹사이트가 단순하든, 화려하든, 상호작용이 되든, 그림으로 가득하든 어쨌든 웹사이트는 모두 HTML 문서다. 사람은 CSS와 브라우저의 힘으로 스타일링된 문서를 보며 제목이 뭔지, 어디서부터 어디가 본문이고 어디가 푸터인지 보지만 검색 엔진들은 HTML 태그를 통해 문서를 본다.
따라서 문서의 위계에서 정확한 기능을 의미하는 태그, 즉 semantic tag를 사용하는 것이 검색 엔진들이 문서를 파악하는데 도움을 준다. HTML5 이전에는 <div>
, <span>
들로 문서들이 가득했다. 문서의 구조는 <div>
에 id
나 class
같은 속성으로 드러났는데, 직관적으로 보이진 않았다. HTML5에서는 <header>
, <nav>
, <aside>
, <footer>
, <section>
, <article>
등 다양한 태그들이 등장해 HTML 문서를 보다 직관적으로 파악할 수 있게 되었다.

<nav>
나 <footer>
등은 블로그 전반의 구조와 관련된 코드에 썼고, 이 웹사이트는 블로그이기 때문에 각 포스트들도 semantic하게 만들 필요가 있었다. 기본적으로 mdx 문서들은 <article>
태그로 감싸서 포스트임을 알리고 있다. 또, 제목은 <h1>
태그로 작성하고 하위는 <h2>
를 쓰고...하는 헤딩 계층 구조를 지킨다. 이외에도 날짜 정보는 <time>
을 쓰고, 강조해야하는 내용은 <strong>
을 쓰고, <figure>
, <img>
등에는 alt
속성을 넣는다.
javascriptexport default async function Page({ params }: { params: Params }) { const { slug } = await params; const { default: Post } = await import(`@/app/content/devlog/${slug}.mdx`); const filePath = path.join( process.cwd(), 'src/app/content/devlog', `${slug}.mdx` ); const fileContent = fs.readFileSync(filePath, 'utf8'); const { data } = matter(fileContent); // ... return ( <article className="w-full overflow-x-hidden"> <h1>{data.title}</h1> <time dateTime={data.date}>{formattedDate}</time> <Post /> </article> ); }
meta 태그
메타 태그는 HTML의 <head>
섹션에 들어가는 정보로, 검색 엔진과 소셜 미디어 플랫폼에 페이지 정보를 제공한다. 사용 방식에 변화를 겪기도 했지만, HTML 2.0(1995년)부터 존재했던 유구한 태그다.
사람 입장에선 눈에 잘 띄지 않는 정보이지만(물론 브라우저 탭 등에서나 개발자 도구 등에서 볼 수 있다), 검색 엔진에게는 해당 문서가 어떤 문서인지에 대한 정보가 담긴 중요한 태그다.
현재 이 블로그에선 각 페이지 타입(review
, notes
, study
등)마다 generateMetadata
함수를 통해 동적으로 메타데이터를 생성한다:
javascriptexport async function generateMetadata({ params }: { params: Params }): Promise<Metadata> { // MDX 파일에서 frontmatter 데이터 추출 const { data } = matter(fileContent); return { title: data.title, description: data.description, keywords: data.tags || [], authors: [{ name: 'Sungyup Joo' }], // ... }; }
이를 통해 각 포스트마다의 제목과 설명을 스타일링할 때도 쓸 수 있고, 검색 엔진에도 알릴 수 있다.
Open Graph
예전엔 없었으나 오늘날엔 (사실 2010년에 나왔으니 한참 되었다) 특히나 중요해진, 또 하나의 메타데이터는 Open Graph이다. 오픈그래프는 페이스북에서 만들어진 프로토콜로, 소셜 미디어에서 링크를 공유할 때 보여지는 미리보기를 설정할 수 있게 한다.
참고로 아래의 canonicalUrl
(정규화 URL)은 검색 엔진에게 컨텐츠의 원본이 이 url임을 명시하는 기능을 한다. 즉, 대표 url이 어떤 주소인지를 알려주는 기능이다. 동일하거나 유사한 컨텐츠가 여러 URL에 있을때, 이 canonical url 덕에 검색 엔진은 여러 url의 검색 순위를 나눠버리거나 중복 컨텐츠로 판단하지 않게 된다. 이 역시 SEO에 중요한 기능을 하는데, 검색했을때 여러 url로 SEO 우선순위가 분산되지 않게 도움을 주기 때문이다.
javascriptopenGraph: { title: data.title, description: data.description, url: canonicalUrl, siteName: "sungyup's", images: [{ url: coverImageUrl, width: 1200, height: 630, alt: data.title, }], locale: 'ko_KR', type: 'article', publishedTime: data.date, modifiedTime: data.date, authors: ['Sungyup Joo'], tags: data.tags, }, twitter: { card: 'summary_large_image', title: data.title, description: data.description, images: [coverImageUrl], creator: '@sungyupjoo', }
Open Graph는 직접적인 SEO 요소는 아니다. Google이나 Bing등의 검색 엔진은 Open Graph 메타태그를 공식적으로 랭킹 요소로 사용하지 않는다고 한다.
하지만, Open Graph는 소셜 미디어에서 공유할때 컨텐츠가 보다 예쁘고 명확하게 보이게 한다. 즉, 클릭하고 싶게 한다. 이로 인해 웹사이트 유입 트래픽이 증가하는데 긍정적인 영향을 미치고, SEO 점수를 받는데 유리해진다. 사실상 오늘날, 트래픽을 늘리는 가장 중요한 방법 중 하나가 소셜 미디어에서 공유되는 것인만큼 Open Graph는 설정하지 않을 이유가 없다.
Sitemap(Google Search Console)
사이트맵은 검색 엔진이 웹사이트의 페이지들을 찾고 인덱싱 할 수 있도록 도와주는 XML(eXtensible Markup Language) 파일이다. 검색 엔진은 크롤링할 때 sitemap.xml
파일을 참고하는데, 이 파일엔 웹사이트에 어떤 페이지가 있고, 언제 업데이트 됐는지를 파악할 수 있는 정보가 포함된다.
이 블로그에선 next-sitemap을 통해 사이트맵을 동적으로 생성한다. 디펜던시를 설치하고, next-sitemap.config.js
파일을 생성한다. priority를 통해 어떤 경로의 어떤 페이지들이 중요한 페이지인지 검색 엔진에 힌트를 줄 수 있지만, 반드시 검색 엔진이 따르는 것은 아니라고 한다.
javascript/** @type {import('next-sitemap').IConfig} */ // ... module.exports = { siteUrl: process.env.NEXT_PUBLIC_BASE_URL || 'https://www.sungyup.com', generateRobotsTxt: false, // robots.txt 문서는 별도로 관리 중 outDir: 'public', generateIndexSitemap: false, // 인덱스 사이트맵 생성 방지 exclude: ['/api/*'], sitemapSize: 5000, changefreq: 'weekly', priority: 0.7, additionalPaths: async (config) => { const result = []; for (const path of additionalPaths) { if ( path.startsWith('/review/') || path.startsWith('/notes/') || path.startsWith('/study/') ) { result.push({ loc: path, changefreq: 'weekly', priority: 0.8, lastmod: new Date().toISOString(), }); } } return result; }, transform: async (config, path) => { if ( path.startsWith('/review/') || path.startsWith('/notes/') || path.startsWith('/study/') ) { return { loc: path, changefreq: 'weekly', priority: 0.8, lastmod: new Date().toISOString(), }; } if ( path === '/' || path === '/review' || path === '/notes' || path === '/study' ) { return { loc: path, changefreq: 'daily', priority: 1.0, lastmod: new Date().toISOString(), }; } return { loc: path, changefreq: config.changefreq, priority: config.priority, lastmod: new Date().toISOString(), }; }, };
사이트맵은 프로젝트 빌드 시 함께 생성되도록 package.json
에 설정해두었다.
javascript"scripts": { "dev": "next dev", "build": "next build", "postbuild": "next-sitemap", }
이 프로젝트는 husky
로 깃훅을 쓰고 있기 때문에, 커밋 시 빌드가 되고 빌드가 되면 위의 package.json
으로 사이트맵도 업데이트된다. 아래는 /husky/pre-commit
파일이다.
bashecho "사이트맵 생성중..." npm run build git add .
다른 사이트들에서 링크
구글이 사용하는 PageRank 알고리즘은 각 페이지에 품질 측정값을 할당했는데, 기본적으로 다른 페이지가 링크를 걸거나, 높은 순위가 매겨진 페이지에서 링크를 건 페이지에 더 높은 점수를 부여했다고 한다.
이에 SNS의 내 프로필들에 내 페이지 링크를 걸어두었다. 링크드인, 인스타그램, 유튜브는 신뢰도가 높은 사이트들이었고 여기에 내 페이지 링크를 걸면서 사이트 방문수가 늘었다. 비단 이 사이트들을 통해서 들어온 사람들 뿐 아니라(Vercel Analytics를 보기 때문에 UTM Parameter를 통해 어디서 온지 알 수 있었다), 전반적으로 점수가 향상된것 같다고 느껴졌다. 가장 놀라운 유입 경로는 ChatGPT였는데, 어쩌다가 내 블로그가 검색되었는지 정말 궁금하긴 하지만 그렇게까지 로깅을 하진 않았다.
또, 여전히 하루에 200명 전후의 사람들이 방문하는 내 네이버 블로그에 이 블로그의 포스팅들 중 일부를 가져다 붙이고, 이 블로그에 원문을 적어두었으니 관심 있으신 분들은 방문해달라고 덧붙였다. 물론 그렇게까지 방문하실 분들이 얼마나 될지는 모르겠지만, PageRank 알고리즘을 위해서라도 링크를 걸어 점수를 높이고자 했다.
SEO, 그래서 정말 필요했나?
사실 이 블로그는 바이럴한 컨텐츠로 폭발적인 반응을 이끌 목적이라기보단 꾸준히 공부한 내용, 생각한 내용들을 기록해두는 사적 공간이고 필요할 시 내 포트폴리오 페이지처럼 사용하고자 만든 것이기에 SEO는 큰 의미가 없을 수도 있다.
하지만 누군가가 읽을 수 있는 글을 쓰는 것과, (진짜로) 혼자만 읽을 글을 쓰는 것의 마음가짐은 굉장히 다르다. 누군가에게 언제든지 읽힐 수도 있는 글을 쓴다고 생각하면 보다 표현이나 오타에 주의를 기울이게 되고, 좀 더 쉽고 명확하게 머릿속에 있는 것을 꺼내기 위해 정제하려는 노력을 한다.
따라서 부족한 이 글들도 '혹시나 필요한' 분들께는 도움이 될 수 있기를 바라는 마음으로 SEO 개선에 노력을 들였다. 물론, 웹개발자로썬 꼭 해보면 좋은 경험이었고 이를 통해 이후 올레오 웹사이트를 만드는데도 도움이 되었다.