React 재활훈련- 10일차, Nextjs소개, page router

0

react

목록 보기
10/11

https://www.udemy.com/course/react-next-master/

Nextjs

nextjs는 Vercel이 개발한 오픈소스 자바스크립트 framework로 react에 추가적인 기능들을 제공한다. 따라서, nextjs를 react의 확장판으로 생각하면 된다.

굉장히 다양한 기능들을 제공하는데 대표적으로는 다음과 같다.
1. directory 기반의 page routing
2. image, font, script 최적화
3. server side rendering(SSR)

가장 중요한 것이 server side rendering이다. react만쓰면 client side rendering(CSR) 방식으로만 동작하는데, nextjs를 쓰면 CSR과 SSR을 같이 쓸 수 있다.

먼저 CSR방식으로 동작하는 react app을 보도록 하자.

------------------ 1.html file      --> --------- 2. display rendering --> ------
|React Web server|                      |Browser|                          |화면|
------------------ 3.bundled js file--> --------- 4. content rendering --> ------

맨 처음 하나의 html 파일 (index.html)을 browser에 전달하고, 이를 js code기반으로 화면에 그려준다. 다음으로 사용자의 interaction으로 변화하는 component들에 대해서 bundled js file을 이용해 component를 렌더링해주는 것이다.

따라서, 맨 처음의 html 파일 하나 만으로 여러 개의 page를 구현한 것처럼 보이게 만든 것일 뿐이다. 장점으로는 page간의 이동(실제 html파일을 바꾸는 것이 아닌, component를 렌더링하는 것)이 매우 빠르지만, 단점으로는 첫 페이지의 로딩이 매우 느리다. 왜냐하면 bundled js file을 넘긴다는 것은, 아직 렌더링에 필요하지 않은 component 구현 사항들도 모두 전달한다는 것이기 때문이다.

가령, Home 화면을 하나 그리는데, 결제 page에 필요한 component의 js code가 필요하진 않을 것이다. 그러나, CSR의 경우 모든 js code를 처음에 넘겨주어야 하기 때문에 초기 로딩 속도가 느릴 수 밖에 없어진다.

이를 해결해주는 것이 SSR이다.

------------------ 1.완성된 html file--> --------- 2. 완성된 화면 렌더링      --> ------
|nextjs Web server|                     |Browser|                               |화면|
------------------ 3.bundled js file--> --------- 4. 인터렉션, 페이지 이동 등 --> ------

nextjs는 빈 html file이 아니라, 완성된 html file을 page마다 바로바로 Browser에 전달한다. 즉, CSR과 달리 초기에 완성된 page를 보내준다는 것이다. 이후 사용자의 interaction이 발생하면 bundled js file에 있는 js component들을 이용하는데, 만약 Home 화면을 그리고 있다면 결제 page의 component는 필요없기 때문에 해당 js code는 split하여 전달하지 않는다.

즉, 결론적으로 page마다 이미 렌더링된 html파일을 전달하여, react의 초기 page의 느린 렌더링 속도를 보완하고, page내의 렌더링은 react가 가진 CSR을 이용하여 효율적으로 화면을 그려준다는 것이다.

여기에 nextjs는 더불어 검색 엔진 최적화(SEO)를 위한 기능도 제공한다. 검색 potal site에서는 robot을 통해서 다양한 page들의 정보를 수집하고 db에 저장한다. 이후 사용자가 검색한 키워드와 연관된 정보를 결과로 우리의 page들을 노출시켜주는데, 문제는 대다수의 검색 사이트가 html기반으로 page정보를 수집한다는 것이다. react의 경우 초기 html파일은 빈 껍데기이므로 검색 사이트의 엔진이 html 기반으로 page 정보를 수집할 때, 제대로 된 정보를 수집하지 못하게 되는 것이다.

그런데, nextjs는 초기에 html page를 이미 렌더링되어 있기 때문에 검색 사이트 robot이 html page를 쉽게 파싱하여 정보를 저장할 수 있다.

Nextjs app 생성

npx를 사용하여 설치하면 된다.

npx create-next-app@latest .

지금은 study용이기 때문에 eslint말고는 그냥 모두 no라고 하자. package.json을 통해 어떤 library가 설치되었는지 확인해보도록 하자.

  • package.json
{
  "name": "section12",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "react": "^18",
    "react-dom": "^18",
    "next": "13.4.12"
  },
  "devDependencies": {
    "eslint": "^8",
    "eslint-config-next": "13.4.12"
  }
}

reactnextjs가 모두 설치된 것을 볼 수 있다. next.js를 실행하는 방법으로 npm run dev를 실행보도록 하자.

npm run dev

를 실행하면 localhost:3000으로 nextjs app이 실행되어 열린 것을 볼 수 있다.

이제 project 구조를 보도록 하자.

  • next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
};

export default nextConfig;

다양한 nextjs 기능들을 추가할 수 있다.

jsconfig.json은 js를 어떻게 compile할 것인지 option을 정하는 것이다.

  • jsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./*"]
    }
  }
}

추후에 변경하면서 알아보겠지만 paths로 alias를 설정하여 import를 편하게 부를 수 있다.

다음으로 pages directory를 보면 실제 js파일들이 있는데, pages directory안에 있는 js file들은 각각의 page 역할을 하는 파일들로 생각하면 된다. 가령 pagesindex.js는 index.html에 쓰이는 file로 생각하면 된다.

styles는 css 모듈들이 있는 곳이다. 이를 통해 nextjs는 css를 모듈로서 사용한다는 것을 알 수 있다.

Page router

nextjs는 page routerapp router를 모두 제공하는데, app router는 버그들이 많고 입문자들이 배우기 어렵다. 따라서, 지금까지 계속 사용되던 방식인 page router를 배우는 것이 좋다.

page router는 directory기반의 라우팅을 제공한다. 다음의 directory들이 있다고 하자.

pages
    - index.js
    - about.js
    post
        - index.js
        - [id].js
    news
        - index.js

/로 요청이 오면 index.js이 렌더링되고, /about이면 about.js가 렌더링된다. 또한, /post로 요청이 오면 post.js가 없기 때문에 postindex.js가 렌더링된다. 만약, 동적 경로(URL parameter)가 필요한 경우 [id].js를 이용하면 되는데, /post/1이 요청이 오면 [id].js가 렌더링된다.

만약 /none과 같이 만들지 않은 url을 요청하면 404 not found가 나온다.

pages directory를 보면 _app.js, _document.js와 같은 component를 볼 수 있는데, 이들은 모든 page component들에 적용되는 공통 요소들로 보면 된다.

  • _app.js
import "@/styles/globals.css";

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

App은 마치 react의 App component와도 같은데 pages directory에 있는 page들이 Component props로 전달되어, child로 렌더링한다. 즉, 모든 page component들의 root역활을 한다.

_document.jsreactindex.js와 비슷한 역할로 생각하면 된다. 즉, 가장 기본 template가 되는 영역이라고 생각하면 된다.

  • _document.js
import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html lang="en">
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

그래서 Html lang을 en에서 kr로 바꾸면 모든 page들의 lang설정이 kr이 된다.

Page router - URL parameter 받기

nextjs에서 page router로 URL parameter를 받는 방법은 매우 간단하다. 만약 /search/KOR, /search/ITA와 같이 뒷부분을 URL paramter로 받고 싶다면, 다음과 같이 pages directory에 js파일을 만들면 된다.

pages/country/[code].js []안에 URL parameter의 이름을 적어주면 된다. 이 URL parameter를 받아내는 것은 nextjs에서 제공하는 useRouter를 사용하면 되는데, 반환된 router는 URL과 관련된 대부분의 정보들을 담고 있다.

  • pages/country/[code].js
import { useRouter } from "next/router"

export default function Country() {
    const router = useRouter()
    const { code } = router.query
    return (
        <div>Country {code}</div>
    )
}

code에 URL parameter로 전달한 값이 전달된다. 만약 [code].js가 아니라 [id].js였다면 router.query안에는 id property로 값이 저장되어있을 것이다.

그런데 만약 country/KOR/123, country/KOR/415 , country/ITA/1254와 같이 두 URL parameter가 연달아 나온다면 어떻게해야할까?? 이때에는 catch all표현으로 [...code].js를 사용하면 된다. 그러면 code안에 모든 URL paramter들의 정보들이 담긴다. 가령, country/KOR/123이면 code는 KOR123이고, country/ITA/1254이면 codeITA1254이다.

또한 URL parameter값이 optional한 경우에는 [[param]]을 써주면 된다. 가령 country/KOR이 아니라 /country에도 대응하도록 만들고 싶다면 [[...code]].js로 만들면 된다.

Page router - page 이동하기

nextjs에서 page를 이동할 때는 Link를 사용하면 된다. 이 LinkCSR방식으로 실제로의 page이동없이 page component를 변경해주는 것이다.

import Link from "next/link";

export default function Home() {
  return (
    <div>
      <Link href={"/search"}>Search Page 이동</Link>
    </div>
  );
}

한 가지 조심해야할 것은 react-router에서의 Linkto에 url을 써주면 되었지만, 여기서는 href에 써주어야 한다.

만약 URL parameter를 쓰고 싶다면 다음과 같이 쓸 수 있다.

import Link from "next/link";

export default function Home() {
  const code = "KOR"
  return (
    <div>
      <Link href={`/search/${code}`}>Search Page 이동</Link>
    </div>
  );
}

그러나 pages directory에 있는 file명 기준으로 쓰고 싶을 수도 있다. 이때에는 URL object이용하면 된다.

import Link from "next/link";

export default function Home() {
  const code  ="KOR"
  
  return (
    <div>
      <div>
        <Link href={"/search"}>Search Page 이동</Link>
      </div>
      <div>
        <Link href={{pathname: "/country/[code]", query: {code: code}}}>Search Page 이동</Link>
      </div>
    </div>
  );
}

url object는 두 개의 property로 이루어져 있는데, 하나는 pathname 하나는 query이다. pathname에는 url이 아니라 pages directory에서 어떤 component를 사용할 것인지를 지정하고, query는 url parameter로 어떤 것을 보낼 지 결정하는 것이다.

다른 방법으로 useRouterrouter객체를 사용하는 방법이 있다. 위에서 언급했듯이 useRouter로 반환되는 router는 nextjs routing에 관한 모든 정보와 방법들을 다 가지고 있다. 따라서, 특정 page로 이동하는 것도 가능한데 router.push를 사용하면 된다.

import Link from "next/link";
import { useRouter } from "next/router";
export default function Home() {
  const code  ="KOR"
  const router = useRouter()

  const onClickButton = () => {
    router.push('/search')
  }
  
  return (
    <div>
      <div>
        <button onClick={onClickButton}>Search page로 이동</button>
      </div>
      <div>
        <Link href={"/search"}>Search Page 이동</Link>
      </div>
      <div>
        <Link href={{pathname: "/country/[code]", query: {code: code}}}>Search Page 이동</Link>
      </div>
    </div>
  );
}

이제 버튼이 눌리면 router.push('/search')가 실행되며 /search page에 해당하는 page component가 렌더링된다. 만약, url object를 쓰고 싶다면 다음과 같이 수정이 가능하다.

const onClickButton = () => {
    router.push({pathname: "/country/[code]", query: {code: code}})
}

router에서는 push 외에도 뒤로가기를 방지하여 page를 이동하는 replace와 뒤로가기를 유발하는 back, 새로고침을 유발하는 reload 등이 있다.

layout 설정하기

전체 page의 layout을 설정하고 싶으면 _app.js component를 이용하면 된다. _app.js에서의 App component는 모든 component의 부모 역할을 한다.

import "@/styles/globals.css";
import Layout from "@/components/Layout";

export default function App({ Component, pageProps }) {
  return (
    <div>
      <Layout>
        <Component {...pageProps} />;
      </Layout>
    </div>
  )
}

다음과 같이 nextjs에서의 모든 page component들은 Component로 전달되기 때문에 Component의 부모에 우리가 원하는 layout을 가진 Layout component를 설정해주면 된다.

그런데 만약, 특정 page에만 component를 설정하고 싶을 때는 어떻게 해야할까?? 가령 /pages/country/[code].js/pages/search/index.js에만 SubLayout component가 생기도록 만들고 싶을 때와 같은 경우이다.

이 경우 먼저 대상이 되는 component에게 Layou이라는 property로 layout이 되는 component를 적용시켜주면 된다.

  • /pages/search/index.js
import SubLayout from "@/components/SubLayout"

export default function Search() {
    return <div>Search Page</div>
}

Search.Layout = SubLayout

함수 object에 Layout이라는 property로 SubLayout을 설정해주도록 한다.

참고로 SubLayout component는 다음과 같다.

  • /components/SubLayout.js
import style from "./SubLayout.module.css"

export default function SubLayout({ children }) {
    return (
        <div className="SubLayout">
            <div>{children}</div>
            <footer className={style.footer}>@winterlood</footer>
        </div>
    )
}

이 다음 _app.js로 넘어가도록 하자.

  • /pages/_app.js
import "@/styles/globals.css";
import Layout from "@/components/Layout";

export default function App({ Component, pageProps }) {
  return (
    <div>
      <Layout>
        <Component.Layout>
          <Component {...pageProps} />;
        </Component.Layout>
      </Layout>
    </div>
  )
}

이제 App component의 Component props에는 Layout이라는 property안에 SubLayout component가 있을 것이다. 이를 사용하기 위해서 Component.Layout안에 Component를 child로 넣어주면 된다.

그러나, 이렇게 사용할 경우 ComponentLayout property를 넣어주지 않은 객체에 대해서는 error가 발생한다. 즉, / 경로로 들어가 확인해보면 react error가 발생해있을 것이다. 이는 ComponentLayout property가 없기 때문에 발생한 문제라고 생각하면 된다.

이를 해결하기 위해서 약간의 trick을 주도록 하자.

  • /pages/_app.js
import "@/styles/globals.css";
import Layout from "@/components/Layout";

export default function App({ Component, pageProps }) {
  const EmptyLayout = ({children}) => <>{children}</>
  const SubLayout = Component.Layout || EmptyLayout

  return (
    <div>
      <Layout>
        <SubLayout>
          <Component {...pageProps} />;
        </SubLayout>
      </Layout>
    </div>
  )
}

SubLayoutComponentLayout property가 없다면 EmptyLayout을 갖도록 한다. EmptyLayout은 그대로 children을 반환하도록 하는 객체로 아무런 기능도 없는 객체이다.

0개의 댓글