見出し画像

Next.jsとVercel、markdownファイルを使ってブログサイトを作ってみた話

こんにちは、Goimiです。2つ目の記事を書いてみようと思い、何を書こうか考えていたのですが書くことを決めるのってなんだか難しいですね〜。
ボヤッとはあれ書こうかなとか思ってたりしたつもりだったのですがいざ書こうとするとなかなか手が進まない…。

なのでまずは書きやすそうなところから、仕事でやったことを書いてみようかと思います。一度いろいろ調べながらやったことなら書きやすいのかなと思い一旦書き始めてみます。

コーポレートサイトの作成のご依頼をいただきました

今回は以前お仕事をご一緒させていただいたデザイナー/イラストレーターの小久保さんが独立して企業するということでコーポレートサイトのご依頼をいただきました!

株式会社聴くと描く代表取締役 小久保明美さん

依頼内容としては、小久保さんが作成したデザインを、私が実装するというものでした。

今回はコーポレートサイトの中に、作品や情報を実装時の固定ではなくいつでも更新できるようなものをつけたいというご要望がありました。

そこでwordpressなどのCMSを検討しましたが、サイトの表示スピード・wordpress/プラグインの制約などから解放されるためにCMS上にサイトを構築するのではなく、完全に独立したものが良いという結論に至りました。

その上で、Kさんはmarkdown記法に慣れていたことが決定打となりmarkdownファイルをリポジトリ内に置いてそれを使って作品の記事やNEWSを表示することに決まりました。

【Markdown】
Markdownは、文書を記述するための軽量マークアップ言語のひとつである。本来はプレーンテキスト形式で手軽に書いた文書からHTMLを生成するために開発されたものである。しかし、現在ではHTMLのほかパワーポイント形式やLaTeX形式のファイルへ変換するソフトウェアも開発されている。

Wikipedia

選定された使用技術(抜粋)

  • Next.js@11.1.2

  • SSG(Static Site Generator)

  • Context API

  • typescript@4.3.5

Next.jsを選んだこと自体はそんなに強い理由はなかったのですが、コーポレートサイト+記事ということでサーバーサイドでレンダリングする必要がなかったということと、私が個人的に使ってみたかったので選びました。

Context APIはページごとに動的に変化させたい部分が多く、ページを取得するロジックを一箇所で持って使いまわしたかったので手軽に使えるContext APIを使うことにしました。

完成したサイト

株式会社聴くと描く | 株式会社聴くと描く | 流山市のデザイン会社・イラストレーター

完成したサイトは上の画像から飛べます。今までのデザインやイラストの実績もたくさん掲載されてます!ぜひご覧になってみてください。

いざ実装を開始

今回は必要な部分だけを抜粋して記載していこうと思います。

ディレクトリ構成

app/
 ├ public/
 |  └ images/
 |     └ gazou.png
 └ src/
    ├ articles  // 記事を格納
    |  └ hoge.md  // 記事①
    └ pages/
       └ index.tsx  // トップ(今回はスルー)
       └ posts/
          └ index.tsx  // 記事の一覧を表示
          └ [id].tsx   // 記事を個別に表示

Next.jsの初期状態をベースにsrcフォルダ内にpagesフォルダとarticlesフォルダを格納します。

記事のhoge.mdファイルを用意

---
title: 'サイトタイトル'
date: '2022-02-14'
coverImage: '/images/gazou.png'
categories: ['hoge', 'fuga']
---

ここにテキストが入ります。

あああああああああああああああああああああああああ
あああああああああああああああああああ

ああああああああああああああああああああああああ
ああああああああああああああああああああああああ

この.mdファイルを読み込んで使用していきます。「---」で囲まれた部分は記事の詳細の部分になりそれ以降が本文となって表示される想定です。

では早速記事一覧を表示する部分を作っていきます。

src/pages/posts/index.tsx

// pageで使用する型を定義する
type Props = {
  articles: article[]
}
// 記事の情報の型
type article = {
  title: string;
  content: string;
  matterData: {
    categories: string[];
    title: string;
    date: string;
    excerpt: string;
    coverImage: string;
  };
}

