Yuto Blog
rss-iconx-icongithub-iconyoutube-icon

HonoXでブログを作り直した。そしてテンプレートも作った

Intro

今回自分のブログをNext.jsからHonoXに書き換えをしました。あんまりまだ使ってる人いないと思うので、今後使ってみたい向けにブログとして残しておきます。

honox-blog-templeteを作った

最初に今後HonoXを使っていきたい方向けにhonox-blog-templeteというテンプレートリポジトリを作りました。

基本的にはyusukebeさんのhonox mdx exmapleで事足りるのですが、技術ブログを書きたい人はシンタックスハイライトの設定をしたり、フォーマッターの設定など色々することもあるかと思うので、その辺りを設定しておきました! サクッとブログを作りたい方は使ってみてください!

$ npm run create:post

を実行すると対話形式でブログのテンプレートを作れます!あとはブログを書いて、Cloudflare pagesなりにデプロイをすると完成です!UIはお好みでどうぞ!

感想

1. mdxがそのまま使えるのありがたい

https://github.com/honojs/honox?tab=readme-ov-file#mdxに書いてある内容です。

自分の場合は/app/posts配下にmdxファイルをおいているんですが、ビルドするとhtmlファイルに変換されるので、そのままブラウザに表示させることができます。追加の設定もほぼいらないので、びっくりするほど簡単でした。

2. Hooksが使えるのありがたい

hono/jsxがReactと互換性があるため、Hooksを使うことができます。(https://hono.dev/guides/jsx-dom#hooks-compatible-with-react)

そのため、今回隠れた追加機能としていいね機能を追加したんですが、これにdebounce機能を用いる際にzennにあった参考のhooksをそのまま使うことができ、とても楽でした。

import { useEffect, useState } from "hono/jsx";

export function useDebounce(value: number, delay: number) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue;
}

3. CI組んでprごとにpreviewにも出せるようにできた

自分は書いたブログごとにPRを出すようにしています。そうするとVercelだとpreview環境を用意してくれるためです。書いたブログのチェックで、最終確認を行いたい時にみます。

この体験は損ねたくなかったので、Cloudflare pagesでも同じことしたいと思い調べたところ、cloudflare/pages-action@v1を使うことでとても簡単に実現できました。

GitHub ActionsでビルドしてCloudflare Pagesにデプロイする ← このブログがとても参考になりました!pull_request.ymlにコードがあるので参考にしてみてくださいー!

3. Islandコンポーネントが_renderer.tsxで使うとscriptが読み込まれない

今回いいね機能Xへのシェアとフォローのボタンはisland配下に置いて、全てのブログページ下部で表示させるために_renderer.tsx配下に置いています。

この場合表示はされるんですが、honox/serverのScirptコンポーネントでは使用上HasIslandがtrueになった場合に本番環境でscirptが読み込まれるようになる仕様になっているみたいでした。

export const Script: FC<Options> = async (options) => {
  const src = options.src
  if (options.prod ?? import.meta.env.PROD) {
    let manifest: Manifest | undefined = options.manifest
    if (!manifest) {
      const MANIFEST = import.meta.glob<{ default: Manifest }>('/dist/.vite/manifest.json', {
        eager: true,
      })
      for (const [, manifestFile] of Object.entries(MANIFEST)) {
        if (manifestFile['default']) {
          manifest = manifestFile['default']
          break
        }
      }
    }
    if (manifest) {
      const scriptInManifest = manifest[src.replace(/^\//, '')]
      if (scriptInManifest) {
        return (
          <HasIslands>
            <script
              type='module'
              async={!!options.async}
              src={`/${scriptInManifest.file}`}
            ></script>
          </HasIslands>
        )
      }
    }
    return <></>
  } else {
    return <script type='module' async={!!options.async} src={src}></script>
  }
}

scriptが読み込まれないので、インタクションが発生しても何も起きない状態が発生しません。 そのため、<HasIslands>で囲まないようにしたScriptコンポーネントを自分の方で用意することにしました。 ただ絶対この方法良くないと思うので、なんかいい方法あれば教えていただきたいです🙇‍♂️

https://twitter.com/yusukebe/status/1765727221526983014 ← Xでyusukebeさんにお聞きしたところ直していいとのことなので、今頑張って開発しています。また進捗があれば共有します。

まとめ

個人的には開発体験良くて、Cloudflare pagesへのデプロイも爆速だったので流行って欲しいなと思います! あと今回npm使ったけどbunにしてみたいし、もうちょっとislandの恩恵を感じられるようなインタラクション多めのアプリでも採用してみたいです!

ぜひ皆さんもHonoX使ってみてくださいー!!🔥

0