Next.js Tutorial

# Next.js Tutorial

## 概要

Next.js を使って以下のことをやりたい場合の Tutorial です。

* Server Side Rendering
* 静的ファイルへの出力

## Getting Started

最初にプロジェクト用のファイルを用意します。

```
mkdir practice-next.js
cd practice-next.js
yarn init -y
yarn add react react-dom next express isomorphic-unfetch --save
yarn add http-server --dev
mkdir -p pages
touch pages/{index.js,about.js,shared.js} server.js next.config.js
```

create `pages/shared.js`

```jsx
const layoutStyle = { 
  margin: 20, 
  padding: 20, 
  border: '1px solid #DDD'
}

export const Layout = (props) => (
  <div style={layoutStyle}>
    <p>rendered by: {getRenderedBy()}</p>
    {props.children}
  </div>
)

export function getRenderedBy() {
  return (typeof window !== 'undefined' && window.document) ? 'client' : 'server';
}
```

create `pages/index.js`

```jsx
import { Layout } from './shared'

export default () => (
  <Layout>
    <p>This is the index page</p>
  </Layout>
)
```

この状態で以下のコマンドを実行します。

```
yarn next
```

以下の画面で `server` と `client` の文字が切り替わるポイントがあると思います。これは画面を reload した直後は　Server Side Rendering されていて、直後に Single Page Application に置き換えられているからです。

