리액트에서는 react-router-dom 라이브러리를 통해 페이지간 라우팅을 구현할 수 있었다.
그러나 넥스트(Next.js) 에서는 pages 폴더 안에 컴포넌트를 생성하면 자동으로 경로가 설정된다.
Next.js의 Router는 file-system 기반이다. pages/ 와 src/pages/ 가 동시에 존재한다면 pages/가 우선순위를 갖는다. pages 폴더가 존재한다면 src 폴더 내의 내용물은 무시된다고 생각하면 편하다.
Next.js의 Router는 file-system 기반이다. 폴더가 여러가지 생기면 그만큼 더 많은 depth가 생겨나게 된다.
ex) pages/products/first-item.js -> /products/first-item, pages/settings/my-info.js -> /settings/my-info 등
하지만 우리가 프로젝트를 만들다 보면 경로를 수정해야 할 경우도 생기는데 이럴 경우 절대 경로를 설정할 필요가 있다.
그럴 경우에 jsconifg.json 폴더를 만들고 아래처럼 작성해주면 src가 루트가 되었기 때문에 변경할때마다 상대경로를 지정하지 않아도 된다.
{"compilerOptions": {"baseUrl": "src"}}
만약 pages/products나 pages/settings에 접근하고 싶다면? 그 루트에 index.js가 존재해야 접근이 가능하다. 그렇지 않다면 우리가 만들어둔 file-system에만 접근이 가능하다.
slug는 다양한 위계의 Dynamic Routing을 제공한다.
slug는 대괄호를 사용하여 특정 값을 넣으면 그 값의 wild card처럼 동작한다.
예를들어 다음과 같은 방식이다.
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
export default function CategorySlug() {
return (
<>
<h1 className={styles.title}>CategorySlug</h1>
</>
)
}
CategorySlug.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
export default function UsernameInfo() {
return (
<>
<h1 className={styles.title}>UsernameInfo</h1>
</>
)
}
UsernameInfo.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
그렇다면 만약 /category/info로 이동하면 무엇이 출력될까? 답은 CategorySlug가 출력된다. 이렇게 slug로 매핑한 값은 명시해둔 값이 우선시된다.
만약 slug의 depth를 여러 depth로 사용하고 싶다면 아래처럼 사용하면 된다.
다만 이렇게 작성한 slug의 depth는 무한히 갈 수 있다.
그렇다면 [slug]를 어떻게 활용할 수 있을까? 실제로 슬러그 값이 주어지면 그 값에 따라 각기 다른걸 보여줘야 우리가 의도하는 페이지를 만들 수 있을 것이다.
useRouter 훅을 이용하면 하나의 slug를 가지고 다양한 페이지의 컨텐츠를 보여줄 수 있다.
// http://localhost:3000/category/info
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
import { useRouter } from 'next/router'
export default function CategorySlug() {
const router = useRouter();
const {slug} = router.query
return (
<>
<h1 className={styles.title}>{slug}</h1>
</>
)
}
CategorySlug.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
그렇다면 여러개의 query도 받을 수 있을까?
// http://localhost:3000/category/info?from=here&age=123
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
import { useRouter } from 'next/router'
export default function CategorySlug() {
const router = useRouter();
const {slug, from, age} = router.query
console.log(slug, from, age)
// info here 123
return (
<>
<h1 className={styles.title}>{slug} {from} {age}</h1>
</>
)
}
CategorySlug.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
저렇게 여러개의 query도 받을 수 있다.
만약 query에 slug가 있다면, page 구조 안에 있는 slug가 우선시 되어서 뒤에 있는 slug는 무시된다.
왜냐면 page 구조 안에 있는 slug는 file-system에서 우선적으로 잡아놓은 것이기 때문이다.
또한 query가 두가지라면 두가지 모두 넘어온다. (ex. /category/info?from=here&from=home => eventhome)
만약 pages/[username]/[info] 같은 형태의 다중 slug에서는 어떻게 될까?
// http://localhost:3000/jimmy/name
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
import { useRouter } from 'next/router'
export default function UsernameInfo() {
const router = useRouter();
const {username, info} = router.query
console.log(username, info) // jimmy name
return (
<>
<h1 className={styles.title}>{username}'s {info}</h1>
{/* jimmy's name */}
</>
)
}
UsernameInfo.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
pages/cart/[...slug]의 경우에는 배열로 받아진다.
// http://localhost:3000/cart/01/02/03
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
import { useRouter } from 'next/router'
export default function CartDateSlug() {
const router = useRouter()
const { slug } = router.query
console.log(slug)
// ['01', '02', '03']
return (
<>
<h1 className={styles.title}>CartDateSlug {JSON.stringify(slug)}</h1>
{/* ['01', '02', '03'] */}
</>
)
}
CartDateSlug.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
위의 예제에서 cart 뒤에 아무것도 주지 않으면 404 에러가 발생한다. 왜냐면 [...slug]에 아무런 값도 없기 때문이다.
하지만 index를 따로 만들지 않고 slug 값이 없더라도 404 에러를 커버할 수 있다.
폴더 [...slug].js를 대괄호로 한번 더 감싸주면 된다.
// http://localhost:3000/cart
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
import { useRouter } from 'next/router'
export default function CartDateSlug() {
const router = useRouter()
const { slug } = router.query
console.log(slug)
// undefined
return (
<>
<h1 className={styles.title}>CartDateSlug {JSON.stringify(slug)}</h1>
</>
)
}
CartDateSlug.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
이렇게 slug값이 존재하지 않아도 기본 페이지를 보여줄 수 있다.
대표적으로 두가지가 있다.
1. Link를 이용하는 방법
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function CartDateSlug() {
const router = useRouter()
const { slug } = router.query
return (
<>
<h1 className={styles.title}>CartDateSlug {JSON.stringify(slug)}</h1>
<Link href = "/cart/2022/06/05">2022년 6월 5일</Link>
</>
)
}
CartDateSlug.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function CartDateSlug() {
const router = useRouter()
const { slug } = router.query
return (
<>
<h1 className={styles.title}>CartDateSlug {JSON.stringify(slug)}</h1>
<Link href = "/cart/2022/06/05">2022년 6월 5일</Link>
<br/>
<button onClick={() => router.push("/cart/2022/06/24")}>2022년 6월 24일로</button>
</>
)
}
CartDateSlug.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}
Shallow Routing이란 getServerSideProps/getStaticProps등을 다시 실행시키지 않고 현재 상태를 잃지 않고 url을 바꾸는 방법이다.
import Layout from 'components/Layout'
import SubLayout from 'components/SubLayout'
import styles from '/styles/Home.module.css'
import { useState } from 'react'
import { useRouter } from 'next/router'
export async function getServerSideProps() {
console.log('서버에서 데이터 보내는중..')
return {
props: { },
}
}
export default function Info() {
const router = useRouter()
const [clicked, setClicked] = useState(false)
const { status = 'initial' } = router.query
return (
<>
<h1 className={styles.title}>Info</h1>
<h1 className={styles.title}>clicked {String(clicked)}</h1>
<h1 className={styles.title}>status {status}</h1>
<button
onClick={() => {
alert('edit')
setClicked(true)
location.replace('/settings/my/info?status=editing') -> 로컬 state 유지 안되고 리렌더링이 일어난다. 페이지를 아예 새로 로드하는것과 마찬가지
}}
>
edit(replace)
</button>
<br/>
<button
onClick={() => {
alert('edit')
setClicked(true)
router.push('/settings/my/info?status=editing') -> 로컬 state는 유지되지만, data fetching이 다시 일어난다.
}}
>
edit(push)
</button>
<br/>
<button
onClick={() => {
alert('edit')
setClicked(true)
router.push('/settings/my/info?status=editing', undefined, {shallow:true}) -> 로컬 state도 유지되고, date fetching도 일어나지 않는다.
}}
>
edit(shallow)
</button>
</>
)
}
Info.getLayout = function getLayout(page) {
return (
<Layout>
<SubLayout>{page}</SubLayout>
</Layout>
)
}