- Published on
nextjs 의 내장 컴포넌트
- Authors
- Name
- 길재훈
 
 
nextjs 의 내장 컴포넌트
next.js 는 기본적으로 제공되는 native component 가 존재한다. 각 native component 는 개발시 많이 사용되는 것들을 만들어놓은 집합이라고 보면 된다.
각 내장 컴포넌트는 여러가지 구성되어 있는데, 살펴보도록 한다.
Routing
Next.js는pages of concept에 의해 만들어진File-system을 기반을 가지고 있다.
이는 Docs 에 적혀져 있는 문구이다. 여기서 말하는 pages of concept 는 file 기반으로 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)
동적으로 생성되는 posts 의 id 를 기반으로 해당 route variable 을 만들어 서로 다른 페이지를 구성할 수 있도록 해주는 편리한 기능이다.
이러한 route variabel 을 사용했으면, 가져와 사용할 수 있는 방법에 대해서 알아보아야 한다.
보통 2가지 방법으로 많이 사용한다.
getServerSideProps및getStaitcProps의props를 통해 가져오는 방법
export async function getServerSideProps ({ parmas }) {
  const { slug } = params;
  ...
}
next/router의hook을 통해 가져오는 방법
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"}
  ....
}
next/link
nextjs 에서는 a 링크대신 next/linkg 의 Link 컴포넌트를 사용하여, 각 경로를 이동한다.
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',
})
이때, push 는 Link 와는 다르게 pre-laoding 을 제공하지 않는다. 그러므로, page 이동시 왠만하면 Link 를 사용하여 처리하는것이 좋다.
next/image
정적 자원 은 /public 폴더를 통해 전달된다. 이러한 static asset 을 사용할때, 중요한 부분이 바로 image 이다.
nextjs 에서는 CLS 의 문제를 해결하기 위해 Image 컴포넌트를 제공한다.
CLS(Cumulative Layout shift)란?이미지를 제공하지만, 이미지가 늦게 불려와 주변 레이아웃이 변경되는 현상
nextjs 는 img 태그에 복잡한 srcset 속성값을 지정해서 화면 크기별로 이미지를 조정해준다.
예를 들어 각 브라우저 마다 지원해주는 image 포멧이 다를 수 있다. 예를 들어 chrome 사용시 webP 를 사용해서 image 를 최적화 해주며, safari 같은 브라우저 사용시에는 jpeg 로 처리되는것을 볼 수 있다.
이러한 방식으로 각 image 를 받아, srcset 을 형성한후 그에 맞는 포멧으로 자동 변환해주는 매우 편리한 기능이다.
_app.js
_app.js 는 페이지 이동시 서로 다른 페이지간 상태 유지 및 전역 스타일 추가, 페이지 레이아웃 관리, 페이지 속성에 데이터를 추가할때 하용하는 page 이다.
이때, 중요한것은 _app.js 는 getServerSideProps 및 getStaticProps 사용이 불가능하다.
만약, 모든 페이지를 렌더링할대 마다 서버에서 특정 데이터를 가져와야 한다면, 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
여기서 App 의 getInitialProps 를 사용하여, appContext 의 props 를 가져온후, 추가할 props 를 server 로 부터 fetch한후 addProps 에 넣는다.
그리고 appProps 와 addProps 를 합친 객체를 반환하여 pageProps 로 전달하여 각 컴포넌트가 공통적으로 새롭게 생성된 props 를 받아 처리하는 로직이다.
_document.js
Next.js 에서 기본적으로 제공하는 HTML 태그를 지정할 필요가 있다. <head></head> 및 <body></body> 같은 테그들 말이다.
이때, 기본적으로 제공하는 HTML 틀을 처리하기 위한 파일이 _document.js 파일이다.
REST api
nextjs 를 사용하면서 REST 를 사용하여 data 를 주거니 받거니 할 수 있다.
이때, nextjs 는 servier 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 를 사용하여, network 의 headers 를 보면된면 authorization 의 값이 token 의 평문 문자열로 노출되는것을 볼 수 있다. 또한, CORS 로 인해, 처리가 되지 않을 수 도 있다.
이는 token 값을 숨기기위해 환경변수로 사용한것에 위배되며, 악용될 우려가 있다. 이러한 부분을 처리하기위해 nextjs 는 api 를 제공하여, 설정할 수 있도록 한다.
이 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 에서는 req 와 res 에대해서 다음처럼 설명한다.
- req:- http.IncommingMessage의 인스턴스 이며, 일부 미리 정의된- middlewares
- res:- http.serverResponse의 인스턴스 이며, 상태코드를 받아 조작이 가능하다.
위의 http 는 node 에서 제공하는 모듈이다. next.js 의 api 를 통해 처리되는 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 를 사용하여, nextjs 의 api 경로를 통해 userFetch 를 호출한다.
또한, req 의 query 를 전달하기 위해, query 에 대한 값을 query string 을 통해 전달한다.
이러한 식으로 api 를 proxy 형태로 request 가능하도록 구현할 수 있다. 이러한 부분역시 안전하지 못한 방법이라고 설명하며, server 에서만 렌더링하도록 하던지, 아니면, server 쪽 framework 를 통해 안전성을 높이는것이 최선이라고 한다.