사이트맵을 최적화해보자!
현재 내 사이트 머그인(https://www.mug-in.com/)에서는 프론트엔드가 배포 될 때마다 사이트맵을 생성하고있다. 이는 SEO 최적화를 위함인데, 구글봇이 주기적으로 내 사이트를 방문하여 사이트맵을 읽고, 각 경로에 접속해 유효한 색인과 유효하지 않은 색인을 구분해준다.
현재는 next-sitemap이라는 라이브러리를 사용하여 설정 파일을 중심으로 내 next.js 프로젝트가 vercel에 배포될 때마다 사이트맵을 생성하고 있다.
사용은 아래와 같이 하고 있다.
현재 사이트맵 생성 방식
next-sitemap 사용
- 사이트맵 설정파일 생성
- 파일명 : next-sitemap.config.js
- 프로젝트 루트에 생성한다.
module.exports = {
siteUrl: process.env.NODE_ENV === 'production' ? '(...)' : '<http://localhost:3000>',
additionalPaths: async (config) => {
// 레시피 ID 리스트 가져오기
const recipe_res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}recipe/recipe-id-list`);
const recipeIdList = await recipe_res.json();
// 보드 사이트맵 ID 리스트 가져오기
const board_res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}board/sitemap-board-id`);
const board_sitemap_res = await board_res.json();
const paths = [];
// 레시피 경로 추가
for (let i = 0; i < recipeIdList.length; i++) {
paths.push(await config.transform(config, `/recipe-detail/${recipeIdList[i]}`));
}
// 보드 경로 추가
for (let i = 0; i < board_sitemap_res.length; i++) {
paths.push(await config.transform(config, `/board/${board_sitemap_res[i].menuId}/detail/${board_sitemap_res[i].boardId}`));
}
console.log("사이트맵 확인 ", paths);
return paths;
},
generateRobotsTxt: true, // robots.txt 생성
sitemapSize: 5000,
changefreq: 'daily',
priority: 0.7,
exclude: ['/admin/**'],
};
해당 설정이 완료되면 아래 명령어를 실행하여 사이트맵을 생성할 수 있다.
- npx next-sitemap
현재는 pacak.json파일 내에 빌드가 완료될 때 위 명령어를 실행하여 자동으로 사이트맵이 생성되도록 하고 있다.
- 사이트맵 자동 빌드
- 파일명 : package.json
- 라이브러리 이름, 버전 정보
- 의존 관리
- 명령어 단축 스크립트 정의
{
"name": "recipe-front",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"https-dev": "node server.js",
"build": "next build",
"start": "next start",
"lint": "next lint",
"postbuild": "next-sitemap" //빌드 후 실행
},
"dependencies": {
(...)
}
}
- postbuild는 빌드 이후 자동으로 실행되는 스크립트를 정의한다.
- 즉 npm run build를 통해 next build가 실행되면 next-sitemap이 실행된다.
- 이를 통해 위에서 한 설정을 따라 sitemp파일이 생성된다.
나의 요구사항과 수정 사항
나의 요구 사항
위 기능으로 여태까지 사이트를 유지하고 있었으나, 아래 나열한 사항들에 대해 불편함을 느껴 사이트맵 생성을 수정하고자 한다.
- 새로운 게시글이 게시되어도 사이트맵이 새로 생성되지 않는다.(새로 빌드 및 배포 시까지 사이트맵은 유지된다.)
- 사이트맵이 변경되어도 구글 및 네이버가 내 사이트가 업데이트 되었다는 사실을 모른다.
수정 사항
- 게시글이 생성되면 사이트맵에 새로 추가하여 새 사이트맵을 생성한다(10분 캐싱)
- 사이트맵은 스프링 서버에서 생성해서 next.js서버에서 받아서 처리한다.
자바 사이트맵 생성
서칭 후 dfabulich/sitemapgen4j라는 라이브러리를 사용하려고 한다.
가장 쉬운 사이트맵 생성 방법
WebSitemapGenerator wsg = new WebSitemapGenerator("<http://www.example.com>", myDir);
wsg.addUrl("<http://www.example.com/index.html>"); // repeat multiple times
wsg.write();
위 코드를 베이스로 아래와 같이 테스트 코드를 작성해보자.
@Slf4j
@RequestMapping("/seo")
@Controller
public class SEOController {
@GetMapping("/sitemap")
public void generateSitemap(){
try{
File tempDir = new File(System.getProperty("java.io.tmpdir"));
String baseUrl = "<http://www.mug-in.com>";
//sitemap 생성
WebSitemapGenerator wsg = new WebSitemapGenerator(baseUrl, tempDir);
wsg.addUrl(baseUrl + "/test1.html");
wsg.addUrl(baseUrl + "/test2.html");
wsg.write();
System.out.println("사이트맵 생성 완료! 위치: " + tempDir.getAbsolutePath());
}catch (Exception e){
e.printStackTrace();
}
}
}
결과
- 사이트맵 생성 완료! 위치: C:\Users\JUI~1\AppData\Local\Temp
- 해당 경로에 들어가보면 sitemap.xml라는 파일이 있고, 열어보면 아래와 같이 사이트맵이 생성되어있다.

