Next.js
를 알게 된지 얼마 안됐을 때, 예상보다 빨리 문제가 생겼는데...🥲
그것은 바로 styled-components로 작업을 하던 중 처음 브라우저가 로딩 되었을 때는 스타일이 적용됐지만 새로고침을 하면 스타일이 없어지는 현상이었다.
새로고침 전(스타일이 적용된 모습)
새로고침 후(스타일이 적용되지 않은 모습)
결론적으로 이런 문제가 발생한 이유는 Next.js
의 Pre-Rendering의 특징 때문이었는데 이것에 대해 좀 더 자세히 알아보고 해결 방법에 대해서 정리 해 보도록 하자!!
Pre-Rendering
은 Next.js
에서 가장 중요한 개념이라고 할 수 있다.
기본적으로 Next.js
는 모든 페이지를 pre-rendering
한다.
이 말이 무슨 말이냐 하면, pre-rendering
이란 각 페이지에 대한 HTML을 미리 생성한다는 것을 의미한다.
Pre-Rendering
이 각 페이지에 대한 HTML을 미리 생성한다는 것을 알았다면 왜 HTML을 미리 만들어 주는 것인지에 대한 의문이 생길 수 있다. 그렇다면 Pre-Rendering
의 유무 차이에 대해서 한번 살펴보자.
Pre-Rendering 사용하지 않을 때 (React.js 만 사용)
- JS 전체가 로드되어야 하기 때문에 최초 Load가 되었을 때는 사용자에게 화면이 보여지지 않게 된다.
- 만약 전체 페이지가 로드되기 전까지 빈 화면을 봐야한다면 사용자가 불편함을 느낄 수 있다.
Pre-Rendering 사용했을 때 (Next.js 사용)
- 최초 Load가 되었을 때 JS동작만 없는 HTML을 먼저 화면에 보여준다.
- 만약 사용자에 따라 JS를 꺼놓거나, 브라우저 버전이 낮아서 리액트를 실행시킬 수 없는 상황이라해도 Next.js에서 미리 만들어둔 HTML화면을 볼 수 있게 된다.
- 하지만, 아직 JS파일이 로드되기 전이기 때문에
Link
와 같은 컴포넌트는 동작하지 않는다.
정리하자면, SSR로 생성된 HTML은 해당 페이지에 필요한 최소한의 JS코드로 되어있으며, JS파일을 서버로부터 모두 받아오게 되면 페이지를 완벽히 인터렉티브하게 만들게 되며, 이 과정을 hydration 이라고 한다
hydration 이란 위에서도 말했듯이, HTML이 가지고 있던 JS가 실행되면서 완전한 페이지가 되는 과정 이라고 생각하면 된다.
좀 더 자세하게 풀어서 이야기 하자면,
이 과정을 hydration이라고 한다.
서론이 길었는데, 나에게 발생 했던 문제는 styled-component로 작업한 스타일이 새로고침을 하면 사라지는 문제였다.
이런 문제가 발생한 이유는 Next.js
가 Pre-Rendering
을 하면서 HTML을 로드하고 hydration하는과정에서 다른 파일들을 로드하기 때문에 발생했다.
우리가 SSG를 이용해 static한 HTML파일을 생성한 뒤 SSR환경을 구축했다 하더라도, JS에 의해 동적으로 CSS가 생성되는 CSS-in-JS 방식인 styled-component는 SSG과정에서 생성되는 HTML에 코드가 함께 build 되지 않는다.
여기서 잠깐✋🏻
CSR vs SSR vs SSG
- CSR(Client Side Rendring) : 화면을 클라이언트단에서 바꾸는 기법
- SSR(Server Side Rendring) : 화면을 서버단에서 전송해주는 기법
- SSG(Server Side Generation) : 화면을 서버에서 미리 만들어 전송해주는 기법
👀자세한 설명
Next.js
공식 문서 에 나와있는 renderPage
함수로 해결 할 수 있었다.
_document.js
파일 생성pages
디렉토리에 _document.js
파일을 생성한다.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => App,
enhanceComponent: (Component) => Component,
})
const initialProps = await Document.getInitialProps(ctx)
return initialProps
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
ServerStyleSheet
함수를 importimport Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components';
class MyDocument extends Document {
... 생략
renderPage
함수 조건 추가import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const originalRenderPage = ctx.renderPage
const sheet = new ServerStyleSheet();
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => App,
enhanceComponent: (Component) => Component,
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: [
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>,
],
}
}
...생략
위의 해결 방법을 정리하자면,
pages
디렉토리에 HTML custom을 설정할 수 있는 _document.js
파일을 생성한다.ServerStyleSheet
함수를 styled-components에서 impot하여 Global하게 설정한다.renderPage
함수로 렌더링 조건을 Customizing 한다.마무리
해결방안을 찾다가 pre-rendering에 대해서 더 자세히 알아보고 정리도 해봤는데,
next.js의 중요한 개념이라는 것을 공부할 수 록 깨닫게 되어서 나중에 따로 이 부분에 대해서 더 공부를 해봐야겠다.
오... HoneyTip 감사합니다...