ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CRA를 Next.js로 마이그레이션하기
    Web/React 2021. 10. 6. 02:39
    반응형

    서버 사이드 렌더링(SSR)을 언젠가 한 번쯤 해봐야겠다고 생각하곤 있었는데,

    막상 프로젝트 진행하고 미션 진행하고 하다보니 딱히 시도해 볼만한 시간이 안났다.

    사실 핑계이긴 하다.. 쉴 시간 줄여서 공부하면 되긴 하니깐.

    반성...

     

    그러던 중 때 마침 미션으로 SSR 관련 미션이 나와서

    Next.js를 아주 가볍게 이용하여 SSR을 살짝 맛볼 수 있게 됐다.

     

    서버 사이드 렌더링 중에서도 Next.js,

    그 중에서도 CRA에서 Next.js로 마이그레이션 하는 방식으로

    아주 가볍게 도입만 해 본 셈이지만, SSR을 깊게 공부할 수 있는 발판이 될 수 있지 않을까 생각한다.


    1. 설치

    CRA의 불필요한 dependency들을 제거한다. 

    npm uninstall react-scripts react-router-dom

    Next.js 를 설치해준다.

    npm install next

     

    2. package.json 설정

    scripts를 삭제한 react-scripts에서 next를 이용한 script로 수정해준다.

    {
    	"scripts": {
    		"dev": "next dev",
    		"build": "next build",
    		"start": "next start",
    	}
    }

    3. 디렉토리 구조 수정

    Next.js에서는 이미지 같은 에셋 파일들을 전부 public 폴더에 보관한다.

    페이지 관련 자바스크립트 파일들은 전부 pages 폴더에 보관하면 된다.

    pages 폴더에 보관한 파일들은 파일명 그대로 route가 된다.

    ex) about.js → /about

    src 폴더같은 것들은 따로 필요 없을 것 같다.

    4. pages/_document.js

    이 다음에 소개될 _app.js 파일 다음에 실행되는 파일이다.

    _document.js는 기존의 CRA에 있던 index.html의 역할을 대신하는 자바스크립트 파일이라고 생각하면 된다.

    default 파일이 알아서 추가되기 때문에 뭔가 수정하고싶지 않다면 따로 작성하지 않아도 된다.

    만약 모든 페이지에 공통적으로 사용되는 <head>나 <body>의 내용을 커스텀하고 싶다면 

    _document.js를 직접 만들어서 내용을 구현해주면 된다.

    <head>는 주로 메타 태그를 수정하고자 할 때 만져주면 된다.

    // _document.js의 default 형태
    import Document, { Html, Head, Main, NextScript } from 'next/document';
    
    class MyDocument extends Document {
    	static async getInitialProps(ctx) {
    		const initialProps = await Document.getInitialProps(ctx);
    		return { ...initialProps };
    	}
    
    	render() {
    		return (
    			<Html>
    				<Head />
    				<body>
    					<Main />
    					<NextScript />
    				</body>
    			</Html>
    		);
    	}
    }
    
    export default MyDocument;

    a. _document.js 파일을 작성할 때는 꼭 Document 클래스를 상속받는 클래스 컴포넌트의 형태로 작성해야 한다.

    b. render 함수는 반드시 <Html>, <Head>, <Main>, <NextScript> 요소를 return 해줘야 한다.

    c. _document.js 에서 사용하는 <Head>는 next/head가 아니라 next/document에서 import 해와야 한다.

      - next/head는 각 페이지의 title 같은 것들을 설정해줄 때 사용하는 모듈이고, next/document는 모든 페이지에서

        공통적으로 사용될 만한 내용들을 설정해줄 때 사용하는 모듈.

    d. _document.js는 서버에서 항상 실행되기 때문에 브라우저 API 혹은 이벤트 핸들러가 포함된 코드는 실행되지 않는다.

      - <Main>을 제외한 부분은 브라우저에서 실행되지 않으므로 이곳에 비즈니스 로직을 추가해선 안된다.

      - _app.js와 달리 getStaticProps나 getServerSideProps를 통해 데이터를 불러올 수 없다.

     

    내가 적용해 준 _document.js

    import Document, { Head, Html, Main, NextScript } from 'next/document';
    
    class MyDocument extends Document {
    	render() {
    		return (
    			<Html lang="ko">
    				<Head>
    					<meta charSet="utf-8" />
    					<meta name="theme-color" content="#000000" />
    					<meta name="description" content="누구나 접근할 수 있는 항공사" />
    				</Head>
    				<body>
    					<Main />
    					<NextScript />
    				</body>
    			</Html>
    		);
    	}
    }
    
    export default MyDocument;

     

    5. pages/_app.js

    _app.js는 서버로 요청이 들어왔을 때 가장 먼저 실행되는 파일이다.

    페이지에 적용할 공통적인 레이아웃의 역할.

    global style 또한 app.js에서 먹여주면 된다. (font 같은 경우도 마찬가지)

    import '../styles/global.css';
    
    const MyApp = ({ Component, pageProps }) => {
    	return <Component {...pageProps} />;
    }
    
    export default MyApp;

    a. Component의 속성값은 요청한 페이지이다.

      - 예를 들어, about 페이지에 접속하면 Component는 about 컴포넌트를 가리키는 식.

    b. pageProps는 getInitialProps, getStaticProps, getServerSideProps 중 하나를 통해 fetching한 초기 속성값이 된다.

    c. _app.js에서도 getInitialProps를 통해 모든 페이지에서 공통적으로 사용할 속성값을 지정할 수 있지만, 이런 경우에는 자동 정적 최적화(Automatic Static Optimization)가 비활성화되어 모든 페이지가 SSR을 통해 제공된다.

    d. 만약 _app.js에서 getInitialProps를 사용하고자 한다면, 반드시 App 컴포넌트를 불러온 후 getInitialProps를 통해 데이터를 불러와야 한다.

    const MyApp = ({ Component, pageProps }) => {
    	return <Component { ...pageProps } />;
    }
       
    MyApp.getInitialProps = async (appContext) => {
    	const appProps = await App.getInitialProps(appContext);
    	// ex) const { data } = await axios.get( ... );
      
    	return { ...appProps };
    };
    
    export default MyApp;

     내가 적용해 준 _document.js

    import '../global.css';
    import Link from 'next/link';
    
    const App = ({ Component, pageProps }) => {
    	return (
    		<>
    			<ul>
    				<li>
    					<Link href="/">
    						<a>Home</a>
    					</Link>
    				</li>
    				<li>
    					<Link href="/SpinButton">
    						<a>Spin Button</a>
    					</Link>
    				</li>
    				<li>
    					<Link href="/about">
    						<a>About</a>
    					</Link>
    				</li>
    				<li>
    					<Link href="/message">
    						<a>Message</a>
    					</Link>
    				</li>
    			</ul>
    			<Component {...pageProps} />
    		</>
    	);
    };
    
    export default App;

     

    ※ _app.js와 _document.js

    사실 위에서도 이미 말했지만, _document.js와 _app.js는 없어도 된다.

    Next.js에서 자체적으로 제공하는 default 로직으로 실행되기 때문.

    하지만 개발을 하다보면 항상 Customizing의 욕망이 들끓기 마련.

     

    이 두 파일은 서버에서만 실행되는 파일이다.

    그래서 역시나 언급해놨지만, 이벤트 핸들러나 브라우저 API를 사용할 수 없는 파일들이다.

    최초로 실행되는 것은 _app.js이고, 이 파일은 Client에서 띄워야 할 전체 컴포넌트의 레이아웃이다.

    공통 레이아웃으로써 틀을 잡아주고, 내부에 포함되는 컴포넌트들을 실행해주는 역할을 한다.

     

    _document.js_app.js 다음에 실행되는 파일이다.

    _document.js의 <Main> 컴포넌트 아래에서, _app.js가 실행되면서 갖추어진 content 들이 실행된다.

    _document.js에서, 브라우저는 <Main>을 제외한 다른 Component들을 initialize 하지 않는다.

    그래서 어플리케이션 공통 로직은 _document.js가 아닌 _app.js에 구현해주도록 하자.

     

    React components outside of <Main /> will not be initialized by the browser.

    Do not add application logic here. If you need shared components in all your pages (like a menu or a toolbar), take a look at the App component instead.

     

    ※ getInitialProps

    모든 웹페이지는 사전에 불러와야하는 데이터들이 존재한다.

    일명 'data fetching'인데, 보통 CSR에서는 React의 문법에 따라 componentDidMount나 useEffect에서 실행된다.

    이 과정을 서버에서 처리할 수 있도록 도와주는 것이 바로 getInitialProps이다.

     

    특정 페이지에서만 사용할 데이터를 미리 불러오고 싶다면 각 페이지에 getInitialProps를 붙여주면 되고,

    모든 페이지에서의 공통된 data fetching이 필요하다면 _app.js에 getInitialProps를 붙여주면 된다.

    getInitialProps를 붙이는 방법은 저 위에 언급된 코드와 동일.

    페이지마다 붙여줄 때도 동일한 방식으로 붙여주면 된다.

     

    getInitialProps 역시나 서버에서만 실행되기 때문에, 클라이언트에서만 사용 가능한 로직은 피하자.

    또한, 한 페이지 당 무조건 하나의 getInitialProps만 실행할 수 있다.

    이 말은 곧, _app.js에 getInitialProps가 포함돼있다면 그 하부 페이지들의 getInitialProps는 실행되지 않는다는 말.

    다만 다음과 같은 Customizing을 통해 반영할 수 있긴 하다.

    export default class MyApp extends App {
    	static async getInitialProps({ Component, context }) {
    		let pageProps = {};
       
    		// 실행하고자 하는 component에 getInitialprops가 있으면 실행하여 props를 받아올 수 있다.
    		if (Component.getInitialProps) {
    			pageProps = await Component.getInitialProps(context);
    		}
    
    		return { pageProps };
    	}
    
    	render() {
    		const { Component, pageProps, router } = this.props;
        
    		return (
    			<div>
    				<Component {...pageProps} />
    			</div>
    		);
    	}
    };

     

    ※ ctx(Content)

    getInitialProps들은 기본적으로 ctx를 props로 받는다.

    ctx Object의 기본 구성은 다음과 같다고 함.

    - pathname: 현재 pathname. /user?type=normal 에 접속 시, /user

    - query: 현재 query를 object 형태로 출력. /user?type=normal 에 접속 시, { type: 'normal' }

    - asPath: 전체 path. /user?type=normal 에 접속 시, /user?type=normal

    - req: HTTP 요청 객체 (server only)

    - res: HTTP 응답 객체 (server only)

    - err: 렌더링 시 발생하는 에러

     

    Next js 구동방식 과 getInitialProps

    Next js가 React Project의 SSR을 가능하게 한다. 라고는 하는데, 어떤 방식으로 SSR을 가능하게 할까, SSR과 CSR의 구분은 어떻게 되어 있을까.이 궁금증을 해결하기 위해, 먼저 알아야 할 것은 Next js의 구

    velog.io

     

    6. pages/index.js

    index 페이지는 '/'에 해당하는 페이지이다.

    첫 화면을 담당하는 페이지. 원하는 대로 그려주면 된다.

    참고로 global style은 그냥 _app.js에서 import 해주기만 하면 됐지만,

    각 페이지에서 스타일을 먹이고 싶을 때는 css modules의 문법대로 적용을 해줘야 한다.

    css 파일의 이름 형식을 전부 'name.module.css'로 바꾸고,

    classname 또한 module에서 받아오는 형식으로 먹여줘야 한다.

     

    7. Router를 next/link로 대체하기

    CRA를 사용했다면 기존에는 React-router-dom을 활용해서 라우팅을 하고 있을텐데,

    Next.js에서는 next/link를 import해서 Link 태그로 라우팅을 해줘야 한다.

    위에 '내가 적용해 준 _document.js'에서 어떻게 적용해줬는지 참고할 수 있다.

     

    요기까지 해주면 SSR이 적용된 모습을 확인할 수 있다.

     

    참고: https://nextjs.org/docs/getting-started

    https://velog.io/@cyranocoding/Next-js-%EA%B5%AC%EB%8F%99%EB%B0%A9%EC%8B%9D-%EA%B3%BC-getInitialProps

     

    반응형

    댓글

Designed by Tistory.