내가 사이트맵 최적화를 위해 해야 할 것은 아래와 같다.
- next.js의 /sitemap.xml 페이지에 접속시 백엔드로 라다이렉팅한다
- 백엔드에서는 사이트맵을 응답한다.
- 이때, 사이트맵은 스트링 형태로 반환한다.(캐싱을 용이하게 관리하기 위함)
사이트맵 생성 이관
다행이 next.js 설정 자체는 어렵지 않다.
- ref : https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites
- rewrites기능을 이용하면 들어오는 요청을 다른 목적지로 보내버릴 수 있다.
- 해당 기능을 이용하면 유저는 사이트 내 주소 변화가 없이 느낀다고 한다.
module.exports = {
async rewrites() {
return [
{
source: '/sitemap.xml',
destination: '내 백엔드 사이트맵 생성 api 경로',
},
]
},
}
이제 위 두 부분을 합쳐서 사이트맵을 생성한다.
API 서버 코드
controlller
@Slf4j
@Controller
@RequestMapping("/seo")
@RequiredArgsConstructor
public class SEOController {
private final SEOService seoService;
@GetMapping("/sitemap")
public ResponseEntity<String> generateSitemap() throws IOException {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_XML)
.body(seoService.getSiteMap());
}
}
service
@Slf4j
@Service
@RequiredArgsConstructor
public class SEOServiceImpl implements SEOService {
private final SEORedisRepository seoRedisRepository;
private final RecipeRepository recipeRepository;
private final BoardRepository boardRepository;
@Value("${front_url}")
private String frontUrl;
public String getSiteMap() throws IOException {
String cachedXMLSiteMap = seoRedisRepository.getSitemap();
// 캐싱된 사이트맵이 있는 경우 반환
if(cachedXMLSiteMap != null){
return cachedXMLSiteMap;
}
// 캐싱 된 사이트맵이 없는 경우 새로 생성 + 캐싱
File tempDir = new File(System.getProperty("java.io.tmpdir"));
//sitemap 생성
WebSitemapGenerator wsg = new WebSitemapGenerator(frontUrl, tempDir);
setBoardUrlToWsg(wsg);
setRecipeUrlToWsg(wsg);
wsg.write();// 기존에 사이트맵 파일 있을 시 덮어쓰기
log.info("[getSiteMap] - 사이트맵 생성 완료 - 위치: " + tempDir.getAbsolutePath());
Optional<File> sitemapFileOpt = Arrays.stream(Objects.requireNonNull(tempDir.listFiles()))
.filter(f -> f.getName().startsWith("sitemap") && f.getName().endsWith(".xml"))
.max(Comparator.comparingLong(File::lastModified));
if (sitemapFileOpt.isEmpty()) {
throw new BusinessException(ErrorCode.SITEMAP_FILE_NOT_FOUND);
}
File sitemapFile = sitemapFileOpt.get();
String sitemapXml = Files.readString(sitemapFile.toPath(), StandardCharsets.UTF_8);
// 캐싱
seoRedisRepository.setSitemap(sitemapXml, 10);
return sitemapXml;
}
public void setBoardUrlToWsg(WebSitemapGenerator wsg){
List<Board> boardList = boardRepository.getNotDeletedBoardList();
List<BoardSiteMapDTO_OUT> boardSiteMapDTOList = boardList.stream().map((board)-> BoardSiteMapDTO_OUT.builder()
.boardId(board.getBoardId())
.menuId(board.getBoardMenu().getBoardMenuId())
.build()).toList();
for(var boardSiteMap:boardSiteMapDTOList){
wsg.addUrl(frontUrl + "board/" + boardSiteMap.getMenuId() + "/detail/" + boardSiteMap.getBoardId());
}
}
public void setRecipeUrlToWsg(WebSitemapGenerator wsg){
List<Long> recipeIdList = recipeRepository.getNotDeletedRecipeList()
.stream().map(Recipe::getId)
.toList();
for(Long recipeId:recipeIdList){
wsg.addUrl(frontUrl + "recipe-detail/" + recipeId);
}
}
}
이제 해당 프로젝트를 실행 후 사이트 맵을 보면, 아래와 같이 사이트맵이 잘 생성 된 것을 알 수 있다.
<script/>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/2>https://www.mug-in.com/board/1/detail/2</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/3>https://www.mug-in.com/board/1/detail/3</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/4>https://www.mug-in.com/board/1/detail/4</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/5>https://www.mug-in.com/board/1/detail/5</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/6>https://www.mug-in.com/board/1/detail/6</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/7>https://www.mug-in.com/board/1/detail/7</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/2/detail/9>https://www.mug-in.com/board/2/detail/9</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/10>https://www.mug-in.com/board/1/detail/10</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/12>https://www.mug-in.com/board/1/detail/12</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/15>https://www.mug-in.com/board/1/detail/15</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/16>https://www.mug-in.com/board/1/detail/16</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/17>https://www.mug-in.com/board/1/detail/17</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/18>https://www.mug-in.com/board/1/detail/18</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/19>https://www.mug-in.com/board/1/detail/19</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/20>https://www.mug-in.com/board/1/detail/20</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/21>https://www.mug-in.com/board/1/detail/21</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/22>https://www.mug-in.com/board/1/detail/22</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/23>https://www.mug-in.com/board/1/detail/23</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/24>https://www.mug-in.com/board/1/detail/24</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/25>https://www.mug-in.com/board/1/detail/25</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/26>https://www.mug-in.com/board/1/detail/26</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/27>https://www.mug-in.com/board/1/detail/27</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/28>https://www.mug-in.com/board/1/detail/28</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/29>https://www.mug-in.com/board/1/detail/29</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/30>https://www.mug-in.com/board/1/detail/30</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/32>https://www.mug-in.com/board/1/detail/32</a>></loc>
</url>
<url>
<loc><<a href=https://www.mug-in.com/board/1/detail/33>https://www.mug-in.com/board/1/detail/33</a>></loc>
</url>
<url>
(...)
이제 프론트에도 위 설정을 마친 후, 배포하게 되면?
프론트로 들어갔지만 백엔드에서 보내준 sitemap이 화면에 보이게 된다.
이제 특정 게시글이 배포되면 10분간만 기존 사이트맵을 캐싱하고, 새로운 게시글 정보가 포함된 사이트맵을 봇에게 제공해 줄 것이다!
'컴퓨터' 카테고리의 다른 글
| [개발자 면접] 백엔드/자바-스프링 면접 질문과 응답 (1) | 2024.01.23 |
|---|---|
| [Doker]도커 컨테이너에 실행중인 mysql에 접속하기 (0) | 2023.09.20 |
| [Linux] 네트워크 포트 프로세스 확인/삭제/서버 강제종료 (0) | 2023.07.07 |
| [.m5 to .json] 텐서플로우 js .m5확장자 .json변경 (0) | 2023.03.16 |
| [VSC] 비주얼 스튜디오 코드 자동 정렬 단축키 (0) | 2022.12.28 |
댓글