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モジュールを読み込む。
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に下記を貼る
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に下記を追加する。
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は下記。
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で
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かな、という感じです。
これから実際に色々使用してみて、また比較をまとめれたらと思います。