Reese-log
  • About
  • Blog

© 2025 Reese. All rights reserved.

2024년 4월 3일

SPA 환경에서 최선을 다한 SEO 대응기

#SEO

SPA는 index.html에 js를 이용해 동적으로 각 페이지를 생성하기에 SEO 친화적이지 않습니다.

따라서 SPA프로젝트에 최대한 SEO에 대응해보는 작업을 해보겠습니다.

1. 동적 metadata 주입을 위해 react-helmet-async 사용

react-helmet-async

2. JSON-LD - Scheme Markup 적용

JSON-LD란?

기본적인 Meta Tag 보다 페이지에 대한 메타 정보를 자세하고 체계적, 구조적으로 제시하는 양식입니다.

페이지의 종류, 주제, 주소, 예상 동작 등, 페이지에 대한 정보 뿐 아니라, 장소, 인물, 단체, 행사 등 사이트에서 다루는 주제에 대한 내용까지도 자세하게 설명할 수 있습니다.

적절하게 구조화된 데이터를 적용하는 것으로 검색에서 유리한 순위를 배정받을 뿐만 아니라, 구글 검색 결과로 보여지는 내용이 더 풍부해지므로 검색자들의 클릭률도 높일 수 있게됩니다.

javascript
1{
2	'@context': 'https://schema.org/',
3	'@type': 'WebApplication',
4	name: '서비스 이름',
5	url: `${import.meta.env.VITE_OG_URL}`,
6	keyword: ['관광','어쩌고'],
7	logo: '이미지url',
8	description: '설명 적기',
9	category: ['카테고리','카테고리2'],
10	mainEntityOfPage: '서비스 url',
11	breadcrumb: {
12		'@type': 'BreadcrumbList',
13		itemListElement: [
14			{
15				'@type': 'ListItem',
16				position: 1,
17				item: {
18					url: '서비스 url',
19					name: '메인',
20				},
21			},
22			{
23				'@type': 'ListItem',
24				position: 2,
25				item: {
26					url: '서비스 url/promotion',
27					name: '프로모션',
28				},
29			},
30		],
31	},
32};
  • @context: JSON-LD 문서에서 사용되는 용어들의 의미를 정의하는 데 사용됩니다. @context는 용어들을 URI에 매핑하여 명확한 의미를 부여합니다. 일반적으로 Schema.org 어휘집을 사용합니다.
  • @type: 데이터의 유형을 지정합니다. Schema.org에 정의된 유형 (예: Person, Organization, WebPage 등)을 사용하여 데이터의 종류를 명시합니다.
  • property: 각 유형에 따라 관련된 프로퍼티를 사용하여 데이터를 표현합니다. 예를 들어, Person 유형에는 name, jobTitle, email 등의 프로퍼티가 있습니다.
  • @id: 데이터의 고유 식별자를 나타냅니다. URI 형식으로 표현됩니다.
  • 중첩 구조: JSON-LD는 객체 안에 객체를 포함할 수 있는 중첩 구조를 지원합니다. 이를 통해 복잡한 데이터 구조를 표현할 수 있습니다.
  • [참고]

    https://www.w3.org/TR/json-ld11/#specifying-the-type

    https://json-ld.org/

    https://schema.org/

    3. sitemap.xml generator 구현

  • xmlbuilder2라는 패키지를 설치합니다. npm i xmlbuilder2
  • root위치에 createSitemap.js 파일을 생성해 함수를 구현합니다.
  • javascript
    1import path from 'path';
    2import fs from 'fs';
    3import { fileURLToPath } from 'url';
    4import { create } from 'xmlbuilder2';
    5
    6const __filename = fileURLToPath(import.meta.url);
    7const __dirname = path.dirname(__filename);
    8
    9const urls = [
    10  { loc: '서비스 url', lastmod: '2024-04-01', changefreq: 'always', priority: 1.0 },
    11  { loc: '서비스 url', lastmod: '2024-04-01', changefreq: 'always', priority: 0.8 },
    12  { loc: '서비스 url', lastmod: '2024-04-01', changefreq: 'always', priority: 0.6 }
    13];
    14
    15const root = create({ version: '1.0', encoding: 'UTF-8' })
    16  .ele('urlset', { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' });
    17urls.forEach(url => {
    18  const urlEle = root.ele('url');
    19  urlEle.ele('loc').txt(url.loc);
    20  urlEle.ele('lastmod').txt(url.lastmod);
    21  urlEle.ele('changefreq').txt(url.changefreq);
    22  urlEle.ele('priority').txt(url.priority);
    23});
    24
    25const xml = root.end({ prettyPrint: true });
    26
    27fs.writeFileSync(path.resolve(__dirname, 'public', 'sitemap.xml'), xml);
    28
    29console.log('Sitemap generated successfully!');
    30

    package.json script 변경

    javascript
    1"scripts": {
    2		"dev": "vite --host 0.0.0.0 --port 9102 --mode dev",
    3		"build:sitemap": "node createSitemap.js",
    4		"build": "npm run build:sitemap && tsc && vite build --mode prod",
    5		"preview": "vite preview",
    6		"postinstall": "patch-package"
    7	},

    변경된 부분을 주황색으로 표시했습니다.

    4. sitemap.xml 생성(예시)

    javascript
    1<?xml version="1.0" encoding="UTF-8"?>
    2// 문서는 xml이고 UTF-8 인코딩을 사용한다는 의미입니다.
    3<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    4    <url>
    5        <loc>https://www.example.com/</loc>
    6        <lastmod>2024-04-01</lastmod>
    7        <changefreq>daily</changefreq>
    8        <priority>1.0</priority>
    9    </url>
    10    <!-- Additional URL entries -->
    11</urlset>
  • <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">: 모든 URL 항목을 포함하는 XML 파일의 루트 요소입니다. xmlns 속성은 사이트맵의 XML 스키마를 정의합니다.
  • <url>: 사이트 내의 특정 URL에 대한 정보를 포함합니다. 크롤링하고 색인을 생성하려는 사이트의 각 페이지에는 고유한 <url> 항목이 있어야 합니다.
  • <loc>: 페이지의 URL입니다. 이는 완전한 표준 URL이어야 합니다. 예를 들어 https://www.example.com/입니다.
  • <lastmod>: 페이지 콘텐츠가 마지막으로 수정된 날짜입니다. 이는 검색 엔진이 콘텐츠가 마지막으로 업데이트된 시기를 이해하는 데 도움이 됩니다.
  • <changefreq>: 페이지 콘텐츠가 얼마나 자주 변경되는지에 대한 선택적 힌트입니다. 값은 '항상', '매시간', '매일', '매주', '매월', '매년' 또는 '없음'일 수 있습니다. 지시어는 아니지만 페이지를 크롤링하는 빈도에 대해 검색 엔진에 제안하는 것입니다.
  • <priority>: 사이트의 다른 URL에 비해 이 URL의 우선순위를 지정하는 선택적 속성입니다. 값 범위는 0.0~1.0이며, 1.0이 가장 높은 우선순위입니다. 어떤 페이지가 더 중요한지에 대한 힌트를 검색 엔진에 제공하는 데 사용됩니다. 'changefreq'와 마찬가지로 이 역시 제안 사항이며 크롤링에 영향을 미칠 수도 있고 영향을 주지 않을 수도 있습니다.
  • 5. robots.txt 추가

    javascript
    1//예시
    2# https://www.robotstxt.org/robotstxt.html
    3User-agent: *
    4Disallow:/booking/
    5Disallow:/detail/
    6Disallow:/complete/
    7Disallow:/404/
    8
    9Sitemap: 서비스url/sitemap.xml