Published on

nextjs 의 내장 컴포넌트

Authors
  • avatar
    Name
    길재훈
    Twitter

nextjs 의 내장 컴포넌트

next.js 는 기본적으로 제공되는 native component 가 존재한다. 각 native component 는 개발시 많이 사용되는 것들을 만들어놓은 집합이라고 보면 된다.

각 내장 컴포넌트는 여러가지 구성되어 있는데, 살펴보도록 한다.

Routing

Next.jspages of concept 에 의해 만들어진 File-system 을 기반을 가지고 있다.

이는 Docs 에 적혀져 있는 문구이다. 여기서 말하는 pages of conceptfile 기반으로 routing 가능하도록 만들어진것으로 보면 된다.

file 은 각 주소의 경로를 나타낸다.

nexjs 에서는 pages 라는 폴더를 제공하는데, 이 폴더안에 각 경로에 사용되는 이름을 지어 넣어주면, 페이지의 경로가 된다.

Docs 에서는 다음과 같이 경로가 설정된다고 한다.

pages/index.js/
pages/blog/index.js/blog

이는 매우 직관적이고, 사용하기 쉽도록 만들어준다. 현재 보고 있는 책에서의 예시는 다음의 예시도 설명해준다.

pages / index.js
contact - us.js
posts / index.js[slug].js

위의 예시에서 [slug].js 를 사용하는데, 이는 dynamic routing 을 제공해주는 주요한 기능이다.

여기서 [slug]route variable 이라고 부르며, 브라우저 주소창에 입력한 값을 가진다.

https://locahost:3000/posts/im-slug

이때, [slug]im-slug 가 된다. 재미있는것은, route variable 안에 route variable 설정도 가능하다

pages/
  index.js
  contact-us.js
  posts/
    index.js
    [date]/
      [slug].js

이러한 방식을 사용한다면, https://localhost:3000/posts/2023-04-26/im-slug 로도 사용가능하다.

이때, [data]2023-04-26 이 되며, [slug]im-slug 가 된다.

다음은 Docs 에서 제공하는 방식이다.

pages/blog/[slug].js/blog/:slug (/blog/hello-world)
pages/[username]/settings.js/:username/settings (/foo/settings)
pages/post/[...all].js/post/* (/post/2020/id/title)

동적으로 생성되는 postsid 를 기반으로 해당 route variable 을 만들어 서로 다른 페이지를 구성할 수 있도록 해주는 편리한 기능이다.

이러한 route variabel 을 사용했으면, 가져와 사용할 수 있는 방법에 대해서 알아보아야 한다.

보통 2가지 방법으로 많이 사용한다.

getServerSidePropsgetStaitcPropsprops 를 통해 가져오는 방법

export async function getServerSideProps ({ parmas }) {
  const { slug } = params;
  ...
}

next/routerhook 을 통해 가져오는 방법


import { useRouter } from 'next/router'

function Index() {
  const { query } = useRouter()
  ....
}

이렇게 가져온 query 를 사용할때, 주소의 path query 를 사용한다면, 마치 객체의 프로퍼티값을 가져올 수 있도록 만들 수 도 있다.

// pages/posts/name
`https://localhost3000:/posts/jh0-0?test=true`


import { useRouter } from 'next/router'

function Index() {
  const { query } = useRouter()
  // query = {name: jh0-0, test: "true"}
  ....
}

nextjs 에서는 a 링크대신 next/linkgLink 컴포넌트를 사용하여, 각 경로를 이동한다.

hyper link 를 생성하는 a 태그는, 새로고침이 되기 때문에 SPA 를 통해 만들어진 react 에서는 좋은 방법은 아니다.

새로고침이 되지 않고, client 에서 최적화된 페이지 이동을 구현하기 위해서는 Link 를 사용하는것이 좋다.

Link 는 기본적으로 화면에 표시되는 모은 Link 에 대해 연결된 부분에 대한 페이지를 Pre-lodading 한다.

그렇기에, page 로 이동했을때, 해당 페이지는 이미 불러온 상태로써 존재한다. 이러한 방식을 원치 않는다면 preload={false} 를 통해 설정 가능하다.

Link 사용시 복잡한 URL 을 사용한다며 href 속성을 객체로 전달 가능하다.

URL Object 사용

import Link from 'next/link'

function Home() {
  return (
    <ul>
      <li>
        <Link
          href={{
            pathname: '/about',
            query: { name: 'test' }, // /about?name=test
          }}
        >
          About us
        </Link>
      </li>
      <li>
        <Link
          href={{
            pathname: '/blog/[slug]',
            query: { slug: 'my-post' }, // /blog/my-post
          }}
        >
          Blog Post
        </Link>
      </li>
    </ul>
  )
}

router.push

앞에서 말하 next/router 에서는 push 를 제공한다. 이는, link 를 사용하지 않고, useEffect 등을 사용하여 page 를 이동해야 할때 사용한다.

router.push 역시 URL Object 를 사용하여 경로 설정이 가능하다

router.push({
  pathname: '/blog/[slug]',
  slug: 'my-post',
})

이때, pushLink 와는 다르게 pre-laoding 을 제공하지 않는다. 그러므로, page 이동시 왠만하면 Link 를 사용하여 처리하는것이 좋다.

next/image

정적 자원/public 폴더를 통해 전달된다. 이러한 static asset 을 사용할때, 중요한 부분이 바로 image 이다.

nextjs 에서는 CLS 의 문제를 해결하기 위해 Image 컴포넌트를 제공한다.

CLS(Cumulative Layout shift) 란?

이미지를 제공하지만, 이미지가 늦게 불려와 주변 레이아웃이 변경되는 현상

nextjsimg 태그에 복잡한 srcset 속성값을 지정해서 화면 크기별로 이미지를 조정해준다.

예를 들어 각 브라우저 마다 지원해주는 image 포멧이 다를 수 있다. 예를 들어 chrome 사용시 webP 를 사용해서 image 를 최적화 해주며, safari 같은 브라우저 사용시에는 jpeg 로 처리되는것을 볼 수 있다.

이러한 방식으로 각 image 를 받아, srcset 을 형성한후 그에 맞는 포멧으로 자동 변환해주는 매우 편리한 기능이다.

_app.js

_app.js 는 페이지 이동시 서로 다른 페이지간 상태 유지 및 전역 스타일 추가, 페이지 레이아웃 관리, 페이지 속성에 데이터를 추가할때 하용하는 page 이다.

이때, 중요한것은 _app.jsgetServerSidePropsgetStaticProps 사용이 불가능하다.

만약, 모든 페이지를 렌더링할대 마다 서버에서 특정 데이터를 가져와야 한다면, getInitialProps 를 사용하여 처리해야 한다.

이 함수 사용시 모든 페이지를 서버에서 렌더링하기 때문에 동적 페이지에 대한 정적 최적화를 하지 못한다

import App from 'next/app'

function AppIndex({ component, pageProps }) {
  return <Component {...pageProps} />
}

AppIndex.getInitialProps = async (appContext) => {
  const appProps = await App.getInitialProps(appContext)
  const addProps = await fetch(...)
  return {
    ...appProps,
    ...addProps,
  }
}

export defalt AppIndex

여기서 AppgetInitialProps 를 사용하여, appContextprops 를 가져온후, 추가할 propsserver 로 부터 fetch한후 addProps 에 넣는다.

그리고 appPropsaddProps 를 합친 객체를 반환하여 pageProps 로 전달하여 각 컴포넌트가 공통적으로 새롭게 생성된 props 를 받아 처리하는 로직이다.

_document.js

Next.js 에서 기본적으로 제공하는 HTML 태그를 지정할 필요가 있다. <head></head><body></body> 같은 테그들 말이다.

이때, 기본적으로 제공하는 HTML 틀을 처리하기 위한 파일이 _document.js 파일이다.

REST api

nextjs 를 사용하면서 REST 를 사용하여 data 를 주거니 받거니 할 수 있다.
이때, nextjsservier side 렌더링을 제공하므로, data 를 받아서 처리할 수 있지만, client 상에서도 받아 처리 가능하다.

하지만 이때 문제가 발생할 여지가 크다. 일단, nextjs 에서 token 값 같은 인증을 위한 key.env 를 통해, 환경변수로써 저장한후 가져와 사용한다는것은 당연하다.

이때, 이 환경변수가 노출될수 있는 위험이 존재하는데,
clinet 상의 useEffect 에서 data 를 보낼때 노출될 수 있다는 점이다.

const App = ({ username }: IPorps) => {
  const [data, setData] = useState(null)
  useEffect(() => {
    ;(async () => {
      const req = await axios(`/api/${usename}`, {
        headers: { authorization: process.env.API_TOKEN },
      })
      const { data: userData } = await req.json()
      setData(userData.users)
    })()
  }, [])

  return <div>{data && <UserList data={data} />}</div>
}

export default App

위처럼 axios 를 통해 header 값을 process.env.API_TOKEN 으로 보낸다고 가정하자. 이때, 브라우저의 development tools 를 사용하여, networkheaders 를 보면된면 authorization 의 값이 token 의 평문 문자열로 노출되는것을 볼 수 있다. 또한, CORS 로 인해, 처리가 되지 않을 수 도 있다.

이는 token 값을 숨기기위해 환경변수로 사용한것에 위배되며, 악용될 우려가 있다. 이러한 부분을 처리하기위해 nextjsapi 를 제공하여, 설정할 수 있도록 한다.

api 를 사용하면, 이러한 부분을 우회해서 처리 가능하다.

// /api/userFetch
import axios from 'axios'

export default async function handler(req, res) {
  const username = req.query.username
  const API_ENDPOINT = process.env.API_ENDPOINT // endpoint 역시 노출되지 않는것이 좋다고 한다.
  const API_TOKEN = process.env.API_TOKEN

  const axiosReq = await axios.get(`${API_ENDPOINT}/api/${username}`, {
    headers: { authorization: process.env.API_TOKEN },
  })
  res.status(200).json(userReq.data)
}

Docs 에서는 reqres 에대해서 다음처럼 설명한다.

  • req : http.IncommingMessage 의 인스턴스 이며, 일부 미리 정의된 middlewares
  • res: http.serverResponse 의 인스턴스 이며, 상태코드를 받아 조작이 가능하다.

위의 httpnode 에서 제공하는 모듈이다. next.jsapi 를 통해 처리되는 api 는 위의 인스턴스를 통해 조작되므로, res 를 통해 처리된값을 data 로 받도록 만든다.

const App = ({ username }: IPorps) => {
  const [data, setData] = useState(null)
  useEffect(() => {
    ;(async () => {
      const req = await fatch(`/api/userFetch?username=${usename}`)
      const { data: userData } = await req.json()
      setData(userData.users)
    })()
  }, [])

  return <div>{data && <UserList data={data} />}</div>
}

export default App

굉장히 흥미로운 코드이다. req 를 처리할때, fetch 를 사용하는데 /api/userFetch 를 사용하여, nextjsapi 경로를 통해 userFetch 를 호출한다.

또한, reqquery 를 전달하기 위해, query 에 대한 값을 query string 을 통해 전달한다.

이러한 식으로 apiproxy 형태로 request 가능하도록 구현할 수 있다. 이러한 부분역시 안전하지 못한 방법이라고 설명하며, server 에서만 렌더링하도록 하던지, 아니면, serverframework 를 통해 안전성을 높이는것이 최선이라고 한다.