본문 바로가기

project

[React] 인스타 클론코딩 기록 (4) - 서버사이드렌더링과 쿠키설정

앞서 next가 서버사이드렌더링을 편하게 해주기 때문에 설치해 사용했습니다.

 

클라이언트사이드렌더링은

브라우저 -> 프론트서버 -> (!) 브라우저 -> 프론트서버 (!) -> 백서버 -> 프론트서버 -> 브라우저

순서로 데이터를 요청하고 받아오게 되면서 (!) 사이의 단계일 때, 모양은 있지만 데이터가 없는 화면이 보여지게 됩니다.

 

 

서버사이드렌더링은 웹페이지를 리프레쉬 하면 브라우저에서 백엔드 서버로 요청을 보내 데이터를 받아오게 됩니다.

브라우저 -> 프론트서버 -> 백서버 -> 프론트서버 -> 브라우저

요청 한번으로 초기 로딩속도가 좀 빨라지는 듯한 느낌을 줍니다.

 

 

pages 의 index 페이지의 서버사이드렌더링을 위해 초기에 로딩되어야 하는 액션을 파악하고, 

const Home = () => {
    const dispatch = useDispatch();
    const { me } = useSelector((state) => state.user);
    const { mainPosts, hasMorePosts, loadPostsLoading  } = useSelector((state) => state.post);
	
    useEffect(() => {
    	dispatch({
          type: LOAD_MY_INFO_REQUEST,
      })
      dispatch({
          type: LOAD_POSTS_REQUEST,
      });
    }, []);
    
    useEffect(() => {
        function onScroll () {
            //console.log(window.scrollY, document.documentElement.clientHeight, document.documentElement.scrollHeight);
            if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) {
                if (hasMorePosts && !loadPostsLoading) {
                    const lastId = mainPosts[mainPosts.length - 1]?.id;
                    dispatch({
                        type: LOAD_POSTS_REQUEST,
                        lastId,
                    });
                }
            }
        }
        window.addEventListener('scroll', onScroll);
        return () => {
            window.removeEventListener('scroll', onScroll)
        }
    }, [hasMorePosts, loadPostsLoading, mainPosts]);
    
    return (
        <AppLayout>
            {me && <PostForm />}
            {mainPosts.map((post) => <PostCard key={post.id} post={post} />)}
        </AppLayout>
    );
}

export default Home;

위에서는 :6 줄의 useEffect 부분이 데이터를 불러오는 디스패치

 

저 useEffect 부분을 Home 아래에 getServerSideProps 로 담아줍니다.

저기서 wrapper는 configureStore.js 에서 만든 그 wrapper 입니다.

export const getServerSideProps = wrapper.getServerSideProps((context) => {
    context.store.dispatch({
        type: LOAD_MY_INFO_REQUEST,
    })
    context.store.dispatch({
        type: LOAD_POSTS_REQUEST,
    });
});

그럼 이 친구들을 reducer/index 의 HYRATE 에서 받아서 실행을 해주는데 여긴 아직 데이터가 없습니다.

또 redux dev tools 로 보면 Diff 탭에서 user 와 post 정보가 index 안에 들어가 있어서 이것도 빼주도록 해야겠습니다. 

root reducer 의 구조를 재정비 해보겠습니다.

 

import { HYDRATE } from 'next-redux-wrapper';
import { combineReducers } from 'redux';

import  user from './user';
import post from './post';

const rootReducer = combineReducers({
    index: (state = {}, action) => {
        switch (action.type) {
            case HYDRATE:
                return { ...state, ...action.payload};
            default:
                return state;
        }
    },
    user,
    post, 
});

export default rootReducer;

기존의 root reducer 입니다.

 

import { HYDRATE } from 'next-redux-wrapper';
import { combineReducers } from 'redux';

import  user from './user';
import post from './post';

const rootReducer = ((state, action) => {
    switch (action.type) {
        case HYDRATE:
            console.log('HYDRATE', action);
            return action.payload;
        default: {
            const combineReducer = combineReducers({
                user,
                post, 
            });
            return combineReducer(state, action);
        }
    }
    
});

export default rootReducer;

조금 더 확장 가능하게 펼쳐주고 index를 빼 user, post로 덮을 수 있게 했습니다.

그러면 이제 프론트에서 보내는 요청이 보내집니다. 이제 요청을 해결시켜서 완료하면 됩니다.

다시 index로 돌아가...

import { END } from 'redux-saga';

임포트 해주고...

export const getServerSideProps = wrapper.getServerSideProps(async (context) => {
    context.store.dispatch({
        type: LOAD_MY_INFO_REQUEST,
    })
    context.store.dispatch({
        type: LOAD_POSTS_REQUEST,
    });
    context.store.dispatch(END);
    await context.store.sagaTask.toPromise();
});

아래 두 줄을 추가해 줍니다. next-redux-wrapper 사용...

저 store.sagaTask 도 마찬가지로 configureStore.js부터 온 친구입니다.

 

이렇게 해주면 request를 success로 바꿀때까지 기다려줍니다. 메인페이지 서버사이드렌더링 완성!

하지만 아직...

제 새로고침을 하면 로그인 정보를 가져와주지 못합니다... ㅡ아니이이~~ 해주면 됩니다 ㅠ

확인 먼저 해보겠습니다.

 

내 정보 불러오는 라우터에 콘솔을 찍어보니...

console.log('> load myinfo', headers)

헤더에 쿠키가 없자나?

이렇게 프론트는 로그인 되어 있다고 하는데 백에서는 로그인을 한 줄 모르는 사태가 나와버렸습니다.

 

 

클라이언트사이드렌더링은

브라우저 -> 백으로 데이터를 보낼 때, 브라우저가 쿠키를 담아서 보냅니다.

 

서버사이드렌더링은

프론트서버 -> 백서버로 데이터를 보내는데(위의 getServerSideProps) 여기서 (프론트)서버 -> (백)서버 사이의 쿠키는 자동으로 넘어가지 않습니다. 이걸 설정해주면 됩니다. 어떻게? axios에 직접 넣어서 전달해주겠습니다.

 

index 로 가서

import axios from 'axios';

axios 임포트 해주고

export const getServerSideProps = wrapper.getServerSideProps(async (context) => {
    const cookie = context.req ? context.req.headers.cookie : '';
    axios.defaults.headers.Cookie = '';
    if (context.req && cookie) {
        axios.defaults.headers.Cookie = cookie;
    }
    context.store.dispatch({
        type: LOAD_MY_INFO_REQUEST,
    })
    context.store.dispatch({
        type: LOAD_POSTS_REQUEST,
    });
    context.store.dispatch(END);
    await context.store.sagaTask.toPromise();
});
// 이 모양이 기본꼴
// getStaticProps도 마찬가지로 작성하지만 데이터가 고정되어있는? 잘 안바뀔? 페이지에 사용
export const getServerSideProps = wrapper.getServerSideProps(async (context) => {
    
    context.store.dispatch(END);
    await context.store.sagaTask.toPromise();
});

cookie 관련해 추가해 줍니다.

쿠키를 보내는 조건을 서버일 때와 쿠키가 존재할 때만 보내도록 처리합니다.

이렇게 안하면 프론트 서버에서 누군가의 쿠키가 공유돼서 타인이 그 아이디로 활동할 수 있어요

 

그러면 이제 라우터에 콘솔을 다시 보면

cookie가 들어있네요! connect.sid가 제 정보를 담은 쿠키입니다.

이제 서버사이드렌더링에서도 새로고침을 하면 제 로그인 정보도 풀리지 않고 잘 로딩이 됩니다!

 

서버사이드렌더링이 필요한 페이지 모두 적용해주도록 합니다.