검색 엔진 최적화. 네이버나 구글같은 검색 엔진에 뭔가를 검색했을 때, 내가 만든 사이트가 검색 결과에 더 잘 보이게 하기 위한 과정.
검색을 하면,
검색 엔진이 내 사이트 내용물(meta tag나 html 등)을 훑어가고 내용물에 특정한 인덱스 같은 것을 만들어 검색 결과에 보여준다.
검색 엔진 최적화는 검색엔진이 내 사이트를 크롤링할 때 정보를 더 잘 가져갈 수 있도록 도와주는 과정이기도 하다.
React는 HTML 파일이 딱 1개 뿐이고, 렌더링이 되기 전까지(javascript를 실행하기 전까지)는 껍데기 html만 있어, 기본적으로는 검색 엔진에 올라가기 어렵다. 그래서 react에서도 검색 엔진이 긁어갈 수 있도록 미리 html 파일 내용을 보여줄 필요가 있다.
빌드할 때 미리 특정 페이지를 렌더링해서 html 파일을 만들어 두는 것
SPA는 사용자가 실제 콘텐츠를 보기전에 사이트를 구성하는 javascript 번들이 다운로드를 완료할 때까지 기다려야하고, 번들이 클수록 더 오래 기다려야한다.
SSR은 브라우저에서 부팅하는 대신 서버에서 응용 프로그램을 렌더링하는 방식이다. 각 페이지/경로 전환과 함게 완전한 HTML이 서버에서 생성되고 브라우저로 전송되므로 첫 번째 페인트 시간(FCP)이 줄어들지만, 첫 번째 바이트까지 걸리는 시간(TTFB)은 느려질 수 있다.
pre-rendering은 ssr보다 덜 복잡하지만 응용 프로그램에서 첫 번째 페인트 시간을 개선하는 방법을 제공하는 별도의 기술이다.
빌드할 때 html 파일을 만들어두기 때문에 검색 엔진이 크롤링하러 사이트에 들어왔을 때, 빈 껍데기 html 파일 대신 내용물을 가져갈 수 있다.
하지만, pre-rendering 첫 번째 요청에서 그대로 전송되는 정적인 파일만을 가져오는 것이므로, 서버측 데이터는 없다.
패키지 설치
yarn add --dev react-snap
package.jso에 script 추가
postbuild:'react-snap'
index.js 수정
import { hydrate, render } from "react-dom";
const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
hydrate(<App />, rootElement);
} else {
render(<App />, rootElement);
}
"reactSnap":{
"include":["/two","/"]
},
빌드
yarn build
실행해서 확인하기
// server.json 추가
{
"rewrites": [
{ "source": "/", "destination": "/200.html" },
{ "source": "/two", "destination": "/two/index.html" }
]
}
$ yarn global add serve
serve - c serve.json build
localhost:5000에서 build가 잘 되었는지 확인할 수 있음!
SSR은 서버에서 사용자에게 보여줄 페이지에 필요한 데이터를 가지고 와 미리 채운 다음에 페이지를 로드하는 방식
서버에서 페이지를 구성하기 때문에 클라이언트에서 구성하는 CSR보다 페이지를 구성하는 속도는 늦어지지만 전체적으로 사용자에게 보여주는 콘텐츠 구성이 완료되는 시점이 빨라진다.
// configureStore - next js + redux 사용을 위한 설정
import {createWrapper} from 'next-redux-wrapper'
import { applyMiddleware, compose, createStore } from 'redux';
import reducer from '../reducers'
import rootSaga from '../sagas'
import {composeWithDevTools} from 'redux-devtools-extension'
import createSagaMiddleware from 'redux-saga';
const configureStore = ()=>{
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
const enhancer = process.env.NODE_ENV === 'production'
? compose(applyMiddleware(...middlewares))
: composeWithDevTools(
applyMiddleware(...middlewares),
);
const store = createStore(reducer, enhancer)
store.sagaTask = sagaMiddleware.run(rootSaga)
return store
}
const wrapper = createWrapper(configureStore,{
debug:process.env.NODE_ENV === 'development',
})
export default wrapper ;
// _app.js
import wrapper from '../store/configureStore';
const App = ({ Component })=>{
return(
<>
<Head>
<title>NodeBird</title>
</Head>
<Component />
</>
)
};
App.Proptypes = {
Component:Proptypes.elementType.isRequired
}
export default wrapper.withRedux(App); // nextjs-redux
// index.js
import wrapper from "../store/configureStore";
import {END} from 'redux-saga';
const Home () => {
...
}
export const getServerSideProps = wrapper.getServerSideProps((context)=>{
//로그인 쿠키공유
const cookie = context.req? context.req.headers.cookie : '';
axios.defaults.headers.Cookie = '';
if(context.req && cookie){
axios.defaults.headers.Cookie = cookie;
}
axios.defaults.headers.Cookie = cookie;
// 데이터 요청
context.store.dispatch({
type:LOAD_USER_REQUEST
});
context.store.dispatch({
type:LOAD_POST_REQUEST
});
context.store.dispatch(END);
await context.store.sagaTask.toPromise()
})
export default Home;
export const getStaticProps = wrapper.getStaticProps(async (context) => {
console.log('getStaticProps');
context.store.dispatch({
type: LOAD_USER_REQUEST,
data: 1,
});
context.store.dispatch(END);
await context.store.sagaTask.toPromise();
});
데이터없이 화면만 받고 데이터 로딩창을 띄우면서 백엔드에 데이터를 요청해 화면을 렌더링
reference