얼마전 회사에서 후배 개발자의 이슈를 함께 고민하던 중 특이점을 발견했습니다.

몇년이 지난 과거 제가 진행했던 프로젝트에 대한 이슈였는데요

GET 방식의 API를 @RequestBody를 이용하여 요청을 받아내고 있었습니다.

애초 제가 해당 프로젝트를 할 때는 그 데이터를 받을 때는 QueryPram으로 받도록 설계했었습니다.

RequestBody로 변경된 이유를 묻자, 취약점 점검 시 해당 부분이 문제점으로 발견되어 고치게 되었다고 하네요...

이슈 처리 바빠서 그 후배에게는 아직 설명을 못해주었지만, GET API를 QueryPram을 RequestBody로 변경한 것은 적절치 못합니다. 오늘은 그 이야기에 대해 포스팅 해보려고 합니다.

 

과거 프로젝트를 진행할 때 당시 개발자들과의 고민은 "어떻게 해야 MSA 서비스에서 RestFul한 API를 제공할 수 있을지"에 대해서 였습니다. API의 기본 아키텍쳐에 대한 고민이었죠. 그 중 가장 컸던 부분은 필터 기능에 대한 것이었습니다.

간단한 예시를 들자면 게시판에서 여러가지 조건검색을 할 수 있는데 이 필터 조건들을 어떻게 API에게 전달할지에 대한 고민이었죠. 곧장 저희도 RequestBody를 떠올렸습니다. 예를 들면 이러한 JSON구조를 생각해 볼 수 있겠네요.

{
    "filterId": "subject", // 어떤 타입의 필터인지? 제목검색, 날짜검색 등등
    "filterTarget": "개발", // 어떤 대상으로 필터링할것인지? 내용, 날짜 등등
    "filterOrder": "like", // 대상에 대해 어떤 조건으로 필터링할것인지? 포함돼는 문자열, 특정 날짜 범위 등등
}

흠... 확인히 RequestBody로 하면 깔끔하게 보이긴하네요, 쓰고싶어지는 유혹(?)이 듭니다. 하지만 저희는 당시 RequestBody방식을 선택하지 않았습니다. 왜냐하면 이건 RestFul하지 않기 때문이죠. 왜일까요?

기본적으로 RequestBody는 GET에서 사용하길 권장하지 않아요 아래와 같은 이유 때문입니다.

 

  • HTTP 표준 준수: HTTP/1.1 표준(RFC 7231)에 따르면, GET 요청은 요청 본문을 포함하지 않아야 합니다. GET 요청의 목적은 리소스를 조회하는 것이며, 본문을 포함하는 것은 표준을 벗어난 사용입니다.
  • 캐싱 문제: GET 요청은 캐시될 수 있어야 합니다. 캐시는 요청 URL을 기준으로 동작하므로, 본문을 포함한 GET 요청은 캐시 시스템에서 올바르게 처리되지 않을 수 있습니다. 본문을 포함한 요청은 캐시 일관성을 유지하기 어렵게 만듭니다.
  • 안전성과 멱등성: GET 요청은 안전하고 멱등적이어야 합니다. 즉, GET 요청은 서버 상태를 변경하지 않아야 하고, 동일한 GET 요청을 여러 번 실행해도 결과가 동일해야 합니다. 본문을 포함하는 GET 요청은 이러한 특성을 깨뜨릴 수 있습니다.
  • 클라이언트와 서버의 지원 부족: 많은 HTTP 클라이언트 라이브러리 및 서버 프레임워크는 GET 요청의 본문을 지원하지 않거나 무시합니다. 예를 들어, 일부 브라우저는 GET 요청 본문을 보내지 않으며, 일부 서버는 본문을 처리하지 않습니다.
  • 표준 툴 및 라이브러리 호환성: 많은 개발 도구와 라이브러리는 GET 요청에 본문이 없다는 가정을 하고 설계되어 있습니다. 따라서, GET 요청에 본문을 포함하면 이러한 도구들과의 호환성 문제를 일으킬 수 있습니다.

또한 첨언을 더 해보자면, GET은 기본적으로 URL에 데이터가 포함되길 기대되며, URL에 요청 파라미터가 포함된 형태 + Body에도 포함된 형태는 일관적이지않으며 API의 복잡도를 오히려 더 늘어나게 된다고 판단했기 때문입니다. 예를들어 다음과 같은 API를 상상해보세요. 

URL
[GET] http://localhost:8080/api/board/100

Requst Body

{
    "filterId": "subject", // 어떤 타입의 필터인지? 제목검색, 날짜검색 등등
    "filterTarget": "개발", // 어떤 대상으로 필터링할것인지? 내용, 날짜 등등
    "filterOrder": "like", // 대상에 대해 어떤 조건으로 필터링할것인지? 포함돼는 문자열, 특정 날짜 범위 등등
}

 

100이라는 boardSeq 게시판에 대해 RequestBody로 검색하는 기능인데... pathParam도 있고, Body에도 조건이 포함되어 있어요. 이러한 구조는 API의 직관성을 해친다고 생각합니다.

그럼 저희는 어떻게 했을까요? 저희는 JSON데이터를 URL인코딩하여 QueryPram으로 처리하였습니다.

URL
[GET] http://localhost:8080/api/board/100?filter=%7B%22filterId%22%3A%22subject%22%2C%22filterTarget%22%3A%22%EA%B0%9C%EB%B0%9C%22%2C%22filterOrder%22%3A%22like%22%7D

Requst Body

없음

 

URL하나만으로 모든 것을 표현할 수 있습니다. 또한 이러한 특징은 사용자가 URL를 타인에게 공유하거나 응용하여 사용 할 때도 도움을 줄 수 있습니다. :) 훨씬 직관적이죠?

그렇다면 보안취약점에 해당 부분이 문제가 된다면 어떻게 해결해야 할까요? 만약 저였다면 문제로 지적된 파라미터에 대해 암호화 처리 등을 통해 해결하였을 것 같아요. ㅎㅎ 아니면 문제가 될 부분이 아닌데 탐지 되었을 가능성도 있으니 그 부분도 고려를 해서 예외처리를 하던가 해야겠지요? 그 후배가 틀렸다는 이야기를하는 것은 결코 아닙니다 :) 개발에는 좋은 방향성은 존재하지만 정답이 있다고 할 수 없기 때문입니다. 어쨌거나 보안취약점은 해결이 되었으니까요 ㅎㅎ 과거의 깊은 고민과 철학들이 담당자가 달라지며 조금씩 조금씩 사라져가는 것은 아주 조금 슬프긴합니다. ㅋㅋ 사람이 변하고 시간이 흐르면 과거의 고민들도 함께 사라져가는 것 같아요.

여러분들의 생각은 어떤가요? 글 읽어주셔서 감사합니다!

 

 

이번에는 독후감이 아니라 팬픽션을 써보았습니다. 3편이 너무 읽고싶은데 작가님께서 안써주셔서 제가 직접 썼어요.

 

보건교사 안은영 3

Episode1. 옴잡이 파수꾼

 

  신음하는 혜민의 연락을 받고 급히 달려나간 것은 이른바 '용 사건'이 벌어지고난 몇 년이 흐른 뒤의 일이었다. 은영과 인표는 퇴근 후, 학교에서 약 30km 떨어진 어느 아름다운 강가 카페에서 조용히 데이트를 즐기고 있었다. 물가에 비친 저녁 노을이 온화한 빛을 드리우고, 잔잔한 강물 위로 반사된 햇살이 부드럽게 흔들리고 있었다. 그들은 본인들이 서로를 얼마나 좋아하는 지에 대해 열띤 토론을 하며 이 순간의 평화로운 행복을 만끽하고 있었다. 그러나 그 평온함도 잠시 혜민의 전화를 받고 은영은 그 소리를 듣고 가슴이 덜컹 내려앉았다. 달콤한 데이트의 여운은 곧바로 무너졌고, 그녀는 한순간에 날카로운 불안과 걱정으로 가득 찼다. 그녀와 인표는 빠르게 자리를 떠야만 했다.
"선생님... 이렇게 연락드려 죄송합니다. 제가 지금 배가 너무 아픕니다. 위가 뒤틀리는 기분입니다. 아무것도 못하겠습니다."

 

  대학생이 된 혜민의 말투는 여전했다. 혜민을 인표의 차 뒷좌석에 최대한 편안하게 눕히고 병원에 도착했을 때, 꽤나 오랜만에 다시 만난 오동호 교수는 그들의 시선을 사로잡았다. 세월이 남긴 흔적은 그의 흰머리에서 뚜렷이 드러났고, 그 모습은 마치 시간의 무게를 그대로 담고 있는 듯 보였다. 흰머리 하나하나가 지나간 날들, 그리고 쌓인 경험과 고뇌를 상징하는 것처럼 느껴졌다. 오동호 교수의 얼굴에 스쳐간 깊은 한숨과 조용한 결연함은 그의 진지한 책임감을 말해주었다.
교수는 손끝으로 조심스럽게 청진기를 이리저리 대며 혜민의 등과 배를 탐색했다. 그의 표정은 집중과 숙고로 가득 차 있었고, 그의 눈빛은 감춰진 고통의 징후를 읽으려는 듯 깊은 고민에 빠져 있었다. 복부를 눌러보며 부드러운 손길로 혜민의 몸을 살펴보는 동안, 그는 자신의 수술이 완벽했음을 다시 한 번 확인하고자 하는 마음속의 갈망과, 현재의 위급한 상황에 대한 엄중한 책임감을 느끼고 있었다.
"설마 기침이나 목아픈 증상은 없나?"
"없습니다."
"이상한 일이네... 위염이나 위궤양일 수 있어. 일단 진통제랑 해열제를 처방해줄게 주사 맞고 3일 뒤에 보자고."
오동호 교수가 진찰을 하는 동안 은영은 장난감 칼로 열심히 무지갯빛 장난캄 칼을 교수의 머리 위, 등, 팔에 휘두르고 있었고 그 옆에 있던 인표는 마치 행위예술을 보는 관객이 된 듯한 표정으로 바라보고 있었다. 그는 그녀가 보이지 않는 무언가를 휘두르며 열심히 제거하는 모습을 보고, 이 귀여운 행위예술이 일상 속의 작은 마법처럼 느껴졌다. 인표의 시선은 은영의 사랑스러운 동작 하나하나를 소중히 담아내는 듯, 그녀의 모든 행동이 특별하고 소중한 순간처럼 느껴졌다.
"선생님.. 제가 모처럼와서 이렇게 치료해드리는데, 그게 다에요? 제대로 보신 것 맞죠?"
"문제가 될만한 소견은 없네만 경과를 더 지켜보는게 좋겠네."

  결국 별소득없이 혜민의 집으로 돌아왔다. 은영은 혜민을 눕히고 약을 먹이고 땀을 닦아주고 있었다. 
인표는 어질러진 대학생 혜민의 자취방을 정리하기 시작했다. 방 안은 시간이 쌓인 흔적들로 가득했다. 책상 위에는 쌓인 강의 노트와 교과서, 빈 컵과 간식 봉지들이 어지럽게 흩어져 있었다. 벽에는 친구들의 사진과 다채로운 포스터들이 붙어 있었지만, 그 사이로 먼지가 쌓여 있었다. 바닥에는 지저분한 옷과 가방이 널브러져 있었다. 인표는 조심스럽게 물건들을 정리하며, 방의 안쪽에 스며든 대학생의 일상과  남겨온 작은 흔적들을 하나하나 되새겼다. 침대 위에 놓인 비스듬한 책들은 아마도 밤늦게까지 공부하느라 그대로 둔 채로 잠들었을 것이다. 벽에 붙은 사진들은 혜민의 웃음과 추억을 담고 있었고, 각종 메모와 스티커들은 그녀의 하루하루를 기록하고 있는 듯했다. 인표가 책상 위의 혼란스러운 서류들을 정리하면서, 그 안에서 발견되는 조각조각의 이야기를 하나하나 모아갔다. 잃어버린 펜, 책 사이에 끼어 있던 마지막 시험 결과, 그리고 자주 꺼내어 본 참고서들.. 이 모든 것들이 그간 혜민의 열정과 노력, 그리고 고뇌의 흔적을 드러내고 있었다. 그래 참 누구보다도 살고싶어했고 진지한 아이였지. 정리하면서 방 안이 점점 깔끔해지자, 인표는 방의 공간이 조금씩 숨을 쉬는 듯한 느낌을 받았다. 정돈된 공간에서, 자취방은 마치 잠시 동안의 휴식을 취하는 것처럼 고요하고 평화로워졌다. 


  혜민이 고통을 힘겹게 참으며 말했다.
"저 죽는겁니까? 저는 사람으로서 할 수 있는 모든 일은 다 경험하고 싶습니다. 저도 남들처럼 하고싶은거 다 하며 살고 싶다고요."
그간 얼마나 영겁의 시간을 보내왔는지 모른다. 지금까지 20살까지 살다 죽다를 반복해왔으며, 이번이 어엿한 첫 어른으로서의 생활이다. 은영은 혜민의 말을 듣곤 그녀의 영혼 깊숙이 깃든 불꽃 같은 갈망을 느낄 수 있었다. 마치 그녀가 삶의 모든 순간을 불사르고자 하는 듯한, 끝없던 시간 속에서 비로소 빛을 발하는 순수하고도 강렬한 열망이었다. 은영은 그 순수함이 기특하다고 생각했다.
"너는 나랑 한문샘 죽는거 보고 죽어야해. 와서 육개장 먹고 가~"
깔깔대며 대수롭지 않은 일이라는 듯 농담을 했지만 마음속엔 걱정이 한가득이었다. 위를 절제한 것이 정말 원인인걸까...? 조금만 더 지켜보다가 나아지지 않으면 큰 병원에 가서 검사를 받아볼 요령이었다.
은영과 인표가 돌아간 것은 설마 옴과 같은 존재가 혜민을 좀먹고 있는 것은 아닌지 자취방을 샅샅이 뒤져 본 후였다. 그녀의 방에선 아무런 불길한 기운이 느껴지지 않았다.

 


  첫 강의실에 들어서자마자, 혜민은 새로운 세계에 발을 디딘 기분이었다. 캠퍼스의 신선한 공기와 바삐 움직이는 학생들 사이에서 그녀는 이제 막 대학생이 된 자신을 실감했다.
혜민은 복잡한 강의 시간표를 손에 들고, 낯선 건물 사이를 헤매다가도 발걸음이 가벼웠다. 흥분과 설렘이 가슴 속에서 마구 솟구쳐 올랐다. 교실 문을 열고 들어섰을 때, 강의실은 이미 많은 학생들로 북적였다. 모두가 각자의 자리에서 새로운 친구들과 인사를 나누고 있었다. 그녀는 자신도 모르게 웃음을 지으며 빈 자리를 찾아 앉았다.
수업이 시작되자, 교수님의 목소리가 강의실을 가득 채웠다. 혜민은 열심히 아이패드에 필기를 하며, 대학 생활의 첫 단추를 제대로 끼우고자 했다. 아이패드는 입학선물로 은영 커플로부터 받은 것이었다. 은영은 예쁘게 리본으로 포장된 아이패드 박스를 건네며 인생을 자신보다 더 오래보낸, 그러나 경험은 훨씬 짧았던 혜민에게 이 말을 덧붙였다.
"살다보면 이런저런 일을 겪을 거야. 괴로운 일을 피할 수는 없겠지. 하지만 괜찮아. 미래는 그 누구에게도 보이지 않거든. 무리해서 비추어 보려고 하지 않아도 괜찮아."
강의가 끝난 후, 혜민은 같은 과 친구들과 함께 교정을 거닐었다. 그들은 서로의 꿈과 계획을 이야기하며 웃음을 터뜨렸다.

자취방으로 돌아온 은영은 창문을 열고 캠퍼스를 내려다보았다. 밤하늘에 떠 있는 별들이 그녀의 새로운 시작을 축복해주는 것만 같았다. 혜민은 이 순간, 모든 가능성이 열려 있는 자신의 미래를 상상하며 미소를 지었다. 수 세기만에  처음으로 겪어보는 성인으로서의 삶이었다. 이제는 앞으로 70년은 더 살아갈 것이라고 생각했다. 혜민에게 대학생이 된다는 것은 단순히 학교에 다니는 것이 아니라, 새로운 인생의 장을 열어가는 것임을 깨닫는 순간이었다.
그런데 지금 이모양 이꼴이라니 왜 나는 항상 이런걸까...수술한 배에서 느껴지는 고통은 그녀의 인생에 커다란 그림자를 드리웠다. 왠지 죽을 병에 걸렸다는 감각은 무거운 비밀이 되어 그녀를 짓눌렀다.

 


  이틀 후 퇴근한 은영은 곧장 혜민의 간호를 위해 그녀의 자취방으로 향했다. 혜민은 여전히 식은땀을 흘리며 고통스러워하고 있었다.
"저.. 이상합니다. 왠지 배가 더 아픈 것 같은 기분이.."
그 순간 깜짝놀랄 일이 벌어졌다. 어디선가 나타난 여자, 그 여자는 언제나타났냐는 듯 혜민의 배에 손을 조용히 올리고 있었다. 화들짝 놀란 은영은 펄쩍 자리에서 일어났다.
"누구세요? 누구야?! 너?"
"선생님, 무슨 말씀 하시는 겁니까?"
허공에다 소리를 질러대는 은영을 보며 혜민은 은영도 미쳐버린게 아닐까 생각들었다. 은영은 찬찬히 여자를 뜯어보았다. 그림자가 보이지 않는다. 이상하다. 보통 어떤 기운이 느껴지기 마련인데. 마치 이 젤리는 상당히 단단해보이고 악의 또한 전혀 느껴지지 않는다. 그녀가 혜민의 배에 손을 올리고 있는 와중에는 혜민이 더욱 고통스러워하는 듯 했다. 혜민은 여자가 보이지 않는걸까? 옴잡이에서 일반인이 되었으니, 당연하겠구나.
"당신 누구냐고 말을 해보라니까?"
비비탄총을 꺼내 그녀를 향해 겨누고 거칠게 소리쳤다. 그러자 그녀가 손은 여전히 혜민의 배에 대고 있는 채로 천천히 은영쪽으로 고개를 돌렸다.
ㅡ 회수 중 입니다. 저를 계속 방해하신다면 당신 또한 처분할 예정입니다.
대체 뭘 회수한다는 말인가. 선인지 악인지 식별 할 수 없는 이 여자에 대해 은영은 적잖이 당황했다.
"아니... 실례지만 일단 배에 손을 좀 내려놓고 말씀해주시겠어요? 우리 애가 아파하잖아요."
그녀는 무표정하게 대답했다.
ㅡ 그래서 감정을 배제하고 이성적으로 해야합니다. 회수가 거의 다 끝났습니다.
정말이지 감정이라고는 느낄 수 없는 그녀의 대답에 더 늦기전에 어떻게든 그녀의 손을 혜민에 배에서 떨어뜨려야 한다고 생각한 은영은 말 없이 그녀에게 방아쇠를 당겼다. 총알이 그녀의 손목에 맞았고, 효과는 강력했다. 그녀는 말없이 고요하게 곧장 방바닥에 나뒹굴다 천천히 일어났다.
"한번만 더 그랬다간, 곧장 제거해버리겠어 당신이 누군지 말해."
그녀는 여전히 무표정 했고 잠시 할 말을 골똘히 정리하듯 수십 초 간 정적을 이어가다 입을 열었다.
ㅡ 저는 옴잡이 였습니다. 하지만 옴을 씹어먹는 일을 하지 않았어요. 이런 저의 운명을 받아들이지 않은 탓으로 이렇게 제 과업이 남아있습니다.
그녀는 자신의 이름이 살아생전 '은아'라는 이름으로 불렸으며 이러한 옴잡이로서의 영겁의 탄생과 죽음의 반복에 지쳐버린 나머지 어느순간 옴잡이 일을 하지 않았다고 설명했다.
ㅡ 최근 몇번의 환생 동안은 옴도 잡지 않고 마음대로 살다가 20살에 떠났습니다. 제가 그걸 왜 먹어야 합니까? 옴을 잡는 일은 지겹고 20살에 죽어야한다는 것도 재미 없는 일입니다. 그냥 주어진대로 사람의 삶을 살고 싶었습니다. 몇번의 환생동안 일을 하지 않으니 전생이 마지막 기회였었나 봅니다. 저는 정상적으로 회수가 되지 않고 이렇게 영만 남은 채로 옴잡이들을 관리하고 있습니다. 일종의 옴잡이들을 관리하는 일을 하는 겁니다. 특히 이 옴잡이는 이상합니다. 저에게 분명 옴잡이로 인식은 되었으나 이 옴잡이는 고장났어요. 능력을 상실했기에 회수를 해야합니다. 그것이 제가 할 일이죠.
은아는 회수가 완료되면 회수 대상이 결과적으로 정확히 어떻게 돼는지 모른다고 했다. 다만 보이는 현상으로만 설명하자면 흔적도 없이 사라지며 주변 사람들의 기억에서도 잊게 된다는 것 뿐이라는 것이다. 그러고 보니 은아는 혜민과 비슷한 말투를 쓰고 있었다. 은영은 사람이 기계나 사물도 아니고 고장이니 회수니 하는 말이 영 탐탁지가 않았다. 더군다나 어떻게 살린 혜민인데 이제와서 데려가겠다니 은아를 늦기전에 어서 장난감 칼로 제거해야하나 고민하던 와중 다시 은아가 입을 열었다.
ㅡ 저를 방해하실 수는 없을 겁니다. 저는 사라져도 다시 나타나요. 저도 이러고 싶지 않아요. 이제 떠나고 싶은 마음 뿐입니다. 옴에 대한 일은 재미가 없고 옴잡이로서의 삶도 싫습니다. 더군다나 옴잡이들의 팀장 역할이라니 최악입니다.
은아는 일종의 시스템이었다. NPC와 같은 존재였던 것이다. 은아는 단지 못다한 과업을 해야하는 존재였고, 몇년 전 수술을 통해 옴잡이의 정상적인 역할을 하지 못하게된 혜민을 정리하러 왔을 뿐인 것이다. 자신의 일을 못다한 귀신 옴잡이 팀장으로서 과업이 끝날때까지 살아있는 옴잡이들이 올바르게 일을 할 수있도록, 그들이 고장나면 고치도록 프로그래밍 되어있다. 과업은 도대체 언제 끝나는 걸까. 1년일수도 있고 1000년일 수도 있다. 장난감 칼과 비비탄총도 쓸모가 없다니 한숨이 절로 나온다. 이때 문득 도움을 줄만한 생각나는 남자가 있었다. 은영은 그날 밤 침대에 누워 천장을 바라보며 무거운 숨을 내쉬었다. 시계의 초침 소리만이 정적을 깨는 가운데, 그녀의 마음속에서는 끊임없는 번민이 일어나고 있었다. "이 녀석에게 또 연락을 해야 한다니..." 그녀의 마음은 깊은 피로와 불안을 동시에 감당해야 했다. 그와 엮일 때마다 감도는 불쾌감과 어색함이 여전히 그녀를 괴롭혔다.




  은아의 첫 생에서는 모든 것이 신비로웠다. 세상의 아름다움에 매료되고, 사람들과의 교류 속에서 행복을 찾았다. 그러나 20살이 되던 해, 그녀는 갑작스런 죽음을 맞이했다. 눈을 떠보니 다시 태어나 있었고, 동일한 운명을 반복해야 한다는 사실을 깨달았다. 처음에는 그 이유를 알지 못해 혼란스러웠지만, 점차 자신의 사명이 사람들의 나쁜 옴을 제거하는 것임을 알게 되었다. 매번 다른 모습으로 태어나도, 그녀의 운명은 달라지지 않았다. 매 생마다 은아는 20살의 벽을 넘지 못했고, 그 나이에 도달하면 다시 죽음을 맞이했다. 그 과정 속에서 사람들의 나쁜 옴을 씹어먹으며 살아가는 것이 그녀의 삶이었다. 그러나 이러한 반복적인 사명은 그녀에게 점점 더 큰 고통을 안겨주었다. 세기가 지나고 흐를수록 은아는 반복되는 삶에 질려버렸다. 매번 새로운 삶을 시작해도 결말은 언제나 같았고, 그녀는 더 이상 정해진 운명 속에서 살아가고 싶지 않았다. 그녀는 자신의 존재에 대한 회의를 품기 시작했다. 사람들의 나쁜 옴을 제거하는 것만이 자신의 존재 이유라면, 그녀는 더 이상 존재하고 싶지 않았다. 결국 은아는 자신에게 주어진 일을 하지 않기로 결심했다. 나쁜 옴을 씹어먹지 않고, 사람들의 고통을 외면했다. 그녀의 선택은 큰 대가를 불러왔다. 은아는 더 이상 사람으로 태어나지 못하고, 영원한 젤리가 되어버렸다. 차가운 영혼이 된 은아는 이제 이 세상 어디에도 소속되지 못한 채, 끝없는 외로움 속에서 떠돌게 되었다. 그녀는 영혼의 상태에서 자신의 삶을 되돌아보았다. 수많은 생에서 만난 사람들, 그들과 나눴던 순간들, 그리고 그 모든 것을 뒤로 하고 떠나야 했던 슬픔. 은아는 한없이 눈물을 흘렸다. 그녀는 자유를 원했다. 끝없는 속박에서 벗어나고 싶었다. 그러나 이제 그녀에게 주어진 것은 끝없는 고독뿐이었다. 젤리가 되어버리기 직전 전생에서, 여전히 자신의 대의를 지키해 사명을 저버리고 있는 어느 날 은아는 밤하늘을 올려다보았다. 수많은 별들이 반짝이는 하늘 아래에서 그녀는 자신의 존재를 다시금 생각했다. 그녀는 모든 것을 잃었지만, 동시에 모든 것에서 자유로워졌다. 은아는 그제서야 진정한 의미에서 해방된 자신 느낄 수 있었다. 그녀는 그렇게 하늘에 수놓아진 별들을 바라보는 것을 각별히 좋아했다. 별들은 은아가 언제 세상에 태어나더라도 그 자리에서 기다려주었다. 한참을 밤하늘에서 눈을 떼지 못했다. 그리곤 스스로에게 말했다.
'이 세상은 허무해. 처음부터 슬픔이 있었던 것이 아니고 괴로움을 즐거움으로 바꾸는 일도 가능하지. 그러니 괴로움에 걱정에 구애되지 마. 보이고 들리는 것에 매달리지 마.'
더 이상 반복되는 삶 속에서 고통받지 않아도 된다는 사실에 안도하며, 그녀는 마지막으로 깊은 숨을 내쉬었다. 그래, 며칠 후면 다시 죽고, 다시 사람으로 태어나자. 그러나 그것은 오만이었고 하늘은 그녀에게 작정하고, 말을 듣지 않는, 고장난 그녀에게 벌을 주기로 결심한 것 같았다.





  매켄지는 이번에도 연락을 무시하지 않고 학교로 찾아왔다. 지난번 학교에서 벌인 '용 사건'을 생각하면서 시원하게 욕이라도 퍼부으며 이야기를 시작해볼까 했지만 지금은 싫은 소리 해보았자 도움이 될 것이 하나 없다는 걸 알기에 일단 참아보고 자초지종을 설명했다.
"그렇군요... 고스트 버그 이터(Ghost bug eater)라니 정말 흥미롭네요....... 하하 그러게 전문가에게 맡기셨어야지 왜 돌팔이에게 찾아가서 수술을 받아요? 싼게 비지떡..."
매켄지의 말이 끝나기도 전 은영은 결국 참지 못하고 비비탄총으로 매켄지의 머리를 힘껏 내리쳤다. 맞을 말을 했다는 걸 스스로 알기라도 하는건지 걸까 짧은 비명을 지르며 매켄지는 머리를 부여잡을 뿐이었다.
"그래서, 이번 건은 얼마에 해줄거야?"
"저번과 똑같이 2억. 엑스트라는 최대한 발생하지 않도록 해볼게요."
짧은 대답만 하고는 매켄지는 머리를 부여잡으며 우스꽝스럽게 차에 황급히도 올라타더니 이전과 똑같이 대형 세단에 어울리지 않는 요란한 소리와 동작을 하며 도망치듯 멀어져 갔다. 이번 일만 끝나면 다신 연락을 안해야겠다. 생각하며 금액을 어떻게 준비해야 할지 고민에 빠졌다.
"역시 7시리즈는 다르네..."
인표가 매켄지가 사라진 도로를 멍하니 쳐다보며 중얼거렸다. 은영은 인표에게 어머니의 그림을 팔자고 조르는 그런 상상을 잠깐 해보며 그런 애인이 되기에는 정말 최악이라고 생각했다.


  다음날, 학교 지하는 아직 청소를 하지 않은 상태로 은영, 인표, 매켄지 그리고 은아가 모였다. 성질 나쁜 젤리들을 대충 치워낸 인영은 바디샴푸, 바디오일 따위의 제품을 파는 브랜드의 종이백을 매켄지에게 내밀었다. 내용물은 신문지로 덮여있었다. 매켄지는 능청스럽게 바닥에 주저앉아 종이백의 내용물을 쏟아내곤 개수를 세기 시작했다.
"뭐야 450만원 밖에 없잖아요?"
"미안, 조금씩 언젠가는 갚을게? 2억을 어떻게 한번에 준비하냐 일단 이거라도 받고 시작하던가, 아니면 말던가."
매켄지는 곧장 짜증스럽다는 표정을 하더니 곧 일어나 자신이 준비해온 서류가방을 열고 뒤적이기 시작했다. 자신의 다이어리를 꺼내 읽어보곤 마치 매우 소중한 물건인양 조심스레 서류가방에 넣고 어떤 씨앗이 들어있는 통을 꺼내들어 은아에게 성큼성큼 다가갔다.
"뭐하는거야 그 역겨운 씨앗 안집어넣어?"
"은영씨, 그 씨앗 아니에요."
매켄지는 기분나쁘게 히죽 웃으며 대답했다. 은영은 이젠 학교 떠났다고 자신을 선생님 대우 안하냐며, 왜 자신의 이름에 씨를 붙여 말하는지에 대해, 그리고 그것이 얼마나 무례한 표현인지에 대해 열렬히 화를 냈다. 인표는 그런 와중에도 상당히 재미있다는 듯 은영을 쳐다보고 혹시나 모를 기운의 나눔을 위해 손을 쳐다봤다. 화장실에서 얼마나 깨끗하게 씻었는지 손에서는 은은한 물비누의 향기가 났다.
"이건 마리골드 씨앗이에요."
매켄지의 목소리가 지하에 부드럽게 퍼졌다.
"저의 경험에 비추어 보면, 이 씨앗은 은아씨를 원하는 곳으로 안내해 줄 거예요. 진정한 자유를 찾아 떠나게 해줄 거예요."
매켄지는 손끝으로 씨앗을 공중에 살짝 흔들며, 그것의 신비로운 힘을 강조했다.
은아는 묘하게 희망적인 표정을 지었고, 그 표정은 수줍음마저 엿보였다. 싸늘한 지하 바닥에 팔을 벌리고 누운 그녀는 자유로움을 만끽하며 기대감에 가득 차 있었다. 마치 마법 같은 일이 벌어져 그녀를 감싸 안으며 자유의 어떤 장소를 향해 날아오를 것만 같았다. 매켄지는 조용히 은아에게 태어난 생년월일과 시간을 물었고, 은아는 그 질문에 홀린 듯이 대답했다. 매켄지는 자신의 명리학 책을 펼쳐들고, 그 속에서 '옛말에 음양오행이라 하여...'라는 구절로 은아의 일생을 천천히 풀어내기 시작했다. 책장을 넘기며 구구절절 설명을 이어가던 그의 목소리가 은아에겐 따스한 속삭임처럼 느껴졌다. 매켄지는 신중히 마리골드 씨앗을 은아의 이곳저곳에 뿌리며, 그 씨앗들이 그녀의 꿈과 희망을 싹트게 할 것이라 믿었다. 마지막으로, 매켄지는 출처를 알 수 없는 물이 담긴 물병을 꺼내더니, 은아에게 조심스럽게 뿌리며 마무리했다. 물방울이 은아의 몸에 떨어질 때마다, 마치 오래된 전설의 조용한 마법이 펼쳐지는 듯한 느낌을 주었다.

매켄지는 그녀의 마지막을 격려해주었다.
"반복되온 길, 이제는 모든 아픔과 슬픔, 지나온 사랑의 기억을 잊고 별빛 가득한 영원의 품에서 평온히 쉬어요."
ㅡ 고마워요.
은아는 갑작스레 울음을 터뜨렸다. 은영이 보아온 은아는 이런 이미지가 아니었는데... 어린 아이처럼 으앙하며 펑펑 울었다. 왠지 은아의 형체가 서서히 흐물거리기 시작했다. 그동안 지겨웠던 일들에 대한 후련함일까, 이곳을 떠난다는 아쉬움 때문일까, '진짜 죽음'에 대해 극심한 두려움에 우는 것도 같았다. 아마 은아는 '진짜 죽음'이 정말로 무서웠던 것 같다... 정확한 이유는 알 수 없지만, 엄마를 잃고 길에서 우는 아이처럼 매우 서럽게 울었다. 눈물은 끝없이 쏟아져 내렸고, 은아의 몸은 점점 더 흐릿해져 갔다. 그녀는 자신이 이제 완전히 사라질 것임을 느꼈다. 모든 생에서 느꼈던 고통과 슬픔, 그리고 그리움이 한꺼번에 밀려왔다. 다시는 돌아갈 수 없는 시간들, 잃어버린 사랑, 그리고 영원히 가닿을 수 없는 꿈들이 그녀의 눈물 속에 녹아내렸다. 은아는 바닥에 대자로 누워있는 채로 울부짖었다. 그녀의 울음소리는 공허한 지하에 메아리쳤고, 세상은 그녀의 슬픔에 잠시 멈춘 듯했다. 그녀의 영혼은 이제 더 이상 감당할 수 없는 무게에 짓눌려, 무너져 내리는 듯했다. 그동안 견뎌왔던 모든 시간들이 한순간에 무의미하게 느껴졌다.
은아는 자신의 운명 앞에서 한없이 작고 무력했다. 그녀는 자신이 왜 태어나야 했는지, 왜 반복되는 고통을 겪어야 했는지, 왜 이제서야 완전한 죽음을 맞이하게 되었는지 알 수 없었다. 그저 모든 것이 끝나가고 있다는 현실적인 사실만이 그녀를 울게 만들었다.
마지막으로, 은아는 주변을 둘러보았다. 이상한 알 수 없는 기분 나쁜 안좋은 영향의 꿈틀거리며 흩어져 있는 지하의 젤리들이 보인다. 천천히 반대쪽으로 고개를 돌리자 보이는 사람들... 자신의 모양이 어떻게 변해가고 있는지, 어떤 형체로 바뀌고 있는지, 설마 괴상하게 변신해버리지는 않을까 하는 눈빛으로 주시하며 살피고 있는 은영과 매켄지, 은아가 눈에 보이지 않아 그냥 멍하니 딴 곳을 쳐다보고 있는 인표. 이들은 이제 그녀에게 아무런 위안도 되지 않았다. 은아는 흐릿해져가는 시선 속에서, 자신이 사라져가는 것을 느끼며 다시 한 번 울음을 터뜨렸다. 그녀의 울음소리는 점점 희미해졌고, 마침내 은아는 바닥에 누워 우는채로, 고요한 밤 속으로 사라졌다.
은아의 마지막 눈물방울이 땅에 떨어지는 순간, 세상은 다시 조용해졌다. 그녀의 슬픔과 고통은 이제 끝이 났지만, 그 울음소리는 그녀가 위로받던 밤하늘에 영원히 남아 그녀가 겪었던 모든 것을 기억하는 이들의 가슴 속에 깊이 새겨졌을 것이다. 은아는 더 이상 존재하지 않았지만, 그녀의 영혼은 영원히 자유로웠다.


  은영도 덩달아 울음이 났다. 그동안 혼자 얼마나 외롭고 힘들었을까. 자신으로서는 영원히 알 수 없는 그녀만의 슬픔일거라는 생각을 했다. 그녀는 바닥에 무릎을 꿇고 손을 짚으며 주저앉아 버렸다. 눈물이 떨어진 그 자리에, 그녀의 고통과 외로움이 스며들었다. 문득 김강선이 떠올랐다. 김강선도 마지막 순간에 이렇게 펑펑 울고싶었을까? 나로 인해 조금이라도 위로가 되었을까? 은영은 김강선과의 추억에 잠겨 버렸다. 그가 그림자 없는 채로 나타나 함께 시간을 보냈던 집에서의 따뜻한 기억들. 고요한 오후, 소파에 나란히 앉아 책을 읽거나, 작은 키친에서 함께 요리를 하던 순간들. 김강선의 미소와 그의 따뜻한 손길, 그리고 그의 존재가 은영에게 얼마나 큰 위안이 되었는지를 새삼 느꼈다. 그가 정말 행복했을까? 그가 자신의 존재가 은영에게 의미가 있었다는 것을 느꼈을까? 은영은 그와 함께한 소중한 기억들을 가슴 깊이 품고, 그가 은영에게 주었던 마음에 눈물을 흘렸다. 뒤늦게나마... 정말 뒤늦게나마 확인한 서로의 마음이었다. 이런 추억들은 살아있을 때나 만들어주지하며 괜히 투덜대고 싶었다. 은영의 눈물은 김강선과의 추억 속에서 더욱 깊어진다. 김강선은 은영의 마음속에서 여전히 따스한 노을처럼 남아 있었다. 노을이 지는 하늘은 하루의 끝을 맞이하면서도 남아 있는 빛의 흔적을 간직하고 있다. 그 빛은 한편으로는 그리움과 아쉬움을 동반하지만, 또 한편으로는 따스한 기억과 아름다움을 상기시킨다. 그와 함께했던 시간들이 그녀에게 얼마나 큰 의미였는지를 깨달으며, 은영은 잠시 그리움에 잠겨 있다. 그리움과 애틋함이 뒤섞인 눈물 속에서, 김강선과의 소중한 순간들은 은영의 영혼을 따뜻하게 감싸주었다. 부디 잘지내기를... 그와의 추억이 그녀에게 남아 있으며, 그 기억이 그녀의 가슴 속 어딘가에는 영원히 빛나리라는 것을 믿으며, 서서히 눈물을 거두었다.
그때 갑자기 인표의 '끄아악!' 하는 비명이 강렬하게 울려 퍼졌다. 은영은 심장이 뛰는 듯한 충격에 재빨리 고개를 돌렸다. 눈앞에 펼쳐진 장면은 경악 그 자체였다. 인표가 바닥에 쓰러져 있으며, 고통스러운 얼굴로 몸을 움츠리고 있었다. 그의 얼굴은 땀에 젖어 있었다. 그의 비명 소리가 여전히 축축한 공기 중에 맴돌고, 그로 인한 긴장감이 지하에 가득 차 있었다. 은영은 급하게 달려가 인표의 옆에 무릎을 꿇고, 그의 고통스러운 신음을 들으며 안절부절못했다. 인표의 호흡은 불규칙하고 헐떡거렸다. 바닥에 널브러진 그의 모습. 악몽에서 방금 막 튀어나온 듯한 비극적인 신(Scene)은 생생하게 은영의 시선에 박혔다.
"남은 1억 9천 550만원은 이것으로 대신하겠어."
매켄지의 말이 들리는 쪽으로 고개를 돌려보니 매켄지의 신체를 큰 보호막이 둘러싸고있다. '세상에, 미친놈아니야?! 내가 잘못했으니 제발 방어막을 돌려달라고 싹싹 빌어야하나? 당장 사채라도 써서 갚겠다고 제안해야하나' 별의 별 생각이 그 몇초 찰나의 순간에 지나가며 본능적으로 벌떡 일어났다. 하지만 너무 늦었다. 매켄지는 전속력으로 달아났고 인표는 고통에 몸부림치며 바닥에서 신음 소리를 내고 있었다. 그의 몸은 지하실의 음산한 기운에 의해 더더욱 괴로워 보였다. 그리고 그 괴로운 몸부림 사이로, 과거에 보았던 그 씨앗들이 바닥을 덮으며 인표를 에워싸기 시작했다. 씨앗들은 변태적인 생명체처럼 기어오르며, 그의 몸에 붙어 서서히 퍼져나갔다. 지하의 음산한 젤리들이 슬금슬금 인표 쪽으로 기어가고 있었으며 그것은 마치 천천히 다가오는 죽음의 손길처럼 보였다. 젤리들의 기괴한 움직임은 무시무시하고 기이한 감각을 불러일으켰고 실로 불길하게 변해갔다. 이제 그를 보호했던 든든한 보호막은 없고 나약한 몸뚱이만 남아있었다.

 

  은영은 망연자실한 표정으로 마치 시간마저 정지된 듯 그 모든 장면을 멍하니 바라보았다. 그녀의 눈앞에는 인표의 절망적인 모습과 그를 감싸는 씨앗들, 그리고 점점 가까워지는 젤리들의 기괴한 움직임이 혼연일체가 되어 있었다. 은영의 가슴속 깊은 곳에서 솟구치는 절망감은 상상할 수 없을 정도로 강렬했다. 아무것도 할 수 없는 무력감 속에서 그녀는 눈물과 한숨을 삼키며 그 참혹한 광경을 지켜볼 수밖에 없었다. 오늘 무슨 일이 일어난 것인지 은영은 생각했다. 은아는 기약없는 과업으로부터 자유를 찾았을 것이다. 혜민은 지금쯤 복통이 멈추었다는 것을 모른채 작은 자취방 침대에서 곤히 잠들어있을 터였다. 오늘 두 사람을 구해냈다. 그리곤 그녀는 이내 자신의 사명이 무엇인지에 대해 다시금 깨달았다.
"인표야.. 조금만 참아 내가 구해줄게"

 


-2편에 계속...-


 

 

 

 

 

 

 

'독서' 카테고리의 다른 글

밝은 밤  (0) 2024.08.22
덧니가 보고 싶어  (0) 2024.08.11
비행운  (0) 2024.07.27
아주 희미한 빛으로도  (0) 2024.07.14
이만큼 가까이  (4) 2024.06.09

안녕하세요? 어느날 문득 운영단계로 넘어간 작년의 프로젝트가 잘 운영되고 있는지에 대해 살피던 중 SEO가 여전히 잘 안되고 있음을 느꼈습니다. 비록 지금은 제 손을 잠시(?) 떠난 프로젝트이지만, 이상한 애착이 생겼다고해야 할까요... 회사에서는 SPA를 지향하고 있고 팀에서 경험이 전무했던 Next.js를 과감히 도입 결정했던 이유도 SEO 때문이었는데 마음이 불편했습니다. 어쨌든 회사에서는 제 할 일이 주어져있기에 주말에 시간을 내어 스스로 알아보고 해결할 수 있는 방법을 모색해보기로 했습니다. 사족이 길었습니다만 궁금하잖아요?

SEO(검색 엔진 최적화)는 웹사이트의 가시성을 높이고 검색 엔진 결과 페이지에서 더 높은 순위를 차지하기 위해 중요한 요소입니다. Next.js는 React 기반의 프레임워크로, SEO 최적화에 필요한 다양한 기능을 제공하여 웹사이트를 더욱 검색 엔진 친화적으로 만들 수 있습니다. 이번 블로그 포스트에서는 Next.js를 사용하여 SEO를 최적화하는 방법에 대해 알아보겠습니다. 그리고 간단히 라이트 하우스라는 구글의 익스텐션 툴을 이용해 성능 확인하는 방법을 알아보겠습니다!

 

1. 동적 라우팅 및 정적 생성

 

Next.js의 강력한 기능 중 하나는 동적 라우팅(dynamic routing)과 정적 사이트 생성(SSG, Static Site Generation)입니다. 동적 라우팅을 통해 사용자 친화적인 URL 구조를 만들 수 있으며, 정적 사이트 생성을 통해 빠른 로딩 속도와 검색 엔진에 최적화된 페이지를 제공할 수 있습니다.

 

// pages/taehoBlog/[slug].js
import { useRouter } from 'next/router';
import { getAllPosts, getPostBySlug } from '../../lib/api';

export async function getStaticPaths() {
  const posts = getAllPosts();
  const paths = posts.map(post => ({ params: { slug: post.slug } }));
  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  const post = getPostBySlug(params.slug);
  return { props: { post } };
}

const BlogPost = ({ post }) => {
  const router = useRouter();
  if (router.isFallback) {
    return <div>Loading...</div>;
  }

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
};

export default BlogPost;

 

 

상기 샘플소스코드는 동적 라우팅을 설정하고, 정적으로 페이지를 생성하는 것을 보여주며 이를 통해 SEO 최적화에 도움을 줍니다.

 

그렇다면 왜 동적 라우팅이 SEO에 도움이 될까요?

 

이 부분에는 여러가지 이유가 있겠습니다만 주된 이유로는 아래와 같습니다.

 

  1. SEO 친화적인 URL 구조:
    • 동적 라우팅을 사용하면 SEO 친화적인 URL을 쉽게 생성할 수 있습니다. 예를 들어, example.com/blog/post-title 같은 구조는 example.com/?id=123와 같은 구조보다 검색 엔진과 사용자 모두에게 더 이해하기 쉽습니다. 검색 엔진은 URL에 포함된 키워드를 분석하여 페이지의 주제를 파악하는 데 도움을 받습니다.
  2. 콘텐츠 조직화:
    • 동적 라우팅은 콘텐츠를 카테고리나 태그 별로 정리하는 데 유용합니다. 이는 검색 엔진이 사이트 구조를 더 잘 이해하고 크롤링할 수 있도록 도와줍니다. 예를 들어, 블로그 포스트를 example.com/blog/category/post-title와 같이 구성하면 특정 주제에 대한 모든 관련 포스트를 쉽게 찾을 수 있습니다.

 

 

이어서 정적 생성이 SEO에 도움이 되는 이유 뭘까요?

 

  1. 빠른 로딩 속도:
    • 정적으로 생성된 페이지는 빌드 타임에 HTML 파일로 생성되므로, 서버 요청 시 동적으로 생성되는 페이지보다 로딩 속도가 훨씬 빠릅니다. 페이지 로딩 속도는 검색 엔진 순위에 중요한 요소입니다. 구글은 빠른 로딩 속도를 사용자 경험의 중요한 요소로 간주하여 높은 순위를 부여합니다.
  2. 예측 가능한 크롤링:
    • 정적으로 생성된 페이지는 빌드 시점에 모든 HTML이 생성되므로, 검색 엔진이 페이지를 크롤링할 때 동적 콘텐츠 로딩 문제 없이 페이지 전체를 쉽게 인덱싱할 수 있습니다. 이는 JavaScript를 통해 동적으로 로드되는 콘텐츠보다 SEO에 더 유리합니다.
  3. 페이지 안정성:
    • 정적으로 생성된 페이지는 변경되지 않기 때문에, 검색 엔진이 크롤링할 때 일관된 콘텐츠를 확인할 수 있습니다. 이는 동적으로 생성되는 페이지에서 발생할 수 있는 다양한 변수와 변경 사항으로 인해 발생하는 크롤링 오류를 줄여줍니다.

 

 

2. 메타 태그 설정

 

메타 태그는 검색 엔진이 페이지의 내용을 이해하는 데 중요한 역할을 합니다. Next.js에서는 next/head를 사용하여 메타 태그를 쉽게 설정할 수 있습니다.

 

import Head from 'next/head';

const BlogPost = ({ post }) => {
  return (
    <>
      <Head>
        <title>{post.title} | My Blog</title>
        <meta name="description" content={post.excerpt} />
        <meta property="og:title" content={post.title} />
        <meta property="og:description" content={post.excerpt} />
        <meta property="og:type" content="article" />
      </Head>
      <article>
        <h1>{post.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: post.content }} />
      </article>
    </>
  );
};

 

이 코드는 페이지의 제목과 설명을 메타 태그로 설정하여 검색 엔진이 페이지의 내용을 더 잘 이해하도록 돕습니다.

소스코드에 보시면 메타 태그 엘리먼트에 og라는 속성 값이 눈에 띄는데 이것은 무엇을 의미할까요?

메타 태그 설정에서 og는 "Open Graph"의 약자입니다. Open Graph 프로토콜은 페이스북이 개발한 표준으로, 웹 페이지가 소셜 미디어 플랫폼에서 어떻게 공유되고 표시되는지를 제어합니다. Open Graph 메타 태그를 사용하면 웹 페이지의 미리보기, 이미지, 제목, 설명 등을 지정할 수 있어 소셜 미디어에서의 콘텐츠 표현을 개선할 수 있습니다. 주요한 오픈 그래프 메타 태그를 아래에 기록해두겠습니다.

og:title:

  • 웹 페이지의 제목을 정의합니다. 이 제목은 페이지가 소셜 미디어에서 공유될 때 표시됩니다.

og:description:

  • 웹 페이지의 간단한 설명을 정의합니다. 이 설명은 페이지가 소셜 미디어에서 공유될 때 표시됩니다.

og:image:

  • 소셜 미디어에서 공유될 때 표시할 이미지 URL을 정의합니다. 이미지가 시각적으로 매력적이면 더 많은 클릭과 공유를 유도할 수 있습니다.

og:url:

  • 웹 페이지의 URL을 정의합니다. 이 URL은 페이지가 소셜 미디어에서 공유될 때 표시됩니다.

og:type:

  • 웹 페이지의 타입을 정의합니다. 예를 들어, 웹 페이지가 기사라면 article로 설정합니다.

Open Graph 메타 태그는 웹 페이지가 소셜 미디어에서 어떻게 보이는지를 제어하는 중요한 도구입니다. Next.js 프로젝트에 이러한 태그를 적절히 설정하여, 콘텐츠가 소셜 미디어에서 더 잘 공유되고 더 많은 트래픽을 유도할 수 있도록 해봅시다..

 

 

 

3. 정적 사이트 생성(SSG) 및 서버 사이드 렌더링(SSR)

Next.js는 SSG와 SSR을 모두 지원하여 SEO 최적화에 유리합니다. SSG는 빌드 타임에 HTML 파일을 생성하여 빠른 로딩 속도를 제공하고, SSR은 서버에서 페이지를 렌더링하여 검색 엔진이 페이지 내용을 쉽게 크롤링할 수 있도록 합니다.

정적 사이트 생성

export async function getStaticProps() {
  const posts = getAllPosts();
  return {
    props: { posts },
  };
}

const TaehoBlog = ({ posts }) => {
  return (
    <div>
      <h1>TaehoBlog</h1>
      <ul>
        {posts.map(post => (
          <li key={post.slug}>
            <a href={`/blog/${post.slug}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TaehoBlog;

서버 사이드 렌더링

export async function getServerSideProps() {
  const posts = await fetchPostsFromAPI();
  return {
    props: { posts },
  };
}

const TaehoBlog = ({ posts }) => {
  return (
    <div>
      <h1>Blog</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <a href={`/blog/${post.id}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TaehoBlog;

 

 

4. 사이트맵 및 robots.txt 설정

 

사이트맵은 검색 엔진이 사이트의 구조를 이해하고 모든 페이지를 크롤링하는 데 도움을 줍니다. next-sitemap 패키지를 사용하면 쉽게 사이트맵을 생성할 수 있습니다.

npm install next-sitemap
// next-sitemap.config.js
module.exports = {
  siteUrl: 'https://taeho.com',
  generateRobotsTxt: true,
};


// next.config.js
module.exports = {
  // ...기타 설정들
  generateRobotsTxt: true,
};

이 설정은 사이트맵과 robots.txt 파일을 자동으로 생성하여 검색 엔진이 사이트를 더 잘 크롤링하도록 합니다.

 

5. Light house로 성능 검증해보기

 

모든 설정을 마쳤다면, Light house를 이용해 서비스의 검색엔진 최적화 성능을 확인해보면 좋을 것 같아요

Lighthouse는 웹 페이지의 성능, 접근성, SEO, PWA(Progressive Web App) 등을 분석하여 개선할 수 있는 포인트를 제공하는 오픈 소스 도구입니다. Google Chrome의 개발자 도구에 내장되어 있으며, 별도의 CLI(Command Line Interface) 또는 웹 버전으로도 사용할 수 있습니다. 4장에서는 Lighthouse를 사용하여 검색 엔진 최적화(SEO) 기능을 활용하는 방법에 대해 설명하겠습니다.

Chrome 브라우저에서 Lighthouse 실행

  1. 페이지 로드: SEO 분석을 원하는 웹 페이지를 Chrome 브라우저에서 엽니다.
  2. 개발자 도구 열기: Ctrl + Shift + I(Windows) 또는 Cmd + Option + I(Mac)를 눌러 개발자 도구를 엽니다.
  3. Lighthouse 탭 선택: 개발자 도구에서 Lighthouse 탭을 선택합니다.
  4. SEO 선택: Lighthouse 탭에서 원하는 카테고리를 선택합니다. SEO 분석을 위해서는 Search Engine Optimization 옵션을 선택합니다.
  5. 분석 시작: Generate report 버튼을 클릭하여 분석을 시작합니다. Lighthouse는 페이지를 분석하고 보고서를 생성합니다.

SEO를 적용하기 전과 후를 한번 점검해보시기 바랍니다.

 

포스팅을 마치며...

Next.js를 사용하여 SEO를 최적화하는 방법을 살펴보았습니다. 동적 라우팅, 메타 태그 설정, 정적 사이트 생성, 서버 사이드 렌더링, 사이트맵 설정 등을 통해 검색 엔진에 최적화된 웹사이트를 구축할 수 있습니다. 개인적으로 공부하면서 느낀 점은 (1) 메타태그 잘쓰기, (2) 검색엔진을 위한 SSR의 적절한 활용, (3) robots.txt와 sitemap.xml 잘 작성해주기.. 가 핵심인 것 같아요. 아, 그리고 팁을 드리자면 요즘의 검색엔진은 메타태그의 keyword에 대해서는 그렇게 높은 점수를 주지 않는 것이 현실인 것 같다고 하네요(스팸등의 이유로...) 아무쪼록 Next.js의 강력한 기능을 활용하여 더 많은 트래픽과 더 높은 검색 엔진 순위를 달성해보세요. :)

+ Recent posts