const Index: NextPage<Props> = ({ articles }) => {
  <div>
    {articles.map((a) => (
      <div>
        <Image
          src={a.matterData.coverImage}
          width='1000'
          height='1000'
          alt={'トップイメージ'}
        />
        <div>{a.title}</div>
        <div>{a.matterData.date}</div>
        <div>{a.content}</div>
      </div>
    ))}
  </div>
}

export default Index

ここまでで一覧に表示する部分は完成です。styleは当てていないので表示はくずれ放題なので使用するときは調整してくださいね。

次はapp/src/articlesに配置したファイルを読み込んでarticleの配列にデータを格納するコードをgetStaticProps内に書いていきます。

src/pages/posts/index.tsx【getStaticProps】

type Props = {
  articles: article[]
}

...中略

export const getStaticProps: GetStaticProps = async () => {
  // fs.jsのreaddirSyncを使用して記事フォルダ内を参照してファイルを取得
  const files = fs.readdirSync(path.join('src', 'articles'));
  const articles = files.map((filename) => {
    // ファイル名から.mdを取り除いたものを保存
    const slug = filename.replace(/.md/, '');
    // mdファイルからファイル内の情報を取得
    const markdownWithMeta = fs.readFileSync(
      path.join('src', 'articles', filename),
      'utf-8',
    );
    // gray-matterを使用してファイル内の情報から必要なdataを取得
    const { data: matterData, content } = matter(markdownWithMeta);
    // フロントで必要な情報を返却 
    return { slug, matterData, content };
  });
}

export default Index;

以上、こんな感じでarticlesフォルダに配置した.mdファイルの情報を取得して、画面で使用するためのデータを作ることができました。

src/pages/posts/[id].tsx【getStaticPaths】

すみません、今日は力尽きました…泣

書きます!
getStaticPathsは、動的にルーティングをしている部分をビルド時に静的サイトとして生成してくれます。

[id].tsx([]内は変更可能)と言うファイルを1つ作成してgetStaticPaths内でparams: { id: 'hoge' }を定義すると個別のページとして表示できるようになります。

記事の情報の型
type Props = {
  ...略
}

const Index: NextPage<Props> = ({ articles }) => {
  <div>
    // この中で定義記事を表示するのHTMLを記載する
  </div>
}

// pathを定義する
export const getStaticPaths = async () => {
  const newsFiles = fs.readdirSync(
    path.join('src', 'articles'),
  );

  const paths = newsFiles.map((filename) => ({
    params: {
      id: filename.replace(/.md/, ''),
    },
  }));

  return {
    paths,
    fallback: false,
  };
};

// 記事の内容を取得する
export const getStaticProps = async ({ params }: Params) => {
  // paramsの中にgetStaticPathsが渡ってくるのでそれを使用して記事の内容
  const markdownWithMeta = fs.readFileSync(
    path.join('src', 'articles', `${params.slug}.md`),
    'utf-8',
  );
  const { data: matterData, content } = matter(markdownWithMeta);
  return { props: { matterData, slug: params.slug, content } };
};

pathsと一緒にfallback: false定義していますが、これはgetStaticPaths内で定義されていなpathをリクエストしたときの挙動の設定で、falseにしておくと404のエラーページに飛ばされることになります。

ファイルが大量にある場合などビルド時には全てのページを作成せずに・・・、と言うこともできたりするのでその際はtrueにして使用しますが、その話はここではしません。(というか使ったことがありません笑)

完成

どうでしょう?意外とシンプルにできたのではないでしょうか。
CSSでのスタイリングは行ってませんのでカッコ良いデザインに仕上げちゃってください!

markdownでの記述ができないとこのままの使用はできませんが、markdown自体は難しいものではなくかなり直感的に書けるので初めてでも使いやすいと思います。ぜひチャレンジしてみてください!


この記事が気に入ったらサポートをしてみませんか?