Tech系サービスやガジェットの使い心地、自分の作業環境、資産運用について気が向いたときに記録を残しています。

記事内のAmazonアソシエイト適格販売及び、Google Adsenseでお小遣いを得ています。

Astro MDX integrationで画像をレスポンシブ対応にする方法

自転車ブログをGatsbyからAstroに書き換えた。

blog.gensobunya.net

新興フレームワークだけあり、Gatsbyのように豊富なプラグインによる便利な設定というものはないので、殆どの機能を自分で実装する必要がある。

Head APIがなく、レイアウトコンポーネントから必要な要素を全てPropsで渡してheadタグに記述したり、クライアント処理をAstroでやるのはつらいのでReactを使ったりと、Gatsbyに慣れているといろいろ面倒なところが多く感じた。

その中でも、ナレッジが少なく(というかundocumented)でデフォルト処理してくれない、MDX内に相対パスで記述した画像をレスポンシブ化する部分だけ抜粋してメモ。

背景

AstroにはContent Collectionという機能があり、srcディレクトリ内に配置された画像を自動的にフォーマット変換・レスポンシブ対応で自動的に複数サイズの画像をsharpレンダリングする機能がある。

.astroコンポーネントの場合は、組み込みの<Image>, <Picture>タグを通じてこの最適化をコントロールできる。

docs.astro.build

一方で、このContent Collectionは.md, .mdxファイル内の画像リンクも最適化してくれるのだが、この最適化は最適化と名ばかりのオリジナルサイズのwebpを吐き出すだけである(しかもフォールバックもない)。

Content Collectionには(ドキュメントを読む限り)グローバル設定が存在しないうえ、gatsby-plugin-image のようなキャプション設定機能も存在していないため、自前でこれらを実装する必要がある。

Astroの書き味はNext.jsに似ていて、シンプルなコア機能やコンポーネントを提供する方針のようだ。

前提

  • Astro.3.x
  • Content Collection
  • @astro/mdx integration >1.1.0

実装

実は、@astro/mdx インテグレーションにはCustom Componentのマッピングimgを指定した場合、ContentCollectionによって処理された画像に限り AstroのImageMetadataタイプがPropsとして渡される仕様になっている。

github.com

これを知っていると非常にシンプルに記述できる。この情報にたどり着くまでは自分でrehype, remarkプラグインを書かねばならないのかと絶望していたところ。

---
//MdxPicture.astro
import type { ImageMetadata } from "astro"
import { Picture } from "astro:assets"
type Props = {
  src: string | ImageMetadata
  alt: string
}
const { src, alt } = Astro.props
---

{
  typeof src === "string" ? (
    <figure>
      <img class="mx-auto" src={src} alt={alt} />
      <figcaption class="not-prose text-center text-xs text-secondary md:text-sm">
        {alt}
      </figcaption>
    </figure>
  ) : (
    <figure>
      <Picture
        formats={["webp"]}
        fallbackFormat="jpg"
        widths={[360, 752]}
        class="mx-auto"
        {src}
        {alt}
      />
      <figcaption class="not-prose text-center text-xs text-secondary md:text-sm">
        {alt}
      </figcaption>
    </figure>
  )
}
//[...slug].astro
---
~
~
import { MdxPicture } from "~~~"
~
~
const { Content } = await post.render()
---

  <Content
    components={{
      ~
      ~
      img: MdxPicture
    }}
  />


componentsとして渡すときに特段Propsとして明示的に渡す必要がないところがポイント。

ドキュメントに書く価値のある情報のはずなのだが…

デスクトップPCを Flexispot CPUスタンド CH1 で自作昇降デスクに吊る

PCを吊ることで、昇降時のケーブル長やルンバ衝突などの懸念を解消

プライベート用のPCをTsukumoのBTO PC(G-GEAR)に変更。

それにともない、デスク周りの環境を変更した。PC本体を床置きしてしまうと、ルンバと衝突したり、Flexispot の昇降に合わせたケーブル長を確保する必要があったりと、いろいろ不都合がある。

