作成: 3/18/2025, 1:31:20 PM, 最終更新: 3/18/2025, 1:31:20 PM
作成: 3/18/2025, 1:31:20 PM, 最終更新: 3/18/2025, 1:31:20 PM
nopです。この記事ではHono(HonoX)とmicroCMSでブログサイトを作ったものはいいものの、肝心の記事の内容に迷った筆者がブログサイト自体のことをだらだら書いていきます。正直あてにならないことばっかりだと思うので真面目な解説は他の記事を参照していただければと思います。
また、記事の内容がある程度Webアプリケーションの開発に慣れている人に向けて書かれてるため、初心者の方には難しくなっていると思われます。
執筆時 2025/1/26
Hono(公式サイト)とは`Fast, lightweight, built on Web Standards. Support for any JavaScript runtime.`なものだそうで、体感上Expressに近いものになっています.Expressを触ったことのない方に説明するなら、「API・Webなどのサーバを、データを受け取り、処理して、返すまでを流れに沿ってコーディングできるフレームワーク」になると個人的に思います。
microCMS(公式サイト)とは`APIベースの日本製のヘッドレスCMS`という説明の通り、、説明の通りです。CMS(Contents Management System)とはWebサイトのコンテンツを管理・更新できるシステムのことです。WordPressのようなリッチな管理画面で、ブログの内容を執筆・管理することができ、そのコンテンツを自身のWebアプリケーション上にAPI経由で引っ張ってくることができます。microCMSは日本製で有名(所感)なCMSサービスです。
Node.jsの環境が整ってる前提とします。
npm create hono@latestちなみに私はnpmではなくpnpm派です。どうでもいいですね。
Tailwind CSS公式のドキュメントを参考にセットアップしてください。
Viteの設定をいじります。
...
export default defineConfig(({ mode }) => {
if (mode === "client") {
return {
build: {
rollupOptions: {
input: ["/app/global.css"],
},
},
plugins: [client()],
};
} else {
return {
plugins: [honox({ devServer: { adapter } }), build()],
};
}
});最後によくわかっていないおまじないをします。すでに完成したものから引っ張っているのでデフォルトとは違うかもしれませんが、追加したのはLinkの個所だけです。
import Link from "honox/server";
...
export default jsxRenderer(({ children, title }) => {
return (
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="nopのブログサイト" />
<title>{title}</title>
<link rel="icon" href="/favicon.ico" />
<Script src="/app/client.ts" async />
<Link href="/app/global.css" rel="stylesheet" />
<Style />
</head>
<body>{children}</body>
</html>
);
});以上で設定は完了です。お疲れさまでした。
microCMS公式が配布しているSDKをインストールします。
npm install microcms-js-sdkimport {} from "hono";
type Head = {
title?: string;
};
type Bindings = {
MICROCMS_SERVICE_DOMAIN: string;
MICROCMS_API_KEY: string;
};
declare module "hono" {
interface Env {
Variables: {};
Bindings: Bindings;
}
interface ContextRenderer {
(content: string | Promise<string>, head?: Head):
| Response
| Promise<Response>;
}
}環境変数の型定義を追加しました。
import { env } from "hono/adapter";
import { createRoute } from "honox/factory";
import { createClient } from "microcms-js-sdk";
import Header from "../islands/Header";
import HoverPushCard from "../islands/HoverPushCard";
import { cache } from "hono/cache";
import PushCard from "../islands/PushCard";
export default createRoute(async (c) => {
const { MICROCMS_SERVICE_DOMAIN, MICROCMS_API_KEY } = env<{
MICROCMS_SERVICE_DOMAIN: string;
MICROCMS_API_KEY: string;
}>(c);
const client = createClient({
serviceDomain: MICROCMS_SERVICE_DOMAIN,
apiKey: MICROCMS_API_KEY,
});
const contents = await client.get({ endpoint: "blogs" });
const posts = contents.contents.map((content: any) => {
return {
id: content.id,
title: content.title,
eyecatch: content.eyecatch.url,
};
});
return c.render(
<div
class={
"w-screen min-h-screen px-3 lg:px-20 py-6 lg:py-8 bg-neutral-50 dark:bg-neutral-800 flex flex-col items-center gap-16"
}
>
<div class={"w-full"}>
<Header />
</div>
<div class={"flex flex-col gap-6"}>
<div class={"w-fit"}>
<PushCard>
<div class={"py-2 px-4"}>
<h1
class={
"text-2xl font-semibold text-neutral-700 dark:text-neutral-100"
}
>
Posts - {posts.length}
</h1>
</div>
</PushCard>
</div>
{posts.map((post: any) => (
<a href={`/posts/${post.id}`} class={"max-w-(--breakpoint-sm)"}>
<HoverPushCard>
<div class={"p-4"}>
<img
src={post.eyecatch}
alt="eyecatch"
class={"h-auto max-w-full rounded-xl"}
/>
<p
class={
"p-4 font-semibold text-xl text-neutral-700 dark:text-neutral-100"
}
>
{post.title}
</p>
</div>
</HoverPushCard>
</a>
))}
</div>
</div>,
{ title: "nop's blog" }
);
}, cache({ cacheName: "posts", cacheControl: "max-age=3600" }));env関数を使って環境変数を引っ張り、microCMSのAPIを叩いてブログの内容を取得しました。
const { MICROCMS_SERVICE_DOMAIN, MICROCMS_API_KEY } = env<{
MICROCMS_SERVICE_DOMAIN: string;
MICROCMS_API_KEY: string;
}>(c);env関数で環境変数を適切にとってきます。開発中は`.dev.vars`ファイルから読み取ります。
このブログサイトの場合だと、Cloudflare PagesにデプロイするためCloudflareのコントロールパネルから環境変数を設定しました。
const client = createClient({
serviceDomain: MICROCMS_SERVICE_DOMAIN,
apiKey: MICROCMS_API_KEY,
});
const contents = await client.get({ endpoint: "blogs" });
const posts = contents.contents.map((content: any) => {
return {
id: content.id,
title: content.title,
eyecatch: content.eyecatch.url,
};
});この部分はmicroCMSのSDKを用いて記事のデータをAPIから取得しています。先ほどの環境変数を引数に入れることでClientを生やし、その後APIにGETを叩き、記事データを取得します。今回の場合は`endpoint: "blogs"`となっていますが、どうやらこのエンドポイントはmicroCMSのコントロールパネルで変更できるようです。
return c.render(
...
);`c.render`から先は完全にJSX、Tailwind CSSゾーンです。ブログを作成した当初はClineなどは認知していなかったため、すべて手打ちしました。
import { env } from "hono/adapter";
import { createRoute } from "honox/factory";
import { createClient } from "microcms-js-sdk";
import Header from "../../islands/Header";
import { html, raw } from "hono/html";
import PushCard from "../../islands/PushCard";
export default createRoute(async (c) => {
const { id } = c.req.param();
const { MICROCMS_SERVICE_DOMAIN, MICROCMS_API_KEY } = env<{
MICROCMS_SERVICE_DOMAIN: string;
MICROCMS_API_KEY: string;
}>(c);
const client = createClient({
serviceDomain: MICROCMS_SERVICE_DOMAIN,
apiKey: MICROCMS_API_KEY,
});
let post = null;
try {
post = await client.get({ endpoint: "blogs", contentId: id });
} catch (e) {
return c.redirect("/404");
}
if (!post) {
return c.redirect("/404");
}
const publishedAt = new Date(post.publishedAt);
const updatedAt = new Date(post.updatedAt);
return c.render(
<div
class={
"w-screen min-h-screen px-3 lg:px-20 py-6 lg:py-8 bg-neutral-50 dark:bg-neutral-800 flex flex-col items-center gap-16"
}
>
<div class={"w-full"}>
<Header />
</div>
<div class={"h-auto max-w-5xl flex flex-col items-center gap-6"}>
<div class={"w-full"}>
<PushCard>
<div class={"grow p-4 flex flex-col items-center gap-6"}>
<img
src={post.eyecatch.url}
alt="eyecatch"
class={"h-auto w-full rounded-md"}
/>
<h1
class={
"text-2xl font-semibold text-neutral-700 dark:text-neutral-100 text-center"
}
>
{post.title}
</h1>
<p class={"text-neutral-700 dark:text-neutral-100"}>
作成: {publishedAt.toLocaleString()}, 最終更新:{" "}
{updatedAt.toLocaleString()}
</p>
</div>
</PushCard>
</div>
<div class={"w-full"}>
<PushCard>
<div
class={"post px-6 lg:px-20 py-10 flex flex-col gap-4"}
>{html`${raw(post.content)}`}</div>
</PushCard>
</div>
</div>
</div>,
{ title: post.title }
);
});`[id].tsx`にすることで、example.com/1234にアクセスされた際に、`const { id } = c.req.param();`のidに1234が入ります。
それ以外は`index.tsx`と大して変わらないのでスキップします。
ブログ書くのって大変ですね。思っていたよりもだいぶ疲れました。また機会があればよろしくお願いします。
nopです。この記事ではHono(HonoX)とmicroCMSでブログサイトを作ったものはいいものの、肝心の記事の内容に迷った筆者がブログサイト自体のことをだらだら書いていきます。正直あてにならないことばっかりだと思うので真面目な解説は他の記事を参照していただければと思います。
また、記事の内容がある程度Webアプリケーションの開発に慣れている人に向けて書かれてるため、初心者の方には難しくなっていると思われます。
執筆時 2025/1/26
Hono(公式サイト)とは`Fast, lightweight, built on Web Standards. Support for any JavaScript runtime.`なものだそうで、体感上Expressに近いものになっています.Expressを触ったことのない方に説明するなら、「API・Webなどのサーバを、データを受け取り、処理して、返すまでを流れに沿ってコーディングできるフレームワーク」になると個人的に思います。
microCMS(公式サイト)とは`APIベースの日本製のヘッドレスCMS`という説明の通り、、説明の通りです。CMS(Contents Management System)とはWebサイトのコンテンツを管理・更新できるシステムのことです。WordPressのようなリッチな管理画面で、ブログの内容を執筆・管理することができ、そのコンテンツを自身のWebアプリケーション上にAPI経由で引っ張ってくることができます。microCMSは日本製で有名(所感)なCMSサービスです。
Node.jsの環境が整ってる前提とします。
npm create hono@latestちなみに私はnpmではなくpnpm派です。どうでもいいですね。
Tailwind CSS公式のドキュメントを参考にセットアップしてください。
Viteの設定をいじります。
...
export default defineConfig(({ mode }) => {
if (mode === "client") {
return {
build: {
rollupOptions: {
input: ["/app/global.css"],
},
},
plugins: [client()],
};
} else {
return {
plugins: [honox({ devServer: { adapter } }), build()],
};
}
});最後によくわかっていないおまじないをします。すでに完成したものから引っ張っているのでデフォルトとは違うかもしれませんが、追加したのはLinkの個所だけです。
import Link from "honox/server";
...
export default jsxRenderer(({ children, title }) => {
return (
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="nopのブログサイト" />
<title>{title}</title>
<link rel="icon" href="/favicon.ico" />
<Script src="/app/client.ts" async />
<Link href="/app/global.css" rel="stylesheet" />
<Style />
</head>
<body>{children}</body>
</html>
);
});以上で設定は完了です。お疲れさまでした。
microCMS公式が配布しているSDKをインストールします。
npm install microcms-js-sdkimport {} from "hono";
type Head = {
title?: string;
};
type Bindings = {
MICROCMS_SERVICE_DOMAIN: string;
MICROCMS_API_KEY: string;
};
declare module "hono" {
interface Env {
Variables: {};
Bindings: Bindings;
}
interface ContextRenderer {
(content: string | Promise<string>, head?: Head):
| Response
| Promise<Response>;
}
}環境変数の型定義を追加しました。
import { env } from "hono/adapter";
import { createRoute } from "honox/factory";
import { createClient } from "microcms-js-sdk";
import Header from "../islands/Header";
import HoverPushCard from "../islands/HoverPushCard";
import { cache } from "hono/cache";
import PushCard from "../islands/PushCard";
export default createRoute(async (c) => {
const { MICROCMS_SERVICE_DOMAIN, MICROCMS_API_KEY } = env<{
MICROCMS_SERVICE_DOMAIN: string;
MICROCMS_API_KEY: string;
}>(c);
const client = createClient({
serviceDomain: MICROCMS_SERVICE_DOMAIN,
apiKey: MICROCMS_API_KEY,
});
const contents = await client.get({ endpoint: "blogs" });
const posts = contents.contents.map((content: any) => {
return {
id: content.id,
title: content.title,
eyecatch: content.eyecatch.url,
};
});
return c.render(
<div
class={
"w-screen min-h-screen px-3 lg:px-20 py-6 lg:py-8 bg-neutral-50 dark:bg-neutral-800 flex flex-col items-center gap-16"
}
>
<div class={"w-full"}>
<Header />
</div>
<div class={"flex flex-col gap-6"}>
<div class={"w-fit"}>
<PushCard>
<div class={"py-2 px-4"}>
<h1
class={
"text-2xl font-semibold text-neutral-700 dark:text-neutral-100"
}
>
Posts - {posts.length}
</h1>
</div>
</PushCard>
</div>
{posts.map((post: any) => (
<a href={`/posts/${post.id}`} class={"max-w-(--breakpoint-sm)"}>
<HoverPushCard>
<div class={"p-4"}>
<img
src={post.eyecatch}
alt="eyecatch"
class={"h-auto max-w-full rounded-xl"}
/>
<p
class={
"p-4 font-semibold text-xl text-neutral-700 dark:text-neutral-100"
}
>
{post.title}
</p>
</div>
</HoverPushCard>
</a>
))}
</div>
</div>,
{ title: "nop's blog" }
);
}, cache({ cacheName: "posts", cacheControl: "max-age=3600" }));env関数を使って環境変数を引っ張り、microCMSのAPIを叩いてブログの内容を取得しました。
const { MICROCMS_SERVICE_DOMAIN, MICROCMS_API_KEY } = env<{
MICROCMS_SERVICE_DOMAIN: string;
MICROCMS_API_KEY: string;
}>(c);env関数で環境変数を適切にとってきます。開発中は`.dev.vars`ファイルから読み取ります。
このブログサイトの場合だと、Cloudflare PagesにデプロイするためCloudflareのコントロールパネルから環境変数を設定しました。
const client = createClient({
serviceDomain: MICROCMS_SERVICE_DOMAIN,
apiKey: MICROCMS_API_KEY,
});
const contents = await client.get({ endpoint: "blogs" });
const posts = contents.contents.map((content: any) => {
return {
id: content.id,
title: content.title,
eyecatch: content.eyecatch.url,
};
});この部分はmicroCMSのSDKを用いて記事のデータをAPIから取得しています。先ほどの環境変数を引数に入れることでClientを生やし、その後APIにGETを叩き、記事データを取得します。今回の場合は`endpoint: "blogs"`となっていますが、どうやらこのエンドポイントはmicroCMSのコントロールパネルで変更できるようです。
return c.render(
...
);`c.render`から先は完全にJSX、Tailwind CSSゾーンです。ブログを作成した当初はClineなどは認知していなかったため、すべて手打ちしました。
import { env } from "hono/adapter";
import { createRoute } from "honox/factory";
import { createClient } from "microcms-js-sdk";
import Header from "../../islands/Header";
import { html, raw } from "hono/html";
import PushCard from "../../islands/PushCard";
export default createRoute(async (c) => {
const { id } = c.req.param();
const { MICROCMS_SERVICE_DOMAIN, MICROCMS_API_KEY } = env<{
MICROCMS_SERVICE_DOMAIN: string;
MICROCMS_API_KEY: string;
}>(c);
const client = createClient({
serviceDomain: MICROCMS_SERVICE_DOMAIN,
apiKey: MICROCMS_API_KEY,
});
let post = null;
try {
post = await client.get({ endpoint: "blogs", contentId: id });
} catch (e) {
return c.redirect("/404");
}
if (!post) {
return c.redirect("/404");
}
const publishedAt = new Date(post.publishedAt);
const updatedAt = new Date(post.updatedAt);
return c.render(
<div
class={
"w-screen min-h-screen px-3 lg:px-20 py-6 lg:py-8 bg-neutral-50 dark:bg-neutral-800 flex flex-col items-center gap-16"
}
>
<div class={"w-full"}>
<Header />
</div>
<div class={"h-auto max-w-5xl flex flex-col items-center gap-6"}>
<div class={"w-full"}>
<PushCard>
<div class={"grow p-4 flex flex-col items-center gap-6"}>
<img
src={post.eyecatch.url}
alt="eyecatch"
class={"h-auto w-full rounded-md"}
/>
<h1
class={
"text-2xl font-semibold text-neutral-700 dark:text-neutral-100 text-center"
}
>
{post.title}
</h1>
<p class={"text-neutral-700 dark:text-neutral-100"}>
作成: {publishedAt.toLocaleString()}, 最終更新:{" "}
{updatedAt.toLocaleString()}
</p>
</div>
</PushCard>
</div>
<div class={"w-full"}>
<PushCard>
<div
class={"post px-6 lg:px-20 py-10 flex flex-col gap-4"}
>{html`${raw(post.content)}`}</div>
</PushCard>
</div>
</div>
</div>,
{ title: post.title }
);
});`[id].tsx`にすることで、example.com/1234にアクセスされた際に、`const { id } = c.req.param();`のidに1234が入ります。
それ以外は`index.tsx`と大して変わらないのでスキップします。
ブログ書くのって大変ですね。思っていたよりもだいぶ疲れました。また機会があればよろしくお願いします。