1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Next+ChakraUI+GitHub Pagesで静的サイトを作った & Markdownで内容を更新できるようにした

1
Last updated at Posted at 2023-06-19

注:Web系の技術をまんべんなく初心者レベルな人間が書いています

書いた目的

ぱっとQiitaを見た感じ2023年時点でPagesを使ったデプロイがなかったのとNextを触ってみたかったのでその開発時の困ったところとかの備忘録

ディレクトリ構成

.
├── components
│   ├── Footer.tsx
│   ├── MarkdownContent.tsx # マークダウンを解析してChakra UIに変換
│   └── NavBar.tsx
├── markdown # マークダウン置き場
│   ├── blogs
│   │   └── test.md
│   ├── contact.md
│   ├── history.md
│   ├── hobby.md
│   ├── profile.md
│   └── skills.md
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
│   ├── _app.tsx
│   ├── blogs
│   │   ├── [id].tsx # どんなファイル名かはわからないので動的にページを生成するところ
│   │   └── index.tsx # 一覧を表示する
│   ├── contact.tsx
│   ├── history.tsx
│   ├── hobby.tsx
│   ├── index.tsx
│   └── skills.tsx
├── ppm-lock.yaml
└── tsconfig.json

開発環境

  • node v19.9.0
  • pnpm v8.3.1
  • React v18.2.0
  • next v13.3.2
  • @chakura-ui/react v2.6.1

エディタ(?)はCodespacesでやりましただからVSCです

Markdownで更新を楽に

フロントも最近いろんな技術が生えまくっていて、ポートフォリオ書く時にもいちいち内容と一緒に装飾を考えるのが手間だったので、デザインと内容で別個に編集できるようにコンポーネント部分とマークダウン部分に分けた。

ChakraUIのコンポーネントに変換する

マークダウンの解析・レンダリングにReactMarkdownを使ったところ、すべての要素が同じスタイルになった。どうやらChakraUIの性質のようなので、レンダリングされたタグに基づいてそれぞれChakraUIで想定しているコンポーネントに変換するように設定した。

MarkdownContent.tsx
//...
 const components: Components = {
     h1: ({ node, ...props }) => <Heading as="h1" size="xl" {...props} />,
     h2: ({ node, ...props }) => <Heading as="h2" size="lg" {...props} />,
     h3: ({ node, ...props }) => <Heading as="h3" size="md" {...props} />,
     blockquote: ({ node, ...props }) => <Box as="blockquote" p="1rem" borderLeft="4px solid #ddd" {...props} />,
     ul: ({ node, ...props }) => <List spacing={3} styleType="disc" paddingLeft={5} {...props} />,
     ol: ({ node, ...props }) => <List as="ol" spacing={3} styleType="decimal" paddingLeft={5} {...props} />,
     li: ({ node, ...props }) => <ListItem {...props} />,
     pre: ({ node, ...props }) => <Box as="pre" p="1rem" {...props} />,
     code: ({ node, ...props }) => <Code {...props} />,
     a: ({ node, ...props }) => <><Link color="teal.500" {...props} /><GoLinkExternal/></>,
     img: ({ node, ...props }) => <Image {...props} />,
     table: ({ node, ...props }) => <Table {...props} />,
     thead: ({ node, ...props }) => <Thead {...props} />,
     tbody: ({ node, ...props }) => <Tbody {...props} />,
     tr: ({ node, ...props }) => <Tr {...props} />,
     th: ({ node, ...props }) => <Th {...props} />,
     td: ({ node, ...props }) => <Td {...props} />,
     hr: ({ node, ...props }) => <Divider {...props} />,
     p: ({ node, ...props }) => <Text {...props} />,
 };
//...

export default function MarkdownContent ({ markdown }: MarkdownContentProps) {
    return (
        <Container ml={4}><ReactMarkdown components={components}>{ markdown }</ReactMarkdown></Container>
    );
};

ブログ機能を作ってみた

ジャンルごとにnote,notion,qiita,みたいなジャンル分けをしたほうがいいのだろうけど自分にはうまく管理できずどれもなぁなぁで放置されやすいので、マークダウンだけでページ作れるようにしたしついでに実装しようという気分になった。

ただやみくもにページを増やすとやっぱり毎回TSでコーディングしないといけなくなるので、Next.jsの動的ルーティングを用いて/blog/にマークダウンのファイルの数だけページを生成してくれるような実装にした。

/blogs/[id].tsx
import { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
import NavBar from '../../components/NavBar';
import Footer from '../../components/Footer';
import path from 'path';
import fs from 'fs';
import MarkdownContent from '../../components/MarkdownContent';
import { Link } from '@chakra-ui/react';

export default function Blog({markdown}: {markdown: string}) {
    return (
      <>
          <Head>
              <title>My Blog</title>
          </Head>
          <NavBar />
          <MarkdownContent markdown={markdown}/>
          <Link href="/blogs" pos="fixed">Back to Blogs</Link>
          <Footer />
      </>
    );
  }

export const getStaticPaths: GetStaticPaths = async () => {
  const blogsDirectory = path.join(process.cwd(), 'markdown', 'blogs');
  const filenames = fs.readdirSync(blogsDirectory);
  const paths = filenames.map((filename) => ({
    params: { id: filename.replace(/\.md$/, '') },
  }));

  return { paths, fallback: false };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const blogsDirectory = path.join(process.cwd(), 'markdown', 'blogs');
  const filePath = path.join(blogsDirectory, `${params?.id}.md`);
  const markdown = fs.readFileSync(filePath, 'utf8');

  return { props: { markdown } };
};

このコンポーネントが行うのは

  • /markdown/blogs/のマークダウンファイルを読み込んで、その名前で静的ページのパスを生成する
  • /blogs/[id]に生成するための内容をファイルから読み込んでMarkdownContentに渡す

なので、それぞれNextで用意されているgetStaticPaths,getStaticPropsを定義して行う。

Nextのちょっと困ったところ

これはおそらくNextへの理解度が足りてないから起こったのだろうなとおもっているけど、next/linkLinkコンポーネントで指定したhref属性は基本的に前にbasePathが挿入される。なので軽い気持ちで何も考えずLinkコンポーネントを使うと予期しないリンクを作ってしまうかもしれないのでちゃんと確認はしようと思った。ちょっと煩わしかったので、ひとまずここではChakuraUIのほうのLinkでごまかした。

デプロイを自動化しよう

金もサーバーもないので、GitHub Pagesを使いました。お初のデプロイのときにQiitaとZennをめぐってみたところ2023年版のブログないなーと思っていたので一応現状のActionsをここに残しておく
多分これはGitHub Pagesのドキュメントみたら作れると思う……?

(2023年6月19日追記: バージョンが古かったのでv3に変更しました)

gh-page.yml
name: github pages

on:
  push:
    branches:
      - profile

jobs:
    build-deploy:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v3
            - name: Setup node
              uses: actions/setup-node@v3
              with:
                node-version: 'latest'
            - name: Install pnpm
              run: npm install -g pnpm
            - name: Cache node modules
              uses: actions/cache@v3
              with:
                path: ~/.pnpm-store
                key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
                restore-keys: |
                  ${{ runner.os }}-pnpm-

            - name: Install dependencies
              run: pnpm install --frozen-lockfile
            
            - name: Next build
              run: pnpm run build
            
            - name: Export
              run: pnpm run export && ls ./
            
            - name: Add nojekyll
              run: touch out/.nojekyll

            - name: Deploy
              uses: peaceiris/actions-gh-pages@v3
              with:
                github_token: ${{ secrets.GITHUB_TOKEN }}
                publish_dir: ./out

実際に出来上がったもの

自分にとっては設計しやすいし、これからも変更が楽しい感じになったかなと思ったのでOK

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?