본문 바로가기
공부/프로그래밍

[react, redux, next, typescript] 프로젝트 만들때 최소 설정하는 것 정리

by demonic_ 2021. 3. 22.
반응형

프로젝트 만들때마다 세팅하는게 귀찮아(...) 자주 쓰는것들 위주로 구성하여 기본설정 한다.

사용하지 않은 것은 직접 빼서 커스텀 하시길.

 

여기서는 크게 3가지를 설명한다

 

1. axios 기본설정

2. ui 적용(material-ui, styled-component 사용)

3. cookie를 이용한 authorization 등록 & SSR에서 자동 header에 설정하기

 

관련 설정 & 코드는 다음 github에 저장되어 있다.

https://github.com/lemontia/react-project-base-config

 

lemontia/react-project-base-config

Contribute to lemontia/react-project-base-config development by creating an account on GitHub.

github.com

 

 

이번 프로젝트를 설정하면서 사용된 패키지는 다음과 같다

npx create-next-app --example with-typescript [앱 이름]

추가설치

npm i redux react-redux 
npm i immer 
npm i @types/react-redux 
npm i typesafe-actions
npm i next-redux-wrapper
npm i redux-devtools-extension
npm i axios
npm i next-cookies
npm i react-cookies
npm i @types/react-cookies
# UI
npm i styled-reset  
npm i babel-plugin-styled-components
npm i @material-ui/core
npm i @material-ui/icons
npm i @material-ui/styles
npm i @material-ui/lab

 

react, redux, next, typescirpt 의 구체적인 설정방법을 알고 싶다면 이전글 참조

lemontia.tistory.com/988

 

[react] react + nextjs + redux + typescript 설정하기(redux toolkit 사용)

react + redux + typescript를 이용한 프로젝트 생성을 알아보려 한다 타입스크립트가 적용되는 reaxt + nextjs 를 설치한다. npx create-next-app --example with-typescript test-app npx 를 사용하는 이유는 이..

lemontia.tistory.com

 

 

github에서 clone한 후 폴더로 이동, 다음 명령어를 실행한다

# 패키지 다운로드
npm i

# 실행
npm run dev

 

그럼 기본 포트 3000 번으로 연결해 다음과 같이 실행된다

접속: localhost:3000

 

1. axios 기본설정

Backend와의 연결은 아직 개발하지 않았는데, 설정은 다음을 참고하면 된다.

config/config.json

{
    "development": {
        "BACKEND_URL": "http://localhost:10003",
        "REDIRECT_URL": "http://localhost:3000",
        "IMAGE_URL": ""
    },
    "production": {
        "BACKEND_URL": "http://localhost:10003",
        "REDIRECT_URL": "http://localhost:3000",
        "IMAGE_URL": ""
    }
  }

 

각각 옵션의 설명을 첨부하자면

BACKEND_URL : 백엔드 서버의 URL을 의미

REDIRECT_URL: 프론트엔드에 리다이렉트 할때 사용(소셜 로그인 등 리다이렉트를 받아야할때 사용)

IMAGE_URL: 이미지 URL 의 루트경로

 

해당 설정을 config/config.tsx 에서 글로벌로 설정한다

const configFile = require("../config/config.json")

const serverEnv = process.env.NODE_ENV
const config = configFile[serverEnv]

export const BACKEND_URL = config["BACKEND_URL"]
export const IMAGE_URL = config["IMAGE_URL"]
export const REDIRECT_URL = config["REDIRECT_URL"]

이것을 이용해 axios 설정을 할 수 있는데 _app.js 에 다음과 같이 했다.

import { BACKEND_URL } from '../config/config'
...

axios.defaults.withCredentials = true;
axios.defaults.baseURL=BACKEND_URL
axios.defaults.timeout=10000

 

 

 

2. ui 적용(material-ui, styled-component 사용)

UI 프레임워크는 material-ui를 사용했다. styled-components 를 쓰고싶다면 material-ui/core/style 을 사용하는 것을 추천한다. (둘다 같이 쓸 수 있지만 그것보단 하나만 쭉 쓰는게 코드 가독성에 좋아보인다.)

material-ui를 쓰고싶지 않다면 제외해도 무방. 또한 bootstrap 으로 바꾸고 싶다면 바꿔 설정하길 추천.

 

material-ui 는 bootstrap 처럼 UI Framework 이다. 자세한 설명은 다음 홈페이지 참조

https://material-ui.com/

 

Material-UI: A popular React UI framework

React components for faster and easier web development. Build your own design system, or start with Material Design.

material-ui.com

material-style은 다음처럼 사용한다.

...
const ContainerMainView = styled(Container)({
    padding: "10px 10px"
})
const BoxDiv = styled(Box)({
    marginBottom: "10px"
})
...

const Index = () => {
    return (
        <>
            <ContainerMainView>
                <BoxDiv>
                    메인페이지 입니다
                </BoxDiv>
...

ContainerMainView에는 padding이 BoxDiv에는 marginBottom이 적용된 것을 확인할 수 있다.

 

padding이 적용된 것을 확인

 

개인적으로 이것을 선택한 이유는 디자인 상속이 가능하단 점. 예를들면 다음처럼 설정이 가능하다.

const BoxDiv = styled(Box)({
    marginBottom: "10px"
})
// BoxDiv 상속
const BoxViewText = styled(BoxDiv)({
    color: "#ff0000"
})

...
    return (
        <>
...
                <BoxDiv>
                    reducer 테스트: 
                    <BoxViewText>
                        Input 값: { text }
                    </BoxViewText>
                </BoxDiv>
...

 

margin-bottom 이 적용된걸 확인할 수 있다.

 

다만 이대로 설정을 끝내면 새로고침 할때마다 다음 에러가 발생한다.

Prop `className` did not match. Server

이 부분을 해결하기 위해 _document.tsx 에 설정을 했는데, 이전글에 자세히 소개했다.

lemontia.tistory.com/1014

 

[react, material-ui] material-ui style 사용중 SSR 에러 발생(Prop `className` did not match. Server)

다음처럼 에러가 발생했다. Prop `className` did not match. Server: "MuiTypography-root WithStyles(ForwardRef(Typography))-root-8 MuiTypography-h6" Client: "MuiTypography-root WithStyles(ForwardRef(Ty..

lemontia.tistory.com

 

 

 

3. cookie를 이용한 authorization 등록 & SSR에서 자동 header에 설정하기

화면에서 버튼을 누르면 token을 생성해 cookie에 저장하기로 한다. 운영되는 프로그램이라면 token을 backend에서 받아오겠지만 여기서는 backend를 제외했기 때문에 임시로 'aaa'라고 만든다.

 

화면에 버튼을 추가하고 버튼 핸들러를 등록한다.

...
const TokenArea = styled(BoxDiv) ({
})
...
    // 쿠키에 token 저장
    const createTokenHandler = () => {
        const accessToken = "aaa"; // 본 토큰은 서버에서 받아오도록 해야하지만 여기선 임시로 aaa로 함

        axios.defaults.headers.Authorization = "Bearer " + accessToken;

        // 만료시간 1일 설정(60초 * 60 * 24)
        const expires = new Date()
        expires.setDate(Date.now() + 1000 * 60 * 60 * 24)

        cookie.save(
            'accessToken'
            , accessToken
            , {
                path: '/'
                , expires
            }
        )

        dispatch({
            type: SET_TOKEN
            , payload: {
                accessToken: accessToken
            }
        })

        alert("저장되었습니다")
        console.log("header: ", axios.defaults.headers.Authorization)
    }
...

    return (
        <>
...
                <TokenArea>
                    <BoxDiv>
                        <Typography variant={"h6"} className={""}>
                                Token + cookie 테스트
                        </Typography>
                    </BoxDiv>
                </TokenArea>
                <BoxDiv>
                    <Button
                        variant="outlined"
                        onClick={createTokenHandler}>
                        토큰 쿠키 저장
                    </Button>
                </BoxDiv>
...

 

버튼을 누르면 알림창이 나오면서 다음처럼 cookie에 저장, axios header에 추가, 그리고 Redux 에 저장되는 것을 확인할 수 있다.

 

크롬 개발자도구 => application 창에서 확인 가능한 cookie

console 창에서 보는 axios에 설정된 값.

 

 

그러나 새로고침하면 axios와 redux에 저장한 토큰이 리셋된다. 그래서 이것을 SSR에서 설정해주어야 하는데 _app.tsx 에 설정을 추가했다.

 

...
RootApp.getInitialProps = async (appContext: AppContext) => {
    const { ctx } = appContext;
    const allCookies = cookies(ctx);

    // accessToken를 이용해 고객정보 조회
    const accessTokenByCookie = allCookies['accessToken'];    
    if ((accessTokenByCookie === "" || accessTokenByCookie === undefined) === false) {
        axios.defaults.headers.Authorization = "Bearer " + accessTokenByCookie
        ctx.store.dispatch({
            type: SET_TOKEN
            , payload: {
                accessToken: accessTokenByCookie
            }
        })

    }

    const appProps = await App.getInitialProps(appContext)

    return { ...appProps }
}

 

이제 새로고침해도 cookie의 값을 가져와 axios와 redux에 넣어주는 것을 확인할 수 있다.

 

실제로 들어갔는지 확인하는 코드를 추가해보자.

...
    const {accessToken} = useSelector((state:RootState) => state.userReducer)
...

    return (
        <>
...
                <BoxDiv>
                    store에 저장된 accessToken => {accessToken}
                </BoxDiv>
...

 

 

 

끝.

 

반응형

댓글