- 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
를 통해 안전성을 높이는것이 최선이라고 한다.