2
6

More than 3 years have passed since last update.

Next チュートリアルやったよ

Last updated at Posted at 2020-06-14

Gatsbyに引き続きNext.jsのチュートリアルもやってみたので、
覚えたことをまとめておきます。

【公式】https://nextjs.org/learn/basics/create-nextjs-app

Link

Next.jsのページ内リンクは下記のように呼び出す。

import Link from 'next/link'

~
<Link href="/posts/first-post"><a>this page!</a></Link>
~

スタイルを効かせたい時はaタグの方につけることに注意。

Asset

assetファイルはpublicファイルに置くことで呼び出せる。
呼び出すときのルートはpublicからスタート。


<img src="/vercel.svg" alt="Vercel Logo" className="logo" />

robots.txtや認証系なんかもここに置くと便利。

メタデータ

メタデータを変更したい場合は下記のようにする。

import Head from 'next/head'

~
<Head>
 <title>タイトル</title>
</Head>
~

htmlタグをカスタマイズする場合はDocumentコンポーネントを使う。
下記ソースは以下の公式から抜粋。詳しい解読はまた次回。
https://nextjs.org/docs/advanced-features/custom-document

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

CSS

Styled-jsxが最初から入っている。もちろんstyled-componentsやemotionも使える。

CSSモジュールを使う際は、.module.cssを末尾につけてcssファイルを生成する。
importするときは下記のように、stylesで読み込み、 styles.<class-name> で使用する。

import styles from './layout.module.css'

export default function Layout({ children }) {
 return <div className={styles.container}>{children}</div>

globalモジュールを作成する場合は、

page/_app.js ファイルを作成し、下記のような高階コンポーネントを作成し、そこでCSSモジュールを読み込む。

page/_app.js
import '../styles/global.css'
export default function App({ Component, pageProps }){
    return <Component {...pageProps} />
}

_app.js追加はホットリロードに対応していないので、サーバーを立ち上げ直す。

postCSSの設定をカスタマイズする場合は、一番上の階層にpostcss.config.jsを作成する。

autoprefixerは最初からNextに組み込まれている。

Sassのモジュールも使用可能(.module.scss,module.sassなど)
使用する際はsassをinstallする必要がある。

pre-rendering

Nextでは、事前にHTMLを生成するpre-renderingを行なっている。
生成されたページはhydrationされる。

データを使用して静的ページを生成する場合(外部APIのfetch,データベースへのアクセス等)、
getStaticPropsを使用する。

getStaticPropsを使用することで、pre-renderする際にfetchしてからHTMLを生成するようになる。

データをもとに静的ページを生成する場合の例

posts以下にmdファイルを置いた場合

lib/posts.jsに下記を貼る

lib/posts.js
import fs from fs
import path from 'path'
import matter from 'gray-matter'

const postsDirectory = path.join(process.cwd(), 'posts')

export function getSortedPostsData() {
  const fileNames = fs.readdirSync(postsDirectory)
  const allPostsData = fileNames.map(fileName => {
    const id = fileName.replace(/\.md$/, '')

    const fullPath = path.join(postsDirectory, fileName)
    const fileContents = fs.readFileSync(fullPath, 'utf8')

    const matterResult = matter(fileContents)

    return {
      id,
      ...matterResult.data
    }
  })

  return allPostsData.sort((a, b) => {
    if (a.date < b.date) {
      return 1
    } else {
      return -1
    }
  })
}

gray-matterはmdファイルのメタデータをjsonに変換してくれるパッケージ。
sortすることによって、記事の順番を日付が最新のものが先頭に来るように調節している。

import Head from 'next/head'
import Layout, { siteTitle } from '../components/layout'
import utilStyles from '../styles/utils.module.css'
import { getSortedPostsData } from '../lib/posts'

export default function Home({ allPostsData }) {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
      <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  )
}

export async function getStaticProps() {
  const allPostsData = getSortedPostsData()
  return {
    props: {
      allPostsData
    }
  }
}

gatsbyに比べて全部手動で追加/加工しないといけないような感じ。
でもその分柔軟性がありそう。

リクエスト時のデータフェッチ

この場合はSSRを試す必要がある。

SSRではgetStaticPropsの代わりにgetServerSidePropsを使用する。

dataをPre-renderingする必要がないのであれば、CSRでも対応できる。
ダッシュボードなどのSEOが関係ない部分ではオススメ

NextではSWRというデータ取得用のreactフックが用意されているので、CSRでやる場合はオススメ

Dynamic Route

動的ルート生成するには、 pages/postsの下に [ ]で囲ったファイルを作る [id].jsなど。

getStaticPaths()を使用する。

lib/posts.jsに下記を追加する。

lib/posts.js
export function getAllPostIds() {
  const fileNames = fs.readdirSync(postsDirectory)
  return fileNames.map(fileName => {
    return {
      params: {
        id: fileName.replace(/\.md$/, '')
      }
    }
  })
}

export function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`)
  const fileContents = fs.readFileSync(fullPath, 'utf8')

  const matterResult = matter(fileContents)

  return {
    id,
    ...matterResult.data
  }
}

getAllPostIdsにて守らなければならないのは、returnが必ずオブジェクトになっていること、そしてparamsがあり、そのオブジェクトには[]の中で指定したkeyがあること。

idを受けてファイルデータを取得する関数も作っておく。

pages/post/[id].jsは下記。

pages/post/[id].js
import Layout from '../../components/layout'
import { getAllPostIds, getPostData } from '../../lib/posts'

export default function Post({ postData }) {
  return (
    <Layout>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
    </Layout>
  )
}


export async function getStaticPaths() {
  const paths = getAllPostIds()
  return {
    paths,
    fallback: false
  }
}

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id)
  return {
    props: {
      postData
    }
  }
}

getStaticPathではidのリストとfallbackを指定する。
getStaticPropsで作成したデータがpageのpropsとして渡される。
idからファイルデータを取得するgetPostDataを使用し、propsとして渡す。

mdのメタデータ以外の取得

npm install remark remark-html

そして、/lib/postsで

lib/posts
export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`)
  const fileContents = fs.readFileSync(fullPath, 'utf8')

  const matterResult = matter(fileContents)

  const processedContent = await remark()
    .use(html)
    .process(matterResult.content)
  const contentHtml = processedContent.toString()

  return {
    id,
    contentHtml,
    ...matterResult.data
  }
}

あとは[id].js内でpostData.contentHtmlとして取得すればOK。

Indexにリンクを貼る場合

<Link href="/posts/[id]" as="/posts/ssg-ssr">
  <a>...</a>
</Link>

この形で用いる。他は[id].jsと同じ。

          {allPostsData.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              <Link href="/posts/[id]" as={`/posts/${id}`}>
                <a>{title}</a>
              </Link>
              <br />
              <small className={utilStyles.lightText}>
                <Date dateString={date} />
              </small>
              {id}
              <br />
              {date}
            </li>
          ))}
...


export async function getStaticProps() {
  const allPostsData = await getSortedPostsData()
  return {
    props: {
      allPostsData
    }
  }
}

ファイル名に ...を使うこともできる。
pages/posts/[...id].jsは /posts/a, /posts/a/b などにマッチする。

getStaticPathsで/posts/a/b/cを作成するには配列を用いる。

return [
  {
    params: {
      // Statically Generates /posts/a/b/c
      id: ['a', 'b', 'c']
    }
  }
  //...
]

404ページの作成

page/404.jsを作ればOK。

APIルートの作成

pages/apiファイルの中に以下のようなfunctionを作成する。

export default (req, res) => {
  res.status(200).json({ text: 'Hello' })
}

あとは/api/ファイル名にアクセスするだけ!
getStaticPropsやgetStaticPathsではfetchできないので注意。
なぜならこの2つはサーバー側でのみ走るため。

Gatsbyと比べてみて

Nextは公式でexampleが豊富に準備されているのでありがたいです。
https://github.com/vercel/next.js/tree/2cd691050af6b2bb75d6e6ab7c63efcea7074f1b/examples

Gatsbyはスターターが優秀で、スターターを元に基本的なことはすぐできちゃう!
という印象を受けました。
Gatsbyのように基本に特化していない分Nextは柔軟性があるように感じました。
Gatsbyは複雑な処理でなければ内部で使われているパッケージを直接扱わないが、nextでは直接扱う場面が多かったことからもわかります。(grey-matterやremarkなど)

それからgatsbyはgraph.qlが使われる前提の設計なので、好みが分かれそうだと思いました。

スピード優先・そこまで複雑なことをしない/graph.qlを手軽に扱いたいのであればGatsby、規模がそれなりにあったり複雑である or Graph.qlに縛れられたくないのであればNextかな、という感じです。
これから実際に色々使用してみて、また比較をまとめれたらと思います。

2
6
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
2
6