React는 기본적으로 CSR(Client Side Rendering) Framework이다. 서버에서 기본 Html만 받아 Client에서 데이터를 구성하여 화면에 렌더링한다. 그래서 React Application 들은 SPA(Single Page Application)이다. 하나의 페이지를 서버에서 받아서 Client에서 화면들을 구성하기 때문이다.
CSR은 이용자와의 Interaction에 유리하다. 좋아요 버튼을 누를 때, 화면이 다시 렌더링 된다면 이용자들은 불편함을 느낄 것이다. 이용자의 액션에 따라 필요한 부분만 렌더링할 때 장점이 있다. 하지만 사이트의 데이터와 컨텐츠가 커지면 최초에 리소스들을 모두 가져와야 하기 때문에 페이지 로딩 속도가 낮아질 수 있다.
그렇다면 Client만 담당하던 데이터 및 연산 작업을 서버와 나누어서 할 수 없을까?
SSR(Server Side Rendering)을 접목하면 가능하다. 최초에 서버에서 기본 Html만 제공하는 것이 아니라 데이터 연산 작업도 함께 하여 Client에게 보내주고, 그 후에 작업들은 CSR로 처리한다면 CSR로만 했을 때보다 속도를 더 빠르게 할 수 있다. 이러한 점을 도와주는 Framework가 NEXT js이다.
NEXT js homepage에서 소개하는 SSR의 첫 렌더링 속도 비교
두번째 장점은 SSR로 SEO 최적화를 할 수 있다는 점이다. 검색엔진의 크롤링 봇이 잘 되어 있어서 요새는 잘 된다고는 하지만 SPA로 된 사이트는 기본적인 크롤링 방식으로는 사이트 컨텐츠들을 수집할 수 없다. 빈 HTML만 있을 것이기 때문이다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Silence Of Lambs</title>
<link href="/static/css/main.90816efb.chunk.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
</body>
</html>
위는 SPA로 만들어진 개인 프로젝트 사이트에 GET요청을 보낸 결과물이다. 아무 컨텐츠가 없이 비어있는 상태이다. 이렇게 템플릿을 받아서 데이터를 채우는 것은 Client가 하게 된다.
반대로 SSR을 하게되면 Contents가 채워진 상태로 받을 수 있다. 다음은 나의 벨로그에 GET 요청을 보낸 결과물이다.
<body>
<div id="root">
<div class="__jazzbar false false" style="width:0%"></div>
<div class="sc-ckVGcZ foYhJp sc-jKJlTe hvvFoU">
<div style="margin-top:0" data-testid="Header" class="sc-kpOJdX hTMHXK">
</div>
<div class="sc-cHGsZl gWOoyb sc-TFwJa gzYRmt">
<div class="sc-epnACN ivjglg sc-gqPbQI hNTnhz">
<div class="sc-iQNlJl bpvKqr"><a
href="/@cyranocoding">
<div class="sc-bsbRJL gMakhm">
<div class="name"><a href="/@cyranocoding">박한준</a></div>
<div class="description">개발자로 한걸음 한걸음 가고 있어요.</div>
</div>
</div>
<div class="sc-hZSUBg kanTEs"></div>
<div class="sc-cMhqgX YyOhv"></div>
</div>
<div class="sc-bHwgHz bbcNuM"></div>
<div class="sc-bMVAic gndoLp">
<div class="sc-bAeIUo cRlnLr"><a aria-current="page" class="sc-iujRgT gSdDxl active"
href="/@cyranocoding">글</a><a class="sc-iujRgT gSdDxl"
href="/@cyranocoding/series">시리즈</a><a class="sc-iujRgT gSdDxl"
href="/@cyranocoding/about">소개</a>
<div style="left:0%" class="sc-GMQeP jeKThE"></div>
</div>
</div>
<div>
너무 길어서 줄였다. 위와 같이 컨텐츠들을 확인할 수 있다.
$ npm install --save react react-dom next
NEXT는 React 기반으로 이용되는 프레임워크이므로 react와 같이 설치를 한다.
그리고 package.json
의 scripts 부분을 다음과 같이 수정을 한다.
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
`npm run dev` 명령어를 사용하여 next 서버를 실행할 수 있다
NEXT structure에서는 url = Pages 폴더 구조이다. 예를들어 루트페이지는 /
url을 가지므로 pages/index.js
파일을 읽어온다. /user
라는 pathname으로 접속을 하게 되면 pages/user.js
파일을 읽어온다. url 구조를 pages
폴더에 직관적으로 배치하면 된다.
기본적으로 pages 폴더 내부가 실행이 되지만, 그 속의 컨텐츠들은 components에서 대부분 관리하는 것이 좋다. 공통으로 사용하는 컴포넌트들이 있을 수도 있고, 나중에 getInitialProps
를 붙여서 관리하게 되면 자칫 pages 내부가 복잡해질 수 있기 때문이다. 나 같은 경우는 pages 폴더에 대응하는 routes 폴더를 만들고 기초 작업을 한 뒤 components에 분리하였다. 다음과 같은 구조로..
project
|
|---- pages
| |
| |---- index.js // 루트페이지 (/)
| |---- user.js // 유저페이지(/user)
|
|----- routes
| |---- index.js // 루트페이지 (pages/index.js)
| |---- user
| |
| |----index.js // 유저페이지 (pages/user.js)
|
|----- components
이렇게 구성하여 pages/index.js
는
import Index from 'routes';
const Index = () => {
return <Index/>
};
pages/user.js
는
import User from 'routes/user';
const User = () => {
return <User/>
};
이렇게 코드를 작성하고 실질적인 컨텐츠들은 routes에서 관리한다.
위와 같이 라우팅을 구성하고 나면 그 다음은 리액트와 동일하다. 실제로도 SPA처럼 작동하는 코드를 만들 수 있다. route가 변했을 때 NEXT는 다른 page를 가지고 온다. 따라서 한 페이지 내에서 변화하는 이펙트를 주고 싶다면 page 변환을 하지 않고 한 페이지 내에서 작동하도록 해야 한다. Url path가 변하면 SSR이 작동하면서 새로운 페이지의 컨텐츠들을 가지고 오고, 브라우저는 새로고침하는 것 같은 이펙트를 주게 된다.
next 서버를 실행하면 최초에 pages/_app.js
가 실행된다. 그렇기 때문에 store관리는 여기서 진행이 된다. (context나 redux를 사용한다면 Provider를 여기에 붙이면 된다. 또한 공통되는 레이아웃이 있다면 이 곳에 적용을 하면 된다.) 대강 아래와 같은 코드가 될 것이다.
export default class MyApp extends App {
state = {
user: null,
}
render() {
const { user } = this.state;
const { Component } = this.props;
return (
<Provider
user = {user}
>
<Layout>
<Component {...props} />
</Layout>
</Provider>
);
}
}
_document.js
는 SSR로 데이터를 보내기 위한 html 포맷을 결정한다. 보통 다음과 같은 방식으로 작성한다.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
이젠 기초 작업은 끝났다. 이제 하나씩 컴포넌트들을 채우고 route를 구성하면 된다.
개인적으로 next를 사용했을 때 편리했던 점은 다음과 같다.
그 외에도 SEO를 간편하게 적용할 수 있다는 점. SSR로 브라우저의 기능들을 온전히 사용할 수 있다는 점 등이 있다.
NEXT와 관련해서는 몇 번 더 포스팅할 기회가 있을 것 같다. 잠깐 언급했던 getInitialProps
나 SEO를 적용하는 방법 등 스스로 연구해서 알아낸 것들이 몇가지 있는데 공유하면 좋을 것 같아서.
기존 리액트와 개념 자체가 다르기 때문에 처음에 조금 어렵게 느껴졌었는데 결국 리액트를 사용하기 위한 한 방법 중에 하나라고 생각이 들었다. SSR이 어려운 리액트를 SSR이 가능하도록 만드는, 그것도 매우 쉽게 만드는 프레임워크라고 생각한다.
제목이.. Nest -> Next로 되야할듯 합니다..