チュートリアル にしたがって脳死状態でコマンドを打ち込んでいきます
mkdir yoppibirthday2020 cd yoppibirthday2020 npm init -y npm install --save react react-dom next mkdir pages
package.json の scripts をちょっといじって以下のようにしてあげる
{ "name": "yoppibirthday2020", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "next", "build": "next build", "start": "next start" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "next": "^9.2.2", "react": "^16.13.0", "react-dom": "^16.13.0" } }
npm run dev
すると localhost:3000 でリクエストを待ち受けてくれるようになり、まだ何も作ってないので 404 が帰るようになる
pages/index.js
を以下のような内容で作成するとハロワ終了
export default function Index() { return ( <div> <p>Hello Next.js</p> </div> ); }
どうやら ホットリロードが何も記述しなくても働くっぽくて、該当のソースコードを保存すると自動的に更新される(便利)。そして pages/about.js
を作ると localhost:3000/about でアクセスできる。
next/link
を用いるとクライアントサイドのナビゲーションで遷移できる。 History に積まれるので普通に Back ボタンも動作する。
import Link from "next/link"; export default function Index() { return ( <div> <Link href="/about">About</Link> <p>Hello Next.js</p> </div> ); }
ルーティングの仕組みの詳細については次のページに記載があるようです: Introduction - Documentation | Next.js
The only special directories are /pages and /public.
特別なディレクトリは pages と public のみで、コンポーネントは特に決まった命名のディレクトリはなし。components
でも comps
でもなんでも良いようです。
components/Header.js
を以下のように作成して、index.js と about.js に Header コンポーネントを差し込むと共通ヘッダーが作成できる。
import Link from "next/link"; const linkStyle = { marginRight: 15 }; const Header = () => ( <div> <Link href="/"> <a style={linkStyle}>Home</a> </Link> <Link href="/about"> <a style={linkStyle}>About</a> </Link> </div> ); export default Header;
同様に使い回しの効くスタイルであるレイアウトコンポーネントも作成できる。たとえば components/MyLayout.js
を以下のように作成できる。
import Header from './Header'; const layoutStyle = { margin: 20, padding: 20, border: '1px solid #DDD' }; const Layout = props => ( <div style={layoutStyle}> <Header /> {props.children} </div> ); export default Layout;
なお、レイアウトコンポーネントは子要素を props にとったり、関数の引数にとったりすることもできる: Learn - Using Shared Components | Next.js
next/router
の useRouter
の query にクエリが格納されているらしい// pages/post.js import { useRouter } from "next/router"; import Layout from "../components/MyLayout"; const Page = () => { const router = useRouter(); return ( <Layout> <h1>{router.query.title}</h1> <p>This is the blog post content.</p> </Layout> ); }; export default Page;
/post?title=hoge
にアクセスすると タイトルに hoge と書かれた記事を表示できるcontents/[title].js
みたいな形でファイル作れば良いっぽい、なるほどなぁ。isomorphic-unfetch
を使うため、npm install isomorphic-unfetch
getInitialProps
に初期化に必要なデータ取得処理を記述するimport Link from "next/link"; import Layout from "../components/MyLayout"; import fetch from "isomorphic-unfetch"; const PostLink = props => ( <li> <Link href={`/contents/${props.title}`}> <a>{props.title}</a> </Link> </li> ); const Index = props => ( <Layout> <h1>Tasks</h1> <ul> {props.tasks.map(task => ( <li key={task.id}> <Link href="/p/[id]" as={`/p/${task.id}`}> <a>{task.title}</a> </Link> </li> ))} </ul> </Layout> ); Index.getInitialProps = async () => { await sleep(4000); const res = await fetch("https://jsonplaceholder.typicode.com/todos"); const data = await res.json(); return { tasks: data }; }; export default Index;
また getInitialProps でクエリの値などを利用したいときは次のように context.query
から引っ張ってこれる
import Link from "next/link"; import Layout from "../components/MyLayout"; import fetch from "isomorphic-unfetch"; const PostLink = props => ( <li> <Link href={`/contents/${props.title}`}> <a>{props.title}</a> </Link> </li> ); const Index = props => ( <Layout> <h1>Tasks</h1> <ul> {props.tasks.map(task => ( <li key={task.id}> <Link href="/p/[id]" as={`/p/${task.id}`}> <a>{task.title}</a> </Link> </li> ))} </ul> </Layout> ); Index.getInitialProps = async context => { const res = await fetch("https://jsonplaceholder.typicode.com/todos"); const data = await res.json(); const tasks = data.slice(0, context.query.limit); console.log(tasks); return { tasks: tasks }; }; export default Index;
<style jsx>{` h1, a { font-family: "Arial"; } `}</style>
As you have witnessed, CSS rules have no effect on elements inside of a child component. This feature of styled-jsx helps you to manage styles for bigger apps.
というわけで、グローバルに適用したいスタイルは style jsx global
で定義する
Next.js では pages/api
が API のルートであり、ここに例えば以下のような簡単な API を生やす
export default (req, res) => { const now = new Date().toISOString(); res.status(200).json({ date: now, message: "Hello" }); };
すると、以下のように簡単に API が生える
$ curl -w '\n' http://localhost:3000/api/hello {"date":"2020-03-01T11:58:29.589Z","message":"Hello"}
リモートフェッチ時には swr
がベンリ、ひとまず npm install swr
して、わかりやすさのために API に sleep をはさんどく
function sleep(sec) { return new Promise(resolve => setTimeout(resolve, sec * 1000)); } export default async (req, res) => { await sleep(1); const now = new Date().toISOString(); res.status(200).json({ date: now, message: "Hello" }); };
んで、 index.js
を次のようにすれば loading, error, onLoad で表示を手軽に変化させられる
import Link from "next/link"; import Layout from "../components/MyLayout"; import Markdown from "react-markdown"; import fetch from "isomorphic-unfetch"; import useSWR from "swr"; function fetcher(url) { return fetch(url).then(r => r.json()); } const PostLink = props => ( <li> <Link href={`/contents/${props.title}`}> <a>{props.title}</a> </Link> </li> ); const Index = props => { const { data, error } = useSWR("/api/hello?hoge", fetcher); let message = data?.message; if (!data) message = "Loading..."; if (error) message = "Failed to fetch time."; return ( <Layout> <Markdown source={` # Tasks time: ${message} **This is a list of my tasks.** Could you please help me? `} /> <ul> {props.tasks.map(task => ( <li key={task.id}> <Link href="/contents/[id]" as={`/contents/${task.id}`}> <a>{task.title}</a> </Link> </li> ))} </ul> <style jsx>{` h1, a { font-family: "Arial"; } ul { padding: 0; } li { list-style: none; margin: 5px 0; } a { text-decoration: none; color: blue; } a:hover { opacity: 0.6; } `}</style> </Layout> ); }; Index.getInitialProps = async context => { const res = await fetch("https://jsonplaceholder.typicode.com/todos"); const data = await res.json(); const tasks = data.slice(0, context.query.limit); console.log(tasks); return { tasks: tasks }; }; export default Index;
この API Routes には便利なように ビルトインのミドルウェア が組み込まれており、簡単にクエリパラメータ、リクエスト本文、クッキーなどを取得できる
brew install now-cli now
デプロイ終わった すご w
エクスポートにあたっては next.config.js
というファイルを作成する必要がある
module.exports = { exportTrailingSlash: true, exportPathMap: function() { return { '/': { page: '/' } }; } };
そして package.json
に以下を追加して npm run build && npm run export
"scripts": { "build": "next build", "export": "next export" }
成果物を確認するには serve
を使えばよい
npm install -g serve cd out serve -p 8080
next.config.js
は普通に JS ファイルなので、以下のような芸当も可能
const fetch = require("isomorphic-unfetch"); module.exports = { exportTrailingSlash: true, exportPathMap: async function() { const paths = { "/": { page: "/" }, "/about": { page: "/about" } }; const res = await fetch("https://jsonplaceholder.typicode.com/todos"); const tasks = await res.json(); tasks.forEach(task => { paths[`/contents/${task.id}`] = { page: "/contents/[id]", query: { id: task.id } }; }); return paths; } };
npm install --save-dev typescript @types/react @types/node
*.js
→ *.tsx
として npm run dev
すると勝手に tsconfig.json
が生成されるtsconfig.json
の strict を true にすると、型アノテーションが抜けてる際にエラーとなる以下のようにするだけ
export const config = { amp: true }; // export const config = { amp: 'hybrid' };