Next.jsで作ったサイトをAstroに変えた際の、Mardocの取り扱い

hidetaka.devという個人のポートフォリオサイトを運用しており、Next.jsとNetlifyで公開していたが、SSRへの移行を検討。CloudflareとAstro採用し、MarkdocからNext.jsにカスタマイズ追加。Astroではastro.config.mjsに設定記述。取得処理やAstroコンポーネント実装も実施。Next.jsからAstroに移行し、getStaticProps内のカスタマイズもAstroコンポーネント側に移行。最終的にCloudflare Pagesでfsが使えないことが判明し、Markdocでのコンテンツ管理諦めたが、Astroの使用を今後他の環境でも検討。

広告ここから
広告ここまで

目次

    https://hidetaka.dev という個人のポートフォリオサイトを運用しています。そのホスティング環境やフレームワークを変えたので、その時の覚書を整理しました。

    背景

    もともとSSGとしてNext.jsを利用し、Netlifyでお手軽に公開していたサイトでした。ただ、npmモジュールやWordPressプラグインなどの情報もポートフォリオへ含めようとした際、ウェブフックイベントなどがこれらには存在しませんでした。そのため更新情報の反映がSSGでは難しくなってきたことから、SSRできる環境への移行を検討し始めました。

    その中で今後使うことの増えそうな予感がしたCloudflareとAstroを使うことにしました。

    Markdocのレンダー処理を変える

    Markdownで書いていたコンテンツは、Markdocを使って描画していました。そして外部リンクのようなMarkdown単独では表現しにくい部分に、独自タグのレンダリング用に入れていたカスタマイズを、Next.jsに追加していました。

    import { parse, transform } from '@markdoc/markdoc'
    import { readFileSync } from 'fs'
    import { join } from 'path'
    
    export const loadMarkdownFile = (path: string) => {
      const content = readFileSync(join(process.cwd(), path), 'utf-8')
      return transform(parse(content), {
        tags: {
          externalLink: {
            render: 'ExternalLink',
            attributes: {
              className: {
                type: String,
              },
              href: {
                type: String,
              },
              label: {
                type: String,
              },
            },
          },
        },
      })
    }
    

    この設定からカスタムのReactコンポーネントを描画するためのrender処理を入れるところまでをやっていました。

    import { renderers } from '@markdoc/markdoc'
    import React, { FC } from 'react'
    import { ExternalLink } from './ExternalLink'
    
    export const MarkdocContent: FC<{
      content: string
    }> = ({ content }) => {
      return (
        <>
          {renderers.react(JSON.parse(content), React, {
            components: {
              ExternalLink,
            },
          })}
        </>
      )
    }
    

    Astroの場合は、astro.config.mjsにConfigを書くことになります。

    import { defineConfig } from 'astro/config';
    import markdoc from "@astrojs/markdoc";
    
    // https://astro.build/config
    export default defineConfig({
      integrations: [markdoc({
        tags: {
          externalLink: {
            render: 'ExternalLink',
            attributes: {
              class: {
                type: String,
              },
              href: {
                type: String,
              },
              lebel: {
                type: String,
              }
            }
          },
        },
      })]
    });

    あとは取得処理などをアイランド内に記述し、Astroコンポーネントとして実装しています。

    ---
    import { getEntryBySlug } from 'astro:content';
    import ExternalLink from '../components/markdoc/ExternalLink.astro';
    
    const lang = getLanguageFromURL(new URL(Astro.request.url).pathname) || 'en-US'
    
    const entry = await getEntryBySlug('profiles', `hero${/ja/.test(lang) ? '-ja' : ''}`);
    const speakerProfile = await getEntryBySlug('profiles', `speaker-profile${/ja/.test(lang) ? '-ja' : ''}`);
    const { Content } = entry ? await entry.render() : { Content: null};
    const { Content: SpeakerProfile } = speakerProfile ? await speakerProfile.render() : {Content: null}
    
    ---
    {Content ? (
        <Content
            components={{ ExternalLink }}
        />
    ): null}
    
    {SpeakerProfile ? (
        <SpeakerProfile
            components={{ ExternalLink }}
        />
    ): null}

    Next.jsの場合、いくつかgetStaticProps内で見た目のカスタマイズを入れていたため、この辺りについてもAstroのコンポーネント側に移すことができました。

    
    export const getStaticProps: GetStaticProps<{
      profiles: {
        hero: string
        speakerBio: string
      }
    }> = async (context) => {
      const heroArticle = loadMarkdownFile('contents/profiles/hero.md')
      const heroContent = (heroArticle as any).children
      heroContent[0].attributes = {
        ...heroContent[0].attributes,
        className: 'mt-3 text-base text-gray-500 sm:mt-5 sm:text-xl lg:text-lg xl:text-xl',
      }
      const speakerBioFileName = isJapanese(context.locale)
        ? 'speakerProfile.ja.md'
        : 'speakerProfile.md'
      const speakerBio = loadMarkdownFile(`contents/profiles/${speakerBioFileName}`)
      const profiles = {
        hero: JSON.stringify(heroContent),
        speakerBio: JSON.stringify(speakerBio),
      }
    
      return {
        props: {
          profiles,
        },
        revalidate: 1 * 60 * 60,
      }
    }

    やってみての感想

    この後、Cloudflare Pagesでfsが使えないことが判明し、Markdocでのコンテンツ管理は結局諦めました。しかしどちらのやり方もざっと触ることができたので、今後他の環境でAstroを使うことになった際は使ってみようかと思います。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark