LoginSignup
6
10

More than 5 years have passed since last update.

Next.js Tutorial

Last updated at Posted at 2018-07-20

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

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

import { Layout } from './shared'

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

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

yarn next

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

Jul-20-2018 08-33-42.gif

Navigate Between Pages & Using Shared Components

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

  • http://localhost:3000/
  • http://localhost:3000/about

create pages/about.js

import { Layout } from './shared'

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

edit pages/shared.js

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

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

edit pages/index.js

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

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

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

   <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

 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

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

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 を設定する必要があります。

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 を使って自前で実装する事もできます。

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

6
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
10