また、仕事用のPCは変わらずノートなので、モニターを含めた周辺機器を共有はマスト。

いろいろ手段を検討したが、シンプルに天板と一体固定してしまうのが早かったので、Flexispot の CH1 を使って天板下に吊るすことにした。

Flexispot CH1 取り付け

メーカー純正の組み合わせだが、実際は直付け式

純正品ということで、取り付けが簡単であることを期待したが、届いた説明書を読んだところ、特にFlexispot本体と共締めするような機構はなく、天板に直接ねじ止めが必要だった。

若干の失望を持ちつつも、天板からPC上部のオフセットは昇降デスク本体のケーブルトレイとは接触しないように設計されていたので、気を取り直して作業していく。

特に難しい点はないのでマーキングして下穴を開ける

作業自体はとても簡単で、2mmのドリルで下穴を開け、本体をねじ止めするだけ。脚の取り付けに比べれば一瞬だ。

デスク下のケーブルカバーや、昇降スイッチとの干渉もないのは流石純正。特にケーブルカバーとのクリアランスは少しだけ余裕を持たせつつ、かなり攻めている。

下準備:デスクトップPCにThunderbolt 4カードを増設

周辺機器共有はノートPCの場合、USB Type-C (10Gbps以上)のポートからドックを介して接続しておけば、ドックの接続先を変更するだけで済む。

自分の場合は、HP Thunderbolt 3 Dock 120W G2をずっと使っていた。

gensobunya-tech.hatenablog.com

ポート数・独自電源・HP製PC向けの電源連動など、スペックもいいし当時は非常に安価だった。(残念ながら今は廃盤)

一方で、タワー型のデスクトップPCにはThunderbolt端子が装備されていることは少ない。ノートPCやミニPCと違って、インターフェースが豊富なので基本的には不要だからだろう。

とはいえ、現在の作業環境はすべてこのドックを介しているので、この良さはそのまま流用したい。そこで、対応マザボを使っているBTOということで、G-GEARに白羽の矢を立てた。

また、ツクモBTO PCは販売している状態から機器を増設しても、組み立て手順に問題がなければ保証が継続するという、流石の老舗サポートだった点も大きい。

購入したPCのマザーボードは「ASRock B660 Pro RS」。BTOオリジナルモデルだったが、変更点はSSDヒートシンクのみ、いわゆる去勢はされていないと確認して購入した。

TB4カード互換性

Thunderboltカードをを増設する場合(ASUSとASRockしか見ていないが)、単純なUSB接続だけでなく、Thunderboltヘッダと呼ばれるものに増設カードを接続する必要がある。

つまり、マザーボード側もThunderboltカードの増設に対応している必要がある。B660にThunderboltヘッダはあるのだが、現行品のThunderbolt 4 AIC R2.0の対応品にB660 Pro RSの表記がない…?

www.asrock.com

サポートを切られたのかと思いきや、公式アカウント曰く更新が間に合っていないだけとのこと。

手順書通り取り付け、ドライバをインストールしたところ、USB機器・ディスプレイ共に問題なくドック経由で認識するようになった

ドックのケーブル交換

HP Thunderbolt 3 Dock 120W G2は、本体からケーブルが生えているように見えて、実はケーブルを交換可能。

先駆者のブログを参考に、底部のカバーを外して交換。はずして分かったのだが、底部ポートからまっすぐケーブルが出ていないので将来的にかならず断線する仕様…自分のケーブルも被覆が破れていた。

www.buzzyvox.com

Thunderbolt4からは、アクティブケーブル・パッシブケーブルをユーザーが意識する必要がなくなったため、遠慮なく2mのThunderbolt4ケーブルを購入して交換した。

ここは大容量のデータが流れるので、ケチらず信頼性の高いメーカーを使う。

まとめ

デスクトップ↔ノートPCでのケーブル付け替えだけで作業環境のスイッチが完了する状態を構成した。

懸念はケーブルの付け替えが面倒で、毎回デスク下にもぐる必要がある点。Thunderbolt 4 スイッチャーや、Thunderbolt ハブがあれば、手元だけでの変更も可能だが、現状2万円もかける価値があるかといわれると微妙…

Notion + Markdown + VivlioStyle で共著の同人誌を作成する

ここ数年、自分の同人サークルである幻想サイクルの同人誌作成では、vivliostyleを使って原稿を作っている。

vivliostyle.org

組版アプリケーションを使っていて「このオプション、もう少し細かく設定できたらなぁ……」と思ったことはありませんか? Vivliostyle を使った CSS 組版では、比較的柔軟かつプログラマブルに原稿のスタイルを設定できます。

最大でも年に2回しか同人誌を作成しない身としては、InDesignIllustratorのような高価かつ、同人誌作成でしか利用しないスキルを身につけるのは様々な意味でコストが高い。(サークルカットなどを作るにもInkscapeを使ってAdobe税を回避しているほどだ)

VivlioStyleは、普段から仕事で使っているフロントエンドのスキルセット(主にCSS)を使いPDFレイアウトができるツール。さらに、入稿の元データも馴染みのあるMarkdown構文で記述できる。

テーマに原稿データを流し込み、テンプレートエンジンのような感覚で入稿可能なPDF※を生成できる。画像の配置・改ページ・ノンブルや目次の自動生成など、手作業でやると抜けの出やすい部分をおまかせでき、執筆者陣はコンテンツ作成に集中できるという寸法。

※印刷用の入稿PDFにはフォントのアウトライン化やグレースケール化をはじめとするPDF/X-1aという規格に準拠する必要がある

この夏の新刊はこちら。

www.gensobunya.net

後述するが、印刷物としての制限がないKindleでは写真を全てカラー版に置き換えている。

こういった複数フォーマットに対しても、ビルド時の設定を変更することで、単一の原稿ファイルからPDF/X-1a規格とKindle用のPDFを出し分けられる点が非常に強力だ。

組版ツールでも、リンク先のファイルを置き換えるだけで対応できるといえばできるだろうが、こうした機械的な作業は作業漏れ対策も兼ねて自動化するに限る。

VivlioStyleの強みと、自転車同人誌のミスマッチ

VivlioStyleのプロジェクトはMarkdown + CSS + 設定ファイルという構成になり、テキストベースのファイルだけで構成できるので、Gitを使って管理できる。

ここまでくれば、ITエンジニア諸氏にはGitHubなどのGit WEBサービス上にリモートリポジトリを設定し、Pull Requestベースでお互いの原稿ファイルをレビューしたり、ローカルでは好みのエディタを使ってLinterやフォーマッターを使って原稿の質を確保する…といったメリットがすぐに思い浮かぶだろう。(コードではなく文章を書く場合にもTextlintというものがあり、これが素晴らしい仕事をしてくれる)

github.com

事実、技術書の執筆ではこうしたコラボレーションツールと、Textlintを用いたMarkdown原稿での運用事例が山のように転がっている。(MarkdownでなければRe:viewが多いか)

ただ、幻想サイクルの共著者すくみずさんはどちらかというとハードウェア寄りのスキルの持ち主なので、Gitを設定してVSCode上でFormat on SaveしながらLinterの吐いたWarningを読んで修正…ということを1から身に着けてもらうのは難しい。繰り返しだが、年に2回のイベントでしか使わないスキルを身に着けるというのは無理がある。大抵は次回までに体が忘れている。

そこで、ITエンジニアでなくとも使いやすいツールで共同作業しながら、こうしたソースと成果物を分割できるワークフローを回せないかと試行錯誤している。

GoogleDriveでファイルを共有しながら、.mdファイルをいじる…ということもやってみたのだが、テキストエディタMarkdownをいじるという行為をPrettier無しでやると、画像リンクや改行など"流儀"の差が出てしまう他、校正や見直した部分がわからなくなったり、最終編集時刻を見ずにいじって原稿データのデグレが頻発するなど、気分のいいものではなかった。

ようやくこの記事の本題だが、C102では下記のステップで原稿を作った。手戻り少なく成果物もわかりやすい状態を維持できて、中々いい具合だったので記録に残しておきたい。

  1. 下書きと構成をNotionでコメント入れながら試行錯誤(Notion内)
  2. Markdownエクスポートしたものをリポジトリに入れてTextlintとPrettierで綺麗にする
  3. VivliostyleでPDF作って校正(チャットでやり取り)

1. Notionでの原稿下書き

Notionでの下書きについては特に何も考慮することはない…わけでもない。

エクスポート時に、Notionにおける「ページ」が1つのMarkdownファイルになるため、これを意識したページ構成にする必要がある。また、後々は原稿と画像ファイルの紐付け管理のために、Markdownファイルと、それに伴うアセットファイルがうまく整理されると嬉しい。

Vivliostyleの設定ファイルから、理想的な下書きの吐き出し方を考慮してページ構成を決める必要がある。

//vivliostyle.config.js

module.exports = {
  title: "限界自転車部屋",
  author: "Gen and skmz",
  language: "ja",
  size: "JIS-B5",
  theme: "theme_print.css",
  tocTitle: "目次",
  entry: [  //原稿用Markdownのパス
    { path: "toc.md", rel: "contents", theme: "theme_toc.css" },
    "chapter0/intro.md",
    "chapter1/chapter1.md", 
    "chapter2/chapter2.md",
    "chapter99/outro.md",
  ],
  entryContext: "./contents",
  workspaceDir: ".vivliostyle",
  output: "./output/bike-room-catalog.pdf", 
  toc: true,
};

(個々人の好みはあるにせよ)チャプターごとにディレクトリを分けておきたいところ。

Vivliostyleは章ごとにファイルを指定するので、Notionページも章ごとに分けておく。(これは粒度が大きすぎるので、もっと細かくファイルを分けたいところだがVivliostyleの設定を見る限りこれ以下の粒度にはできないっぽい)

サブページを出力し、フォルダ分けするオプションをONにすると都合のいい出力となる

それっぽいオプションをONにして出力すると、いい具合にページごとのディレクトリが作成される。

ページ名+ハッシュがアセットのディレクトリ名となり、日本語名は文字化けするので英字必須。この変換・出力は5分ほどかかる点に注意。

後処理の必要性があるファイル名とディレクトリ名だが、後ほど処理する

Google Docsライクな共同作業能力

ページの編集自体は何の変哲もなく、Googleドキュメントのようなオンライン同時編集と、履歴管理・コメント管理ができる。これでレビューや章立ての議論・見直しを行いながら本文を書きあげていく。

Gitでバージョン管理して複数案検討したり、それぞれの案で現物出力したいなどの欲は出るが、この方法でも必要十分な議論はできる。

2. エクスポートした原稿を整形し、改ページなどをいじる

ここからはITエンジニアの作業。

エクスポートしたファイルたちを取り込むvivliostyle.config.jsを書き、フォーマッター・Linterを用いて原稿を綺麗にする。そのうえで、Vivliostyleのプレビュー機能を使いながら改ページや段落の分割などを行っていく。

NotionエクスポートしたMarkdownの下処理

Lintの表記を修正したくなるところだが、キャプション付き画像のキャプションがMarkdown Image記法と本文で2重に出力されたり、キャプションnullの画像はファイル名が挿入されるなどの都合があるため、これらを一括修正する必要がある。

ファイル名はともかく、段落にもキャプションが入ってしまうのは仕様としてイケていないので何とかしてほしい…

キャプション未設定だとキャプションにファイル名が入る

なぜかキャプションが二重に…

これらを正規表現で修正。

また、ページ名とハッシュの間にもスペースがありLinuxでは困るという点については、ディレクトリとファイル名のスペースを削除してMarkdown内は置換でなんとかする。

まとめると、下記の手順となる。

  1. アセットディレクトリのスペースを削除し、Markdown内の相対リンクも半角スペースを消したディレクトリ名で置換
  2. キャプション被りを正規表現置換で修正する
    1. ^!\[(.*)\]\((.*)\)\n\n\1 to ![$1]($2)
  3. ファイル名キャプションの画像を修正する
    1. ^!\[(.*.[png|jpg|JPG|PNG|webp|WEBP])\] to ![ ]

Markdown文法上は、キャプションの中身を0文字にしても問題ないが、Vivliostyle側でエラーになってしまうので半角スペースを挿入している。

校正

Prettierやmarkdownlintで自動整形が走った後に、textlintのサジェストを一つ一つ確認しながら、ルールを無効にするか、文章を修正するか判断して機械的に判定できる点の構成を行っていく。

プリインのルールや、技術書向けのルールを適用している

利用している校正ルールの一覧

これだけのルールを人力で見つけるのは非現実的なので、機械的に検出することで漏れをなくし、時間も劇的に節約できる。

改ページ処理

実際の出来上がりをプレビューしながら、自動改ページを手直しする

Vivliostyleが画像や段落に応じて自動的に改ページを入れてくれるのだが、そのままにしておくと「写真が1枚載っているだけ」というページや、「改ページ直後にその後の本文と無関係な写真が入る」という現象が起きる

これらを改ページ用の不可視区切り<hr class="page-wrap" />を挿入して改ページを増やしたり、段落分割を行ったりして見た目にダサい本文にならないように修正。

3. PDFプレビュー

一通りの処理が終わったら、PCで読めるPDFファイルに出力して最終校正を行う。

グレイスケール化や、フォントのアウトライン化を行わないことで軽量なPDFを出力して読み合わせし、温かみのある課題表を手作りして対応。

// package.json
  "scripts": {
    "lint": "textlint README.md 'contents/**/**.md'",
    "lint:fix": "textlint --fix 'contents/**/*.md'",
    "build": "run-p build:scss build:vivliostyle",
    "build-ebook": "run-p build:scss build:ebook",
    "build:scss": "sass theme:.",
    "build:vivliostyle": "vivliostyle build --press-ready --preflight-option gray-scale",
    "build:ebook": "vivliostyle build -o ./output/ebook.pdf",
    "dev": "run-p preview watch:scss",
    "preview": "vivliostyle preview",
    "preview-ebook": "vivliostyle preview -c vivliostyle.ebook.config.js",
    "preview-build": "npm run build:scss && vivliostyle build -o ./output/preview.pdf",
    "validate": "vivliostyle-theme-scripts validate",
    "watch:scss": "sass --watch theme:."
  },

必要なコマンドを作っているだけでnpmスクリプトが肥大化するが、必要なので仕方ない。

ここばかりは人力が必要。有料版Acrobatを持っていたりすると、直接赤を書き込めて捗る。

まとめ

GUIで共同作業できるNotionと、IDEのパワーを使うことで、入稿期限数時間前まで変更をかけても原稿ファイルが破綻しない。実際にC102ではデスマーチ対応実績も積むことができ、期せずして信頼性の高いワークフローに仕上がったと思っている。

その他:テーマについて

本のテーマは、公式で配布しているvivliostyle-techbookを自分用に手直ししたものだが、飾り気に欠けるのが改善点と思いながら着手できていない。

ひとえに、CSS力が足りない他、Vivliostyleはmarkdown→HTML→PDFという内部変換を行っている影響も大きい。手になじんでいるNext.jsやGatsbyでHTMLを出力すればMDXと合わせてリッチな原稿を作れる可能性を考えたのだが、クライアントサイドのJavascriptCSS-in-JSライブラリがVivliostyleのPDF化ライフサイクルのどこで評価されるか分かりづらいこともあって手を出せなかった。

最近使いだしたAstroは、ゼロランタイムのCSSライブラリと合わせて完全に静的なHTML+CSSを出力できるため、こうしたフレームワークを使って冬コミ原稿にチャレンジしたいところ。

2023年版 地方税(自動車税・固定資産税)と国税(所得税など)を手数料ゼロでクレジットカード納付する

今年も税金の季節になってきました。

自動車税や固定資産税、例にもれず我が家にも届いたのですが、当然コンビニに赴いて現金で納付…なんてことはせずに絶対にクレジットカードで支払いたい。しかも手数料は払いたくない。

色々仕様を調べたらいいルートがあったため記録。

前置き

筆者はポイ活自体は好きだが、手間をかけたポイ活はしたくなくプラチナプリファード一本化で「最小限のアクションでそれなりの還元」を追及している。

同様にチャージ式の決済サービスも嫌っているので、基本的に残高が必要な決済サービスは無視です

地方税自動車税・固定資産税)

三井住友カードをLINE Payのチャージアンドペイに登録して、請求書払いをする。これだけ。

チャージアンドペイは、LINE Payクレジットカードか、三井住友カードだけで利用できるクレカ直結型の支払い手段。(いわゆるクレカプロキシ)

www.smbc-card.com

linepay.officialblog.jp

今年から運用されているeL-QRからf-regiのクレジットカード払いを選択すると、支払手数料がかかるが、LINE Pay経由なら手数料なし

Vポイントも、通常のショッピングと同じ割合で付与される。楽天PayやファミPayもポイント利用や還元で候補になりえるが、チャージが嫌だし普段使っていない経済圏なので使わず。

国税

今回の件とは別だが、オマケで記録。

国税の支払いサイトはAmazon Payを基本的に使う。なぜならギフトカード支払いをするとプライム会員なら1%Amazonギフト券で還元されるから。ギフト券を購入したクレジットカードが1%還元なら合計2%還元。

pay.amazon.co.jp

そして結構な頻度でキャンペーンもやっているので、実質還元率が上がることも多い。もはやAmazonギフト券Amazonで使ってはいけないレベル。

ふるさと納税やその他の通販サイトでもAmazon Payを使えるところは意外と多いので、意識してみると結構お得になる。

Amazonそのもので買い物する際は毎回ギフトカード残高のチェックを外さないといけないので、面倒だが…

Gatsby CloudとCloudflareを利用した二重CDNで日本向けに効率よく配信する

TL; DR

  • Firebase Hostingのデプロイに時間がかかる
  • VSCodeでの執筆はやめたくない
  • 日本にエッジがないホスティングでもCloudflare経由でいい感じに配信できた

これまでの構成と課題

これまでは、Gatsby CloudでビルドしたアセットをFirebase Hostingを使ってホスティングしていた。

Gatsby Cloudに日本のCDN Edgeが無いため、TTBFなどの指標が悪く、定性的にも回線品質が良好な自宅のNW環境でもわかりやすくレスポンスが悪かったので、メインコンテンツをホスティングするには不適だと判断。ブログは画像も多いので、基本無料で従量課金もリーズナブルということでFirebase Hostingを使っていた。

ユーザー向けの体験は問題ないが、記事が多くなるにしたがってデプロイに時間がかかりすぎるというデメリットが大きくなってきた。

ビルド時間よりデプロイ時間の方が長い有様

直近では、ビルド時間が数分にもかかわらず10分以上もデプロイにかかってしまうという事態が頻発しており、ちょっとした更新でも反映が遅くストレスが高くなっていた。

ビルドに関してはGatsby Cloudでログを見ることができるのだが、デプロイコマンドは完全にブラックボックスで行われているため改善策が打てなかった。(おそらくファイル数が多すぎるのだと思う)

システム構成も数年変更を入れていない他、流石に心理的限界が来たのでビルド・デプロイのプロセスを見直すことにした。

検討内容

変更のPushから反映までの時間を短くするという観点で、いくつかの案を検討した。

ビルド時間とファイル数は殆どが画像ファイルの最適化によって生み出されるものなので、画像ファイル処理をどうにかするのが基本線。VSCodeと各種フォーマッタによる執筆体験は失わないようにするのが絶対要件。

  1. 現在ローカルファイルで管理している記事データを画像データをContentfulからの配信に切り替える(画像最適化オフロード+コンテンツ管理の外出し)
  2. 画像のみ画像CDNサービスを利用して配信する(画像最適化オフロード+画像管理のみ外出し)
  3. 二重CDNだけど、日本にエッジサーバーのあるCDNを噛ませてGatsby Cloudから配信する(デプロイ時間への対処)

1や2を基本線に考えており、ContentfulでもローカルMarkdownのように編集するツールがあったので1が最も良い候補に見えたのだが、Gatsby MDX v2プラグインではリモートのMDXを扱えない点が決め手になってしまいコンテンツはGitリポジトリ内という前提が新たに追加された。

3の場合、候補はAmazon CloudfrontとCloudflare。Cloudfrontの方が細かい設定に分があるが、DNSをCloudflareで管理していたこともあり運用対象が増えない点・完全無料である点を重視。まずはCloudflareで検証した後、リダイレクトルールなどを細かく設定したい場合はCloudfrontを使うという気構えで実装面の調査に入った。

検証

計画しているサービス間連携は上記の通り。

ユーザーに最も近い配信場所はCloudflareにするつもりだが、Gatsby CloudもCDNを持っているため、二重CDN構成となる上に、Gatsby CloudのCDNルールはかなりの部分隠蔽されている。不測の挙動があった場合はエスパー力が要求されると思われる。

移行前のテストとして、既存のドメインはFirebaseを向けたままテスト用のサブドメインを切ってGatsby Cloud Hostingに登録し、Cloudflare経由での配信を試みた

移行手順

Gatsby CloudはHostingオプションをONにする。

Firebaseのリダイレクト設定をGatsby Cloudで有効にするため、firebase.json に記載されていた内容をgatsby-node.tscreateRedirect構文で置き換える (gatsby-plugin-gatsby-cloudが必要)

設定画面上で独自ドメインを登録すると、SSL証明書を自動発行するためのCNAMEレコード(GatsbyCloud Hostingで割り当てられるドメイン)を指定するよう指示される。

Cloudflare側は、CDNを有効にするため、DNSでProxiedをONにしてレコードを登録する。

リダイレクトループを避けるため、HTTPS接続のみを利用するようSSL/TLS設定を変更することも忘れずに。

Cloudflare DNS Proxyと各ホスティングサービスの組み合わせ

ここで1つ問題が発生する。

Cloudflare DNSでキャッシングのためのProxyを有効にすると、設定上はCNAMEレコードがあるように見えるが、DNSレコードの実体はAレコードでCloudflareのIPアドレスを指す。

developers.cloudflare.com

つまり、Gatsby Cloud側でレコードが正常に設定されているように見えず、いつまで経ってもSSL証明書が発行されない。気持ち悪いので、試しにカスタムドメインGatsby Cloudに登録しない状態でDNSを向けたところ404となってしまった。

実際のアクセスはCNAMEはGatsby Cloudの所持しているサブドメインに向いているため、こちらでSSL証明書が使えれば問題無くクライアント・CDNからアクセスできるため今回はこのWarningを無視することにした。

※Firebaseはサブドメインに指定のレコードが正常登録されない場合、そもそもHostingが機能しない仕様だったが、Gatsby Cloudはカスタムドメインは登録さえすればHostingできるようだ

結果

レスポンスヘッダからCloudflareを介して配信されていることがわかる

レスポンスヘッダより、Cloudfrontを介して配信されていることがわかる。エッジ→オリジンまでもCloudflareのNWで最短ルートを通るようにしてくれるため、Gatsby Cloudのエッジが日本にないにもかかわらずCFでキャッシュが効き10msそこそこでレスポンスが返ってくる状態になってくれた。

HTMLはGatsbyCloudでキャッシュされているがCFではデフォルト設定でキャッシュされない(キャッシュヘッダを見て決めている)ため、CDNキャッシュ用に設定をいじるともう少しよくなりそう。

Astro1.9からAstro2.0へのアップデートメモ

サークルサイトをAstroで書き直したのだが、リリース日にAstro2.0が発表された上に、泣き所だったMDXコンテンツの呼び出しが改善されていたので即書き直しすることにした。

docs.astro.build

基本はガイド通りに進めていく。

1. コンテンツファイルの場所移動

pageディレクトリに入れていたMDXコンテンツたちを/content/{contenttype}へ移動(今回は/content/portfolioとした)

2. スキーマ定義

config.ts

// 1. Import utilities from `astro:content`
import { z, defineCollection } from "astro:content"
// 2. Define your collection(s)
const portfolioCollection = defineCollection({
  schema: z.object({
    title: z.string(),
    date: z.date(),
    draft: z.boolean(),
    tags: z.array(z.string()),
    cover: z.string().optional(),
    layout: z.string().optional()
  })
})
// 3. Export a single `collections` object to register your collection(s)
//    This key should match your collection directory name in "src/content"
export const collections = {
  portfolio: portfolioCollection
}

3. Astro.glob()していた部分の書き直し 、ルート追加

[slug].astro

---
import { getCollection } from "astro:content"

export async function getStaticPaths() {
  const entries = await getCollection("portfolio")
  return entries.map((entry) => ({
    params: { slug: entry.slug },
    props: { entry }
  }))
}

const { entry } = Astro.props
const { Content } = await entry.render()
---

<Content />

ルーティングはNext.jsっぽい書き方。

markdown用のレイアウトファイルを編集する必要があるかと思ったが、そちらは維持したまま簡単なルーティング用のファイルを置けばとりあえず動いた。 ただ、そのままでは肝心な記事レイアウトのastroコンポーネントで型安全な開発ができないので、レイアウト全体を[slug].astroに寄せている。

github.com

これで、各MarkdownファイルにlayoutのFrontmatterを設定する必要もなくなった。

4. ついでにRSSも追加

pages/rss.xml.ts

import rss from "@astrojs/rss"
import { getCollection } from "astro:content"

export const get = async () => {
  const entries = await getCollection("portfolio")
  return rss({
    title: "Gen's Portfolio(幻想サイクル公式WEBサイト)",
    description:
      "ゲン(@gen_sobunya, gentksb, gensobunya)のポートフォリオサイト兼、同人サークル「幻想サイクル」の公式WEBサイト",
    site: import.meta.env.SITE,
    items: entries.map((entry) => ({
      title: entry.data.title,
      pubDate: entry.data.date,
      link: `/${entry.slug}/`
    }))
  })
}

5. frontmatter処理の変更

data.somefrontmatterで取得するように変更。String前提だった処理も型がついて扱えるようになったので、コードがわかりやすくなった。

TwitterシェアボタンがAndroidで動かないと嘆いている人へ

ここ数カ月、AndroidTwitterシェアリンク(https://twitter.com/share?~~~)がTwitter for Androidで機能していません。

アプリにIntentで飛んだ後に「入力した単語の検索結果はありません」とTweetではなくSearchに飛ばしていると思われている挙動になっています。

この事象が見られた直後には、「どうせすぐHotfixされるだろう」と思っていたのですが、どうやらTwitter社はこれを優先的に直す気はなさそうなので暫定対応しました。

前提

react-sharenext-share など、公式のサンプル以外でツイートボタンを実装している

未確認ですが、公式ウィジェットでボタンを実装していればひっかからないのかもしれません

修正方法

Twitterのシェアボタンには2通りあり、https://twitter.com/share?~~~ を使う方法と、https://twitter.com/intent/tweet?~~を利用する方法です。

上記前提の二つのライブラリは、前者を使っており、このURL経由でツイート画面を開くとアプリの不具合にぶちあたります。

developer.twitter.com

公式ドキュメントを参照しながらintentのURLに置き換えます。Twitter Widgetスクリプトの配置が必要に見えるので、従っておきます。