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"
자동으로 페이지를 pre-rendering 합니다.
페이지와 라우트를 file과 folder 베이스로 정의할 수 있습니다.
백엔드를 Nextjs에서 구성할 수 있습니다.
Nextjs의 public 폴더에는 index.html이 없습니다.
File-based Routing (NextJS) <--> Code-based Routing (React + react-router)
pages 컴포넌트 이름에는 postfix로 Page를 주로 붙입니다.
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이다.)
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
[...some-ids].js
으로 dynamic page를 만들면, 아래에 있는 모든 query 값을 다 받아올 수 있습니다.
https://my-domain.com/blog/a/b/c/d 에 접속했다고 가정해봅니다. 아래 예시 같이 array로 모든 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>
)
}
디렉토리 구조에서 연달아서 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/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파일이 랜더링 됩니다.
useRouter에서 사용하는 기본적인 property 설명입니다.
import { useRouter } from 'next/router';
...
function DetailPage() {
const router = useRouter();
router.query.newsId;
return (...)
}
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]
아래 코드를 실행하면 최초에는 콘솔에서 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 정보를 갖고 있지 않기 때문입니다.
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아래에 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