![Jul-20-2018 08-33-42.gif](https://qiita-image-store.s3.amazonaws.com/0/27622/67bea8c8-7fae-0364-b4f5-f0f8ea5652e7.gif)

## Navigate Between Pages & Using Shared Components

先ほどは単一のページでしたので、今度は複数のページを用意して、画面を行き来できるようにします。about 画面を追加して、以下の画面への導線を準備します。

* `http://localhost:3000/`
* `http://localhost:3000/about`

create `pages/about.js`

```jsx
import { Layout } from './shared'

export default () => (
  <Layout>
    <p>This is the about page</p>
  </Layout>
)
```

edit `pages/shared.js`

```jsx
import Link from 'next/link'

const linkStyle = {
  marginRight: 15
}

export const Header = () => (
  <div>
    <Link href="/">
      <a style={linkStyle}>Home</a>
    </Link>
    <Link href="/about">
      <a style={linkStyle}>About</a>
    </Link>
  </div>
)

const layoutStyle = { 
  margin: 20, 
  padding: 20, 
  border: '1px solid #DDD'
}

export const Layout = (props) => (
  <div style={layoutStyle}>
    <Header />
    {props.children}
  </div>
)

export function getRenderedBy() {
  return (typeof window !== 'undefined' && window.document) ? 'client' : 'server';
}
```

以下のコマンドを実行して動作確認をしてください。

```
yarn next
```

## Create Dynamic Pages

動的なページを作ります。ここでは簡単にダイナミックなページを試すために `/post?title=xxxxxx` のようにクエリーパラメータを利用します。

edit `pages/shared.js`

```diff
+ export const PostLink = (props) => (
+  <Link href={`/post?title=${props.title}`}>
+    <a style={linkStyle}>{props.title}</a>
+  </Link>
+ )
```

edit `pages/index.js`

```jsx
import { Layout, PostLink } from './shared'

export default () => (
  <Layout>
    <p>This is the index page</p>
    <ul>
      <li><PostLink title="Hello Next.js"/></li>
      <li><PostLink title="Learn Next.js is awesome"/></li>
      <li><PostLink title="Deploy apps with Zeit"/></li>
    </ul>
  </Layout>
)
```

create `pages/post.js`

```jsx
import fetch from 'isomorphic-unfetch'
import { Layout } from './shared';

export default withRouter((props) => (
  <Layout>
   <h1>{props.router.query.title}</h1>
   <p>This is the blog post content.</p>
  </Layout>
))
```

以下のコマンドを実行します。

```
yarn next
```

トップページから動的ページ(post.js)への導線が機能していることを確認してください。

## Server Side Support to create many routes

今のままだと `/post?title=Learn%20Next.js%20is%20awesome` のように　URL が少しスマートではないので、これを `/p/learn-nextjs` のように修正します。

create `server.js`

```jsx
const express = require('express')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare()
.then(() => {
  const server = express()

  server.get('/p/:id', (req, res) => {
    const actualPage = '/post'
    const queryParams = { title: req.params.id } 
    app.render(req, res, actualPage, queryParams)
  })

  server.get('*', (req, res) => {
    return handle(req, res)
  })

  server.listen(3000, (err) => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')
  })
})
.catch((ex) => {
  console.error(ex.stack)
  process.exit(1)
})
```

edit `pages/index.js`

```diff
   <Layout>
     <p>This is the index page</p>
     <ul>
-      <li><PostLink title="Hello Next.js"/></li>
-      <li><PostLink title="Learn Next.js is awesome"/></li>
-      <li><PostLink title="Deploy apps with Zeit"/></li>
+      <li><PostLink id="hello-next" title="Hello Next.js"/></li>
+      <li><PostLink id="learn-next" title="Learn Next.js is awesome"/></li>
+      <li><PostLink id="deploy" title="Deploy apps with Zeit"/></li>
     </ul>
   </Layout>
```

edit `pages/shared.js`

```diff
 export const PostLink = (props) => (
- <Link href={`/post?title=${props.title}`}>
-   <a style={linkStyle}>{props.title}</a>
- </Link>
+  <Link as={`/p/${props.id}`} href={`/post?title=${props.title}`}>
+    <a style={linkStyle}>{props.title}</a>
+  </Link>
 )
```

今まで `yarn next` を実行していましたが、今度は以下のコマンドを実行します。

```
node server.js
```

先ほどと同じ画面ですが URL が変わっていることを確認してください。

## Fetching Data for Pages

さて、非同期処理でデータを取得してレンダリングを行います。非同期処理が加わると SSR を実装するときに手間がかかるのですが、 Next.js がその手間を軽減してくれます。

今回自分で API を実装しないで jsonplaceholder の API を使用しています。

edit `pages/index.js`

```jsx
import Link from 'next/link'
import fetch from 'isomorphic-unfetch'

import { Layout, PostLink } from './shared'

const Index = (props) => (
  <Layout>
    <p>This is the index page</p>
    <ul>
      {props.posts.map(post => (
        <li key={post.id}>
          <Link as={`/p/${post.id}`} href={`/post?id=${post.id}`}>
            <a>{post.title}</a>
          </Link>
        </li>
      ))}
    </ul>
  </Layout>
)

Index.getInitialProps = async function() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  const posts = await res.json()

  console.log(`posts count: ${posts.length}`)

  return { posts }
}

export default Index

```

edit `pages/post.js`

```jsx
import { Layout } from './shared';
import fetch from 'isomorphic-unfetch'

const Post = (props) => {

  return (
  <Layout>
   <h1>{props.post.title}</h1>
   <p>{props.post.body}</p>
  </Layout>
  )
}

Post.getInitialProps = async function (context) {
  const { id } = context.query
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
  const post = await res.json()
  console.log(`Fetched post: ${post.title}`)
  return { post }
}

export default Post
```

getInitialProps に非同期処理を記述しておけば面倒な SSR の設定を next.js がよしなにしてくれます

## Export into a Static HTML App

`next.config.js` に exportPathMap を設定する必要があります。

```jsx
const fetch = require('isomorphic-unfetch')

module.exports = { 
  exportPathMap: async function () {
    console.log("exportPathMap");
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts`)
    const posts = await res.json()
    const ids = posts.map(x => x.id);

    const route = { 
      '/': { page: '/' },
      '/about': { page: '/about' },
    }   

    ids.forEach(id => {
      route[`/p/${id}`] = { page: '/post', query: { id } } 
    })  

    return route
  }
}
```

静的ファイルを出力

```
yarn next build
yarn next export
```

動作確認

```
yarn http-server out
```

## まとめ

Next.js を使って Server Side Rendering と静的ファイルの生成を試しました。Single Page　Application にしつつ SEO に弱くならないようにするために SSR を採用したい場面に遭遇した場合は next.js のことを思い出すと良いでしょう。

また、Gatsby のように React component を再利用できる Static Generator がすでに存在していますが、その独特の作法に疲れてしまう人はいっその事 next.js を使って自前で実装する事もできます。

というわけで使い方を覚えておいて損しないツールだと思います。