これから始めたNext.js

`Next.js`を触り始めたのでその過程を残しておこうと思います。書いたコードはgithubに上げてあります（[natsuhikok/next-react](https://github.com/natsuhikok/next-react)）。`Next.js`のexampleが充実してて迷わずに進めるところが好きでした。

## インストールして動かしてみます
インストールと`pages/index.js`だけで動くので`create-react-app`するより、らくちんかもしれません。

```zsh
$　npm install --save next react react-dom
```

`import React from 'react'`が必要ないのは少しきもいです。

```pages/index.js
export default () => <div>これから始めたNext.js</div>;
```

起動スクリプトは以下としました。

```package.json
"scripts": {
   "start": "next"
 },
```

`$ npm run start`でlocalhost:3000にnext.jsアプリが起動します。この時点でホットリロードも効いています。

後述にある`_document.js`などを新しく追加した場合にはアプリのリロードが必要です。変更したけど反映されないなどの場合は、一度アプリを起動し直してみると変なところでつまずかずに済むと思います。

### ビルドとproductionサーバー
ビルドしての本番運用がつらいと`Next.js`でSSRする意味が薄いので先に試してみます。

```package.json
"scripts": {
  "start": "next",
  "build": "next build",
  "production": "next start"
},
```
`next build`でビルドします。`next start`でbuildされた`.next`からサーバーを起動してくれます。pm2による永続起動は以下です。

```package.json
"scripts": {
    "start": "next",
    "build": "next build",
    "production": "next start",
    "pm2": "next build && pm2 start npm --name 'next' -- run production"
}
```

これで安心して他の部分を試していけます。

## styled-components
NextJSで`styled-components`を試したところ特定の条件下で以下のエラーがでてしまいました。

```
Expected server HTML to contain a matching <tag> in <tag>...
```

ExpressなどでReactをSSRしたときにも見かけるあれです。これは`babel-plugin-styled-components`をbabelに追加することで解決できます。

```　.babelrc
{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }]]
}
```

これでエラーは解決されますが、初回のロード時にスタイルが遅れてでるよ。状態になるので`_document.js`を追加します。[公式のexample](https://github.com/zeit/next.js/tree/canary/examples/with-styled-components) 通りです。


```_document.js
import Document from 'next/document';
import { ServerStyleSheet } from 'styled-components';

export default class MyDocument extends Document {
  static async getInitialProps (ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;
    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
        });
      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: [...initialProps.styles, ...sheet.getStyleElement()],
      };
    } finally {
      sheet.seal();
    }
  }
}
```

### createGlobalStyleはどこでするべきか
Metaデータを納めるコンポーネントを作って各ページで読み込むのがよさそうです。`_app.js`で読み込むこともできそうですが`_document.js`, `_app.js`はできる限り手を入れない方向で進めています。

```components/Meta.js
import Head from 'next/head';

export default ({ title }) => (
  <>
    <Head>
      <title>{title}</title>
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
      <meta charSet="utf-8" />
    </Head>
    <GlobalStyle />
  </>
);

import { createGlobalStyle } from 'styled-components';
import normalize from 'styled-normalize';

const GlobalStyle = createGlobalStyle`
  ${normalize}
  * { box-sizing: border-box; }
  html {
    font-size: 62.5%;
    font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Yu Gothic", YuGothic, Verdana, Meiryo, "M+ 1p", sans-serif;
  }
  input { border-radius: 0; }
  body {
    font-size: 1.4rem;
  }
  p, h1, h2, h3, h4, h5, h6 { margin: 0; }
  ul {
    margin: 0;
    padding: 0;
    list-style-type: none;
  }
`
```

GlobalStyleはタイミングで別コンポーネントにすることが多いです。

## redux
NextJSでReduxを使ってみます。生React+reduxとの違いは`_app.js`くらいかと思います。公式の[examle](https://github.com/zeit/next.js/tree/master/examples/with-redux-wrapper)を参考にしました。

```_app.js
import { Provider } from 'react-redux';
import App, { Container } from 'next/app';
import withRedux from 'next-redux-wrapper';
import makeStore from '../store/makeStore';

class ExtendedApp extends App {
  static getInitialProps = async ({ Component, ctx }) => ({
    pageProps: (Component.getInitialProps ? await Component.getInitialProps(ctx) : {})
  })
  render () {
    const { Component, pageProps, store } = this.props;
    return (
      <Container>
        <Provider store={store}>
          <Component {...pageProps} />
        </Provider>
      </Container>
    );
  }
}

export default withRedux(makeStore)(ExtendedApp);

```

```store/makeStore.js
import { combineReducers, createStore } from 'redux';
import { message } from './message';

const rootReducer = combineReducers({ message });

export default initialState => createStore(rootReducer, initialState);
```

## recompose
NextJSの拡張である`getInitialProps`を`recompose`で扱います。

```pages/index.js
import { connect } from 'react-redux';
import { compose, withState, setStatic, pure, withHandlers } from 'recompose';

import Navigation from '../components/Navigation';
import Meta from '../components/Meta';
import { updateMessage } from '../store/message';

const Index = ({ message, handleClick, inputValue, handleInputChange }) => (
  <>
    <Meta title="これから始めたNext.js" />
    <Navigation />
    <Headline>これから始めたNext.js</Headline>
    <p>{message}</p>
    <input value={inputValue} onChange={handleInputChange} />
    <button onClick={handleClick}>submit</button>
  </>
);

export default compose(
  setStatic('getInitialProps', async props => {
    const { isServer } = props;
    if(!isServer) {
      console.log(props);
    }
  }),
  withState('inputValue', 'setInputValue', ''),
  connect(
    ({ message }) => ({ message: message.message }),
    dispatch => ({
      updateMessage: value => dispatch(updateMessage(value)),
    }),
  ),
  withHandlers({
    handleClick: ({ setInputValue, updateMessage, inputValue }) => () => {
      updateMessage(inputValue);
      setInputValue('');
    },
    handleInputChange: ({ setInputValue }) => e => setInputValue(e.target.value),
  }),
  pure,
)(Index);

import styled from 'styled-components';
const Headline = styled.h1`
  font-size: 32px;
  line-height: 1.8;
  border-bottom: 4px dotted blue;
`;
```

## Dynamic URL
`user/:id`のような動的なルーティングが必要です。残念ながらNextJSでこれを実現するためには`server.js`を追加するなど自前でカスタムサーバー・ルーティングする必要があります。ここでは最小限で実装できる`user?id=id`の方式を試してみます。気持ちダサいですがSEO的には問題のない方法です。

queryは`getInitialProps`から取得できるのでこれをViewに渡します。

```pages/users.js
import { compose, setStatic, pure } from 'recompose';
import Navigation from '../components/Navigation';
import Meta from '../components/Meta';

const Posts = ({ id }) => (
  <div>
    <Meta title="これから始めたNext.js" />
    <Navigation />
    <h1>これは{id}番目のユーザーページです。</h1>
  </div>
);

export default compose(
  setStatic('getInitialProps', async props => {
    const { query: { id } } = props;
    return { id };
  }),
  pure,
)(Posts);
```
`query`を指定したリンクは`next/Link`で作ることができます。

```components/Navigation.js
import Link from 'next/link';

export default () => (
  <ul>
    <li><Link href="/"><a>home</a></Link></li>
    <li><Link href="/about"><a>About</a></Link></li>
    <li>
      <Link href={{ pathname: '/users', query: { id: 3 } }}>
        <a>User</a>
      </Link>
    </li>
  </ul>
);
```

## 参考
[zeit/next.js](https://github.com/zeit/next.js/)
[Example app with styled-components](https://github.com/zeit/next.js/tree/canary/examples/with-styled-components)
[Redux example](https://github.com/zeit/next.js/tree/master/examples/with-redux-wrapper)
[how can I use next in pm2?](https://github.com/zeit/next.js/issues/109)