[nextjs] Nextjs 시작하기 (File-based Routing)

dev stefanCho·2021년 12월 26일
0

nextjs

목록 보기
3/9
post-custom-banner

Nextjs 설치하기

npx create-next-app@latest
# or
yarn create next-app

# 만약 실행안되면 시도
npm install 

# 예제 코드가 필요하면 아래로 설치
npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"

Nextjs의 특징

Server-Side-Rendering

자동으로 페이지를 pre-rendering 합니다.

  • SEO와 최초 로드에 유리합니다.
  • 서버에서 fetch한 데이터로 완성된 페이지를 랜더링 합니다.

File-based Routing

페이지와 라우트를 file과 folder 베이스로 정의할 수 있습니다.

  • 코드량을 줄이고, 이해하기 쉬운 구조로 만들 수 있습니다.

Fullstack Capabilities

백엔드를 Nextjs에서 구성할 수 있습니다.

  • 데이터를 저장하고, 가져오고, authentication까지 React Project에 추가할 수 있게 됩니다.

nextjs와 일반 react app의 index.html 차이점

Nextjs의 public 폴더에는 index.html이 없습니다.

File-based Routing

File-based Routing (NextJS) <--> Code-based Routing (React + react-router)
pages 컴포넌트 이름에는 postfix로 Page를 주로 붙입니다.

directory구조 Example

Basic Example

pages
├── _app.js
├── about
│   ├── index.js (pages/about.js와 동일한 route를 가짐)
│   └── [id].js (dynamic route, id가 아니어도 상관없음)
├── about.js (pages/about/index.js와 동일한 route를 가짐)
└── index.js (HomePage, 즉 rootpage이다.)

Advanced Example

pages
├── 404.js		// 404가 발생하면 여기로 보게 된다. (NotFoundPage)
├── _app.js
├── blog
│   └── [...slug].js // 1. catch all-routes (아래 설명 참고)
├── clients
│   ├── [id]		// directory로도 dynamic route가 가능하다.
│   │   ├── [clientprojectid].js // 2. nested-dynamic routes (아래 설명 참고)
│   │   └── index.js
│   └── index.js
├── index.js
└── portfolio
    ├── [projectid].js
    ├── index.js
    └── list.js

1. catch all-routes

[...some-ids].js으로 dynamic page를 만들면, 아래에 있는 모든 query 값을 다 받아올 수 있습니다.

https://my-domain.com/blog/a/b/c/d 에 접속했다고 가정해봅니다. 아래 예시 같이 array로 모든 path 값을 다 받아올 수 있습니다.

  • 예를 들면 /blog/2021/10/30 처럼 2021년 10월 30일의 블로그 path가 이렇다고 하면, ['2021', '10', '30']로 받아서 이걸로 API 조회를 해볼 수 있습니다.
import Link from 'next/link';
import { useRouter } from 'next/router'

export default function BlogPostPage() {
  const router = useRouter();
  // catch all routes
  console.log(router.query); // {"slug": ["a", "b", "c", "d"]}
  return (
    <div>
      <Link href="/" replace>You Can't Go Back</Link>
      <h1>The Blog Post</h1>
    </div>
  )
}

2. nested-dynamic routes

디렉토리 구조에서 연달아서 dynamic page를 만든 경우입니다.
자신을 포함한 상위의 dynamic page의 query를 받아올 수 있습니다.
https://my-domain.com/clients/10/project-a 에 접속했다고 가정합니다.

아래와 같이 자신(clientprojectid)을 포함한 상위(id)에 대해 query를 다 받아올 수 있습니다.

// pages/clients/[id]/[clientprojectid].js
import { useRouter } from 'next/router';

export default function SelectedClientProjectPage() {
  const router = useRouter();
  console.log(router.query) // {id: '10', clientprojectid: 'project-a'}
  return (
    <div>
      <h1>The Project Page for a specific project</h1>
    </div>
  )
}

// pages/clients/[id]/index.js
import { useRouter } from 'next/router';

export default function ClientProjectPage() {
  const router = useRouter();
  console.log(router.query) // {id: '10'}
  return (
    <div>
      <h1>The Project Page</h1>
    </div>
  )
}

Pages Directory

Example

pages/news/index.js : our-domain.com/news
pages/news/sbs : ourdomain.com/news/sbs

# dynamic pages
pages/news/[newId].js : ourdomain.com/news/[newsId] 
pages/news/[newId]/index.js : ourdomain.com/news/[newsId]

Layout.js를 pages의 컴포넌트마다 wrapping 하는 것은 비효율적이고 귀찮은 일입니다.

_app.js을 사용하면 모든곳에 적용할 수 있습니다. 모든 page의 최초 진입 root가 되는 파일이 _app.js입니다.

_app 컴포넌트에서는 props로 Component, pageProps를 자동으로 받습니다. Component는 rendering될 실제 page component를 의미하고, pageProps는 page component가 받게 될 props를 의미합니다.

import '../styles/globals.css'

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

export default MyApp

Layout을 적용할 수 있다.

import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

export default MyApp

동적 페이지 예시

/events 
- 모든 event를 다 보여주는 페이지

/events/[some-id] 
- 특정 event에 대한 페이지 (show selected Event)

/events/[...slug]
- 조건에 의해 필터된 특정 event의 페이지 (show filtered Events)

...slug와 some-id는 충돌되지 않습니다.
/events/1 이라면 some-id로 진입됩니다. (Nextjs에는 어떤것이 더 specific한 path인지 알고 있기 때문입니다.)

index.js : root page로 가장 기본 url(/) 진입시 index파일이 랜더링 됩니다.

hooks

useRouter

useRouter에서 사용하는 기본적인 property 설명입니다.

  • router.query로 url value에서 값 가져오기
import { useRouter } from 'next/router';
...
function DetailPage() {
  const router = useRouter();
  router.query.newsId;
  
  return (...)
}
  • router.push로 url 이동시키기 (Link와 같은 기능)
import router, { useRouter } from 'next/router';
import Card from '../ui/Card';
import classes from './MeetupItem.module.css';

function MeetupItem(props) {
  const showDetailsHandler = () => {
    router.push('/' + props.id);
  }

  return (
    <button onClick={showDetailsHandler}>Show Details</button>
  );
}

export default MeetupItem;
// pages/portfolio/[id].js

console.log(router.pathname) // /portfolio/[id]

useRouter에서 query를 가져오는 타이밍

아래 코드를 실행하면 최초에는 콘솔에서 undefined가 출력됩니다.

// pages/events/[...slug].js
import { useRouter } from 'next/router';

function FilteredEventsPage() {
  const router = useRouter();
  const filterData = router.query.slug;
  console.log(filterData);

  return (
    <div>
      <h1>Filtered Events</h1>
    </div>
  )
}

export default FilteredEventsPage

예를들어, /events/2021/10 에 진입시에

위와 같이 나오게 됩니다. 컴포넌트가 최소 2번 랜더링 되는 것인데, 처음에 undefined가 나오는 것은 최초에는 router.query의 값은 최초에 랜더링이 한번 된 다음에 알 수 있습니다. 최초에 컴포넌트가 랜더링될때는 useRouter가 path parameter 정보를 갖고 있지 않기 때문입니다.

public image

Nextjs내에서는 public 폴더 외에는 visitor가 파일을 로드할 수 없습니다.
public folder는 Nextjs에 의해서 static하게 served되기 때문에, public 경로를 붙일 필요가 없습니다.

<img src={'/'+ 'shop.jpeg'} alt="image" />

a tag 방식은 server에 new request를 보내므로, SPA 방식이 아닙니다. (크롬 탭을 보면 refresh 됨)

<a href='/news/nextjs-start/'>
  Go To Link
</a>

대신 nextjs의 Link component를 사용해야만 합니다.
(링크 클릭으로 이동시에는 SPA이어야 하기때문에 new request를 보내지 않고, 크롬에서 refresh하더라도 finished(완성된) html page에 들어가게 됨 => SEO에 적합)

import Link from 'next/link';

...
<Link href='/news/nextjs-start/'>
  Go To Link
</Link>

Link의 방식은 2가지가 있습니다.
(두가지 방식에 대해서는 선호하는 쪽으로 사용하면 된다.)
첫번째로 우리가 React에서 사용하던 literal string을 이용한 방식이다.

import Link from 'next/link';

const clients = [
  { id: 'cho', name: 'CHO' },
  { id: 'heo', name: 'HEO' },
]

export default function ClientPage() {
  return (
    <div>
      <h1>Client Page</h1>
      <ul>
        {clients.map((client) => (
          <li key={client.id}>
            <Link href={`/clients/${client.id}`}>
              {client.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  )
}

두번째로 href에는 object를 넣을 수 있다. pathname과 query를 맞춰서 넣어줄 수 있다.

import Link from 'next/link';

const clients = [
  { id: 'cho', name: 'CHO' },
  { id: 'heo', name: 'HEO' },
]

export default function ClientPage() {
  return (
    <div>
      <h1>Client Page</h1>
      <ul>
        {clients.map((client) => (
          <li key={client.id}>
            <Link
              href={{
                pathname: "/clients/[id]",
                query: { id: client.id }
              }}
            >
              {client.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  )
}

Link를 갖고 있는 Button 만들기

Link아래에 a tag넣습니다.

import Link from 'next/link';
import styles from './button.module.css';

function Button(props) {
  return (
    <div>
      <Link href={props.link}>
        <a className={styles.btn}>{props.children}</a>
      </Link>
    </div>
  )
}

export default Button
profile
Front-end Developer
post-custom-banner

0개의 댓글