Skip to content

レンダラーの作成

前回 Top ページを作成しましたが、続けて /sub にアクセスした時に、別のページを表示するようにします。

サブページ(仮)の作成

tsx
app.get('/sub', (c) => {
  return c.html(
    <html lang="ja">
      <head>
        <title>サブページ | Hono SNS</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>
        <main>
          <h1>This is sub page.</h1>
          <p>これはHonoのサブページです。</p>
        </main>
      </body>
    </html>
  )
})

/sub にアクセスすると、This is sub page. と表示されているはずです。

レンダラーを作ってみる

さて、ここまでで 2 つのページを作成することができました。 今まで作ってきた二つのページを見比べてみましょう。

作ったページを見比べてみる

tsx
// ...
    <html lang="ja">
      <head>
        <title>トップページ | Hono SNS</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>
        <main>
          <h1>Hello Hono!</h1>
          <p>これはHonoのトップページです。</p>
        </main>
      </body>
    </html>
// ...
tsx
// ...
    <html lang="ja">
      <head>
        <title>サブページ | Hono SNS</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>
        <main>
          <h1>This is sub page.</h1>
          <p>これはHonoのサブページです。</p>
        </main>
      </body>
    </html>
// ...

<body> の中身はページごとに異なるのでいいとして、<head> の中身はタイトル以外毎回同じですね。 また、タイトルも 〇〇 | Hono SNS という形式で、〇〇 の部分だけ変わり、Hono SNS の部分は毎回同じです。

このように、ページごとに異なる部分と、ページごとに同じ部分があるとき、同じ部分を毎回書くのは面倒ですし、変更するときにも全て変えないといけないので、とても面倒です。

単純なレンダラーの作成

そこでこのような部分を共通化するために、Hono のレンダラーというものが提供されています。 これを使うことで、JSX の共通する部分の記述をまとめることができます。

まずは src/index.tsx に以下のコードを加えてください。

tsx
import { jsxRenderer } from 'hono/jsx-renderer'

app.use(
  "*",
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <head>
            <title>Hono SNS</title>
            <meta charset="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          </head>
          <body>{children}</body>
        </html>
      )
    },
    { stream: true, docType: true }
  )
)

そして、src/index.tsxapp.get の部分を以下のように変更してください。

tsx
app.get('/', (c) => {
  return c.html( 
  return c.render( 
    <html lang="ja">
      <head>
        <title>トップページ | Hono SNS</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>
        <main>
          <h1>Hello Hono!</h1>
          <p>これはHonoのトップページです。</p>
        </main>
      </body>
    </html> 
  )
})

app.get('/sub', (c) => {
  return c.html( 
  return c.render( 
    <html lang="ja">
      <head>
        <title>サブページ | Hono SNS</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </head>
      <body>
        <main>
          <h1>This is sub page.</h1>
          <p>これはHonoのサブページです。</p>
        </main>
      </body>
    </html> 
  )
})

npm run dev を実行して、http://localhost:3000http://localhost:3000/sub にアクセスしてみてください。

さっきとほぼ同じように表示されているはずです。HTML の同じ部分をこれで共通化することができました。

ここまでのコード
tsx
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { jsxRenderer } from "hono/jsx-renderer";

const app = new Hono();

app.use(
  "*",
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <head>
            <title>Hono SNS</title>
            <meta charset="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          </head>
          <body>{children}</body>
        </html>
      );
    },
    { stream: true, docType: true }
  )
);

app.get("/", (c) => {
  return c.render(
    <main>
      <h1>Hello Hono!</h1>
      <p>これはHonoのトップページです。</p>
    </main>
  );
});

app.get("/sub", (c) => {
  return c.render(
    <main>
      <h1>This is sub page.</h1>
      <p>これはHonoのサブページです。</p>
    </main>
  );
});

const port = 3000;
console.log(`Server is running on port ${port}`);

serve({
  fetch: app.fetch,
  port,
});

レンダラーにタイトルを渡す

さて、ここまででレンダラーを使うことで、HTML の共通部分を共通化してきましたが、まだタイトルの部分が"Hono SNS"となっています。 ここは共通化したとはいえど、ページごとに異なる部分なので、ページごとに異なるタイトルを表示したいところです。

そこで、レンダラーにタイトルを渡すことができるようにしてみましょう。

まずは src/types.ts を作って、以下のようにしてください。

tsx
import "hono";

declare module "hono" {
  interface ContextRenderer {
    (content: string | Promise<string>, props: { title: string }): Response;
  }
}

ポイントは ContextRenderer の第二引数に { title: string } を追加することです。ここがレンダラーの型を拡張することができる宣言になっています。

そして、src/index.tsx を以下のように編集してください。

まずは jsxRenderer 部分、app.use の部分を以下のように編集してください。

tsx
app.use(
  "*",
  jsxRenderer(
    ({ children }) => { 
    ({ children, title }) => { 
      return (
        <html>
          <head>
            <title>Hono SNS</title>
            <title>{title} | Hono SNS</title>
            <meta charset="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          </head>
          <body>{children}</body>
        </html>
      );
    },
    { stream: true, docType: true }
  )
);

これで、レンダラーに渡された title を使って、タイトルを一部分だけ動的に変更することができるようになりました。

次に、app.get の部分を以下のように編集してください。

tsx
app.get("/", (c) => {
  return c.render(
    <main>
      <h1>Hello Hono!</h1>
      <p>これはHonoのトップページです。</p>
    </main> 
    </main>, 
    { title: "トップページ" } 
  );
});

app.get("/sub", (c) => {
  return c.render(
    <main>
      <h1>This is sub page.</h1>
      <p>これはHonoのサブページです。</p>
    </main> 
    </main>, 
    { title: "サブページ" } 
  );
});

これで、http://localhost:3000 にアクセスすると トップページ | Hono SNShttp://localhost:3000/sub にアクセスすると サブページ | Hono SNS と表示されるようになりました。

お疲れ様でした。これでサイトを構築するための基本的な仕組みが完成しました。 ここまでのコードはこちらにあります。