Next.js로 사이드 프로젝트를 시작하면서 공식문서에서 시키는대로 create-next-app
을 사용해서 프로젝트를 init했다. 13으로 버전업 되면서 차이가 생긴건지 다양한 옵션들이 생겼다.
eslint를 사용할지 말지, src
directory를 추가할지 말지, app
디렉토리를 추가할지 말지 고를 수 있다. experimental이라길래 app
은 제끼고, 나머지를 받아들여보았다.
자동으로 생성된 디렉토리를 확인하면서 파일을 보는데 index.tsx
와 _document.tsx
모두 Head
라는 컴포넌트가 있었다.
// index.tsx
import Head from "next/head";
export default function Home() {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
...
</>
);
}
// _document.tsx
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
...
</Html>
);
}
왜 굳이 import 경로가 다른 Head
가 두개나 필요할까?
최근에 추천사를 작성하게 되면서 실전에서 바로쓰는 Next.js라는 책을 받았는데. 이 책에는
next/head
에 있는 Head
는 오픈그래프 속성, 메타데이터를 정의할 때 사용하고,next/document
에 있는 Head
는 웹사이트의 모든 페이지에 공통으로 사용되는 코드가 있을 때 사용할 수 있고, 메타데이터를 정의하는 태그와 다르다고 설명되어있다.1번은 알겠는데 2번은 잘 모르겠으니, 소스코드를 열어보려고 한다.
next/head
의 Head
는 src/shared/lib/head.tsx에서 온다. 주석에는 <head>
에 element를 inject하기 위해 사용된다고 설명되어있다.
// next/head
function Head({ children }: { children: React.ReactNode }) {
const ampState = useContext(AmpStateContext);
const headManager = useContext(HeadManagerContext);
return (
<Effect
reduceComponentsToState={reduceComponents}
headManager={headManager}
inAmpMode={isInAmpMode(ampState)}
>
{children}
</Effect>
);
}
ReactNode
들을 children
으로 받아오고 Effect
라는 태그로 감싸준다. Effect
컴포넌트는 useLayoutEffect
hook을 사용해서 headManager
의 updateHead
라는 method를 실행시킨다. 이를 통해index.tsx
에 있던 아래 태그들을 브라우저가 화면을 업데이트 시킬 때마다 defaultHead
로 선언된 아래 두개의 meta
tag와
export function defaultHead(inAmpMode = false): JSX.Element[] {
const head = [<meta charSet="utf-8" />];
if (!inAmpMode) {
head.push(<meta name="viewport" content="width=device-width" />);
}
return head;
}
next/head
에서 import한 Head
의 children tag들을 <head>
안에 children으로 넣어준다.
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
next/document
의 Head
는 src/pages/_document.tsx에서 온다. 주석을 보니 next/head
와 가장 큰 차이는 next/document
는 서버컴포넌트라는 것이다. 따라서 next/head
처럼 useLayoutEffect
등의 방식으로 헤더를 업데이트 할 수 없다. 따라서 클라이언트 사이드와 관계없는 tag들만
넣어주어야 한다.
따라서 화면에 해당하는 값을 보여줘야하는 <title>
과 같은 값을 next/document
의 Head
에 넣어주면 경고가 발생한다.
스크린샷에 있는 사이트에서 확인하면 아래 에러메세지를 확인할 수 있다.
Adding
<title>
inpages/_document.js
will lead to unexpected results withnext/head
since_document.js
is only rendered on the initial pre-render.
같인 이유로 <title>
외에 화면의 크기와 관련있는 viewport
option을 넣어도 경고메세지가 발생한다.
next/document
의 Head
의 소스코드는, create-next-app
으로 프로젝트를 처음 생성할 때 index.tsx
에 적용할 style속성과 script태그들을 가지고있다. <title>
관련 경고메세지에서 본 것처럼 해당 Head
는 최초 pre-render에 적용되기 때문에, SEO에 사용될만한 <meta>
tag들을 열심히 심어두면 되겠다.
또 하나의 차이는 next/document
의 Head
를 _document.tsx
외 다른 파일에 넣으면 에러가 발생한다. 이것도 주의해야한다.
앞으로 계속 세팅을 하면서 궁금한 부분이나, 새롭거나 신기한 것을 발견하면 지속적으로 포스팅 해보도록 하겠다.