まずは以下のコマンドを叩く。
npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/basics-final"
空のtsconfig.json
を作成する。次に、以下のコマンドを叩くとtsconfig.json
が自動で書き換わる。
# どちらか一方を入力すれば実行される
npm run dev
yarn dev
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
次に、以下のコマンドでTypeScriptをインストールする。
# npm経由
npm install --save-dev typescript @types/react @types/node
# yarn経由
yarn add --dev typescript @types/react @types/node
ここで、以下のコマンドを入力して開発者サーバを立ち上げる。
npm start
サーバを起動すると、Nextjsは以下のファイルを作成する。
tsconfig.json
:上述の通り。next-env.d.ts
:これは、Nextjsの型がTypeScriptコンパイラに認識されるようにするファイル。決して編集してはいけない。
これでNextjsアプリでTypeScriptを利用できるようになった。
npm install @mui/material @emotion/react @emotion/styled
なんというか、.js
ファイルに型定義が追加されたような感じになる。
date.tsx
に変更して、以下のようにプログラムを変更する。
import { parseISO, format } from 'date-fns'
export default function Date({ dateString }: { dateString: string }) {
const date = parseISO(dateString)
return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
}
layout.tsx
に変更して、以下のようにプログラムを変更する。
import Head from 'next/head'
import Image from 'next/image'
import styles from './layout.module.css'
import utilStyles from '../styles/utils.module.css'
import Link from 'next/link'
const name = '[Your Name]'
export const siteTitle = 'Next.js Sample Website'
export default function Layout({
children,
home
}: {
children: React.ReactNode
home?: boolean
}) {
return (
<div className={styles.container}>
<Head>
<link rel="icon" href="/favicon.ico" />
<meta
name="description"
content="Learn how to build a personal website using Next.js"
/>
<meta
property="og:image"
content={`https://og-image.vercel.app/${encodeURI(
siteTitle
)}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.zeit.co%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}
/>
<meta name="og:title" content={siteTitle} />
<meta name="twitter:card" content="summary_large_image" />
</Head>
<header className={styles.header}>
{home ? (
<>
<Image
priority
src="/images/profile.jpg"
className={utilStyles.borderCircle}
height={144}
width={144}
alt={name}
/>
<h1 className={utilStyles.heading2Xl}>{name}</h1>
</>
) : (
<>
<Link href="/">
<a>
<Image
priority
src="/images/profile.jpg"
className={utilStyles.borderCircle}
height={108}
width={108}
alt={name}
/>
</a>
</Link>
<h2 className={utilStyles.headingLg}>
<Link href="/">
<a className={utilStyles.colorInherit}>{name}</a>
</Link>
</h2>
</>
)}
</header>
<main>{children}</main>
{!home && (
<div className={styles.backToHome}>
<Link href="/">
<a>← Back to home</a>
</Link>
</div>
)}
</div>
)
}
posts.ts
に変更し、以下のようにプログラムを変更する。
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { remark } from 'remark'
import html from 'remark-html'
const postsDirectory = path.join(process.cwd(), 'posts')
export function getSortedPostsData() {
// Get file names under /posts
const fileNames = fs.readdirSync(postsDirectory)
const allPostsData = fileNames.map(fileName => {
// Remove ".md" from file name to get id
const id = fileName.replace(/\.md$/, '')
// Read markdown file as string
const fullPath = path.join(postsDirectory, fileName)
const fileContents = fs.readFileSync(fullPath, 'utf8')
// Use gray-matter to parse the post metadata section
const matterResult = matter(fileContents)
// Combine the data with the id
return {
id,
...(matterResult.data as { date: string; title: string })
}
})
// Sort posts by date
return allPostsData.sort((a, b) => {
if (a.date < b.date) {
return 1
} else {
return -1
}
})
}
export function getAllPostIds() {
const fileNames = fs.readdirSync(postsDirectory)
return fileNames.map(fileName => {
return {
params: {
id: fileName.replace(/\.md$/, '')
}
}
})
}
export async function getPostData(id: string) {
const fullPath = path.join(postsDirectory, `${id}.md`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
// Use gray-matter to parse the post metadata section
const matterResult = matter(fileContents)
// Use remark to convert markdown into HTML string
const processedContent = await remark()
.use(html)
.process(matterResult.content)
const contentHtml = processedContent.toString()
// Combine the data with the id and contentHtml
return {
id,
contentHtml,
...(matterResult.data as { date: string; title: string })
}
}
[id].tsx
に変更する。
import Layout from '../../components/layout'
import { getAllPostIds, getPostData } from '../../lib/posts'
import Head from 'next/head'
import Date from '../../components/date'
import utilStyles from '../../styles/utils.module.css'
import { GetStaticProps, GetStaticPaths } from 'next'
export default function Post({
postData
}: {
postData: {
title: string
date: string
contentHtml: string
}
}) {
return (
<Layout>
<Head>
<title>{postData.title}</title>
</Head>
<article>
<h1 className={utilStyles.headingXl}>{postData.title}</h1>
<div className={utilStyles.lightText}>
<Date dateString={postData.date} />
</div>
<div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />
</article>
</Layout>
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const paths = getAllPostIds()
return {
paths,
fallback: false
}
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const postData = await getPostData(params.id as string)
return {
props: {
postData
}
}
}
index.tsx
に変更する。
import Head from 'next/head'
import Layout, { siteTitle } from '../components/layout'
import utilStyles from '../styles/utils.module.css'
import { getSortedPostsData } from '../lib/posts'
import Link from 'next/link'
import Date from '../components/date'
import { GetStaticProps } from 'next'
export default function Home({
allPostsData
}: {
allPostsData: {
date: string
title: string
id: string
}[]
}) {
return (
<Layout home>
<Head>
<title>{siteTitle}</title>
</Head>
<section className={utilStyles.headingMd}>
<p>[Your Self Introduction]</p>
<p>
(This is a sample website - you’ll be building a site like this in{' '}
<a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
</p>
</section>
<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}>
<Link href={`/posts/${id}`}>
<a>{title}</a>
</Link>
<br />
<small className={utilStyles.lightText}>
<Date dateString={date} />
</small>
</li>
))}
</ul>
</section>
</Layout>
)
}
export const getStaticProps: GetStaticProps = async () => {
const allPostsData = getSortedPostsData()
return {
props: {
allPostsData
}
}
}
_app.tsx
に変更する。
import '../styles/global.css'
import { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
hello.ts
に変更する。
import { NextApiRequest, NextApiResponse } from 'next'
export default (_: NextApiRequest, res: NextApiResponse) => {
res.status(200).json({ text: 'Hello' })
}