지난 6월부터 지인의 요청을 받아 NextJS로 웹사이트를 구축하는 프로젝트를 진행했다.
요구 사항 기획, 디자인, 기능 개발까지 모든 부분을 담당해서 진행하다보니 예상보다 오랜 시간이 걸렸지만 그래도 마무리하게 되었다!
기능 구현까지 마무리하니 SEO를 진행하고 싶은 마음이 생겨 작업에 들어갔다. 그러나, 이 과정에서 프로젝트를 뜯어고칠 일이 생각보다 많이 발생했고 고생 끝에 완벽하지는 않아도 SEO를 진행할 수 있었다.
(구글, 네이버 검색 결과 반영은 시간이 지나고 확인해보자)
이번 글에서는 다음 내용들을 정리하고자 한다.
❓ 프로젝트의 어떤 문제점이 있었는가?
🎉 어떻게 뜯어 고쳤는가?
💡 이 과정에서 뭘 배웠나? 아직 남은 고민은?
use-client
?Next.js 13.4를 기점으로 NextJS의 모든 컴포넌트는 기본적으로 서버 컴포넌트가 되었다. 즉, 서버에서 렌더링과 API 호출 등이 이미 이루어지는 것이다.
이렇다 보니, 클라이언트 사이드에서의 로직들을 수행하기 위해서는 컴포넌트의 최상단에 use-client
를 사용하는 것이 강제시 되었다.
use-client
이 구문이 선언된 파일 내의 모든 모듈, 자식 컴포넌트까지 모두 클라이언트 번들로 취급된다.
styled-components
나는 프로젝트의 스타일링에 있어 styled-components
를 사용했다. 러닝 커브도 낮고 사용하기 쉬워서 사용하게 되었는데 이것이 프로젝트에서 가장 큰 골칫덩어리가 되어 버렸다.
styled-components
는 대표적인 CSS-in-JS 라이브러리이다. 즉, 개발자가 JS 코드 내에 직접 CSS를 작성함으로써 다양한 변수와 함수를 스타일 내에서 사용하거나, 프롭에 따라 동적으로 스타일링을 적용할 수 있는 기능들을 제공한다.
그러나 앞서 설명했듯, NextJS는 server component이다. 따라서, styled-components
를 사용하려면 use-client
의 사용이 필수적이게 된다.
프로젝트에는 모든 페이지에 적용되는 헤더가 있었기에 이를 적용하기 위해 app/layout.tsx
파일에 헤더 컴포넌트를 사용했고, 이를 위해 결국 app/layout.tsx
에 use-client
를 사용하게 되었다.
서버 사이드에서 동작하는 NextJS server component와 클라이언트 사이드에서 동작하는 styled-components의 웅장한 대결..
결국 내가 짠 코드는 NextJS를 활용하는 의미가 1도 없는 코드였던 것이다.
이를 해결하기 위해 NextJS에서 권장하는 대로 만들어놓은 글로벌 레지스트리 컴포넌트를 app/layout.tsx
에서 import함으로써 app/layout.tsx
에서 use-client
를 제공할 수 있었다.
1) next-auth
로그인 기능의 구현에 있어 NextAuth에서 제공하는 카카오 로그인을 활용했다.
카카오톡을 활용해 로그인되면 로그인 정보를 세션에 저장하는 방식인데, 이 과정에서 세션 정보를 컴포넌트에 제공하기 위해 sessionProvider를 사용했다.
문제는, 요 녀석이 client 사이드에서 동작하는 컴포넌트였다는 것.
때문에, authentication 관련된 컴포넌트도 모두 별도의 클라이언트 사이드 컴포넌트로 포함시키는 방식으로 구현했다.
2) 카카오 스크립트
카카오 톡을 활용한 로그인, 카카오톡 채팅 버튼을 구현하기 위해 카카오 JS SDK를 활용했다.
import Script from 'next/script'
export default function KakaoScript() {
function kakaoInit() {
window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_JS_KEY)
}
return (
<>
<Script
src="https://t1.kakaocdn.net/kakao_js_sdk/2.3.0/kakao.min.js"
crossOrigin='anonymous'
onLoad={kakaoInit}
></Script>
</>
)
}
문제는, 여기서 활용한 Script
컴포넌트의 onLoad
핸들러가 오직 클라이언트 컴포넌트에서만 동작한다는 것.. (참고 링크)
때문에, 카카오 스크립트도 별도의 클라이언트 사이드 컴포넌트로 포함하게 되었다.
리팩토링된 코드의 구조는 다음과 같다.
- 클라이언트 사이드 로직을 포함하는 별도의 클라이언트 컴포넌트 개발
- 이를 app/layout.tsx 컴포넌트에서 import해서 사용
레이아웃에서는 꼴보기 싫었던 use-client
를 제거했으나, 결국 다른 컴포넌트는 모두 클라이언트 번들로 취급이 되고 있는 상황이다.
처음 프로젝트를 개발할 때 생각없이 사용했던 styled-comopnents
와 next-auth
가 이렇게 날 괴롭힐 줄은 몰랐다..
NextJS가 RSC
를 공식적으로 채택했지만 아직 많은 라이브러리들이 이를 따라가지 못하고 있는 상황이다. 당장 styled-components
만 해도 지원한다는 소식이 없으니..
아직 CSS-in-JS 라이브러리를 NextJS와 함께 사용하는 것은 시기상조인 듯 싶다.
next-auth 의 경우 공식 문서에 따르면 서버 사이드의 경우 다른 api를 사용할 것을 권장하고 있다.
해서, 다음 작업으로는 스타일링 도구를 교체하는 작업(얼마나 걸릴지..)과 authentication provider를 서버 사이드로 바꾸는 작업을 진행해볼 예정이다.
이 글을 쓰게 된 결정적인 계기이다.
SEO를 진행하려면 페이지의 title, description, openGraph 태그 등을 설정해야 하는데 이 속성들은 모두 서버 사이드에서 접근할 수 있어야 한다.
즉, 기존 코드처럼 app/layout.tsx
에 use-client
가 적용되어 있었다면 아무리 meta
태그를 추가해도 말짱 도루묵인 상황..
문제 1 을 해결하면서 use-client
를 제거했기에 이제야 비로소 metadata를 추가할 수 있게 되었다.
NextJS에서 페이지에 metadata를 추가하는 방식은 다음과 같다.
(물론 <head>
내에 <meta>
태그를 추가해도 되지만.. 코드가 안 이쁘니까)
정적인 metadata
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '...',
description: '...',
}
export default function Page() {}
metadata를 추가하고자 하는 layout
, 또는 page
에 위와 같이 추가하고 싶은 데이터를 추가하면 된다.
전체 페이지에 공통된 metadata를 적용하기 위해 위 코드를 app/layout.tsx
에 적용했다.
SEO를 위해 sitemap과 robots를 작업하고 나니 더 예쁜 검색 결과를 확인할 수 있게 sitemap에 추가한 페이지에 대해서는 metadata를 수정하고 싶어졌다. 수정이 필요한 metadata는 다음과 같다.
title
description
og:title
og:description
이를 위해 주어진 정보로 특정 페이지의 metadata를 만드는 함수를 구현했다.
❓ layout과 page 둘 다 metadata를 적용하면 어떤 게 적용이 될까?
💡 정해진 순서에 따라 적용된다. 즉, 가장 마지막(여기서는 page)에 적용된 metadata가 적용된다.
// lib/utils/metadata.tsx
import { IMetadata } from "@/types/types";
import { METADATA } from "../constants/constatns";
export function genPageMetadata({ title, description, openGraph }: IMetadata) {
// 기본 metadata. layout에 적용된 metadata와 같다.
const baseOpenGraph = METADATA.home.openGraph
return {
title: title || METADATA.home.title,
description: description || METADATA.home.description,
openGraph: {
...baseOpenGraph,
title: openGraph?.title || METADATA.home.openGraph.title,
description: openGraph?.description || METADATA.home.openGraph.description,
}
}
}
metadata를 수정하기 위해서 위 함수를 페이지 단에서 아래와 같이 활용한다.
// page.tsx
import { Metadata } from 'next'
import { genPageMetadata } from '@/lib/utils/metadata'
import { METADATA } from '@/lib/constants/constatns'
export const metadata: Metadata = genPageMetadata(METADATA.introMap)
이렇게 하면 특정 페이지에는 내가 원하는 metadata가 적용이 된 것을 확인할 수 있다.
첫번째 metadata는 메인 페이지, 두번째 metadata는 지도 페이지이다.
원하는 metadata는 추가했으나, 공유 디버거로 테스트를 해보면 결국 app/layout.tsx
에 적용된 metadata만 출력된다.
원인은 결국, 1번 문제를 해결하면서 모든 컴포넌트가 클라이언트 번들로 취급되고 있기에 검색 봇이 기본 metadata만 크롤링하고 있는 것이다.
클라이언트 사이드에 영원히 갇혀버리게 된 내 코드들.. 하루빨리 이 문제들을 해결하고 싶다
단순히 metadata만 추가해보려다가 생각보다 깊은 내용들을 공부하게 되었다.
NextJS는 가장 인기 많은 프레임워크이기도 하지만 동시에 개발 방향에 있어 시끌벅적한 프레임워크이다. 내가 프로젝트를 진행하면서 겪은 서버 컴포넌트의 채택으로 인한 부수적인 라이브러리들의 활용 문제도 그 중 하나일 것이다. (시간을 내서 Why I Won't Use Next.js를 읽어봐야겠다.)
그러나 잘 활용만 하면 편리하게 활용할 수 있는 프레임워크임은 분명하다. 좀 더 공부하고 단순히 프레임워크를 사용하는 프레임워커가 아니라 고민하는 엔지니어로 거듭나야겠다.
참고 문서
제가 겪은문제를 오롯이 겪고계시네요... 저도 nextjs 12 템플릿을 다운받았는데 다 styledComponent로 작성되어서... 13버젼에서는 거의 사용하기 힘든수준입니다 ㅠㅠ layout.tsx에서 'use client'를 선언하니 metaData도 안먹고... 힘든길이네요