こんにちは、hachi8833です。
2013年から私が運営している日本語のあからさまなエラーを検出するWebサービス、enno.jpをRails 8.1とRuby 4.0.1で完全に作り直し、2月8日からリリースしました。使い方はリニューアル前と基本的に同じですが、多数の改良を加えました。
リニューアルの概要については更新情報に手短に記載してありますが、本記事ではRailsやSaaSなどについてもう少し説明します。個別について詳しくは追って記事にしたいと思います。
1: SaaSをHerokuからfly.ioに乗り換えた
SaaS選定の基準は「SQLite3が使えること」「東京リージョンがあること」としました。
Herokuは現在に至るまでSQLite3を利用できず、render.comは2026年2月時点でまだ東京リージョンがない(Singaporeはある)ため、fly.ioに決定しました。
fly.ioを使ってみて
後発だけあって、ダッシュボードが整っていて使いやすい印象です。
fly.ioの「マシン」という概念は、HerokuのDynoに少し似ている気もしますが、課金はマシン数というより「割り当てリソース量(CPU、メモリ、ボリューム、転送量など)」に依存します。また、fly.ioの「マシン」にはHerokuのようなメンテナンスモードが標準では用意されていないので、メンテナンス画面は自分で表示する必要があります。
HerokuのDynoと異なり、マシンは常に動く前提なので、マシンを止めるとfly ssh consoleもできなくなります。また、fly ssh console環境にはpsやptreeコマンドが標準では入っていません。
fly deployコマンド一発でデプロイできるのと、デプロイの設定がfly.tomlでほとんど済む点はありがたいです。fly deployはDockerが前提で、Rails生成のDockerfileとbin/docker-entrypointがそのまま使われるのも便利です。
ただしgitを介さないデプロイなので、不要な巨大ファイルなどは.dockerignoreにも追加して除外する必要があります。現在はローカルからfly deployコマンドでデプロイしていますが、将来はGitHub ActionsのCI連携でデプロイするようにする予定です。
UptimeRobotで監視
最小限の監視として、今回からUptimeRobotというサービスを使って5分に1回/upで死活監視を行うようにしました。UptimeRobotのこの監視は50件まで無料で使えます。個人アプリなら十分だと思います。
参考: UptimeRobot: Free Website Monitoring Service
偶然ですが、今回のenno.jpのリニューアルと前後して、Herokuが事実上のメンテナンスモードになることが報じられました。滑り込み感半端ないです。
ブログ書きました: Herokuが事実上のメンテナンスモードに移行。新機能の導入よりも品質と運用の維持に重点を置くと発表https://t.co/utuDkiNMO9
— Publickey (@publickey) February 8, 2026
2: Railsに余分なものを極力足さない
今回のリニューアルの目標のひとつが、「できるだけRails wayに沿ったidiomaticなRailsアプリにする」でした。以下のrails new以後、できるだけ素直な作りを維持しました。
$ bundle exec rails new . --main -c tailwind --skip-action-mailer --skip-action-mailbox --skip-action-text --skip-jbuilder --skip-system-test
パスはなるべくデフォルトで
Zeitwerkはapp/services/やapp/components/のようなディレクトリはパスの追加なしでオートロードしてくれますが、app/assetsやapp/viewsやapp/javascript/などはデフォルトでオートロードから除外されます。
参考: Rails の自動読み込みと再読み込み - Railsガイド
以前の私はコンポーネントをapp/views/components/に置いていたのですが、標準のapp/components/に置く方が素直だよとAIにツッコまれたのをきっかけに、ViewComponentなどで使うパスはなるべくカスタマイズせずにデフォルトのものを使うようにしました。
3: SQLite3を利用
以下のシリーズ記事で紹介されているように、Rails 7.1からSQLite3への対応が強化され、Rails 8でひととおり完了したので、production環境で使って実感してみたかったのでした。
参考: 🛤 Rails 8はSQLiteで大幅に強化された「個人が扱えるフレームワーク」(翻訳)|YassLab 株式会社
enno.jpでもSQLite3を採用し、前述のようにそのためにfly.ioを選びました。DBをファイルとして扱えるのはなかなか便利ですね😋。
SQLite3のバックアップについて
SQLite3のバックアップをどうするか調べました。以下の記事が参考になりました。
参考: 最近のlitestreamと安DB界隈 --2024年の記事です
fly.ioにはlitestreamの作者であるBen Johnsonがいるのが強みと思えます👍。
なお同記事によるとlitestreamの開発が2024年頃に滞っていたようですが、現在は再び活発に動いています。productionで使うならlitestreamはいけそうです。
なお自分はSQLite3周りをもう少しシンプルにしたかったので、当初以下のTursoというサービスを使ってみました↓。
しかしTursoのラッパーであるlibsql-activerecordがなかなか思うように動かず、リポジトリも2年前から更新されていないのでやめました。
enno.jpの今のサービス内容だと、クラウドベースのSQLite3はトゥーマッチ気味と思えたので、代わりに、fly.io上でsqlite3 #{DB_FILENAME} ".backup #{backup_filename}"でSQLite3ファイルをバックアップしてローカルに保存する簡単なスクリプトを作りました。
4: 開発環境をDocker化せず、miseで環境をセットアップした
私も少し前まで開発環境のDocker化(主にVS Codeでのdevcontainer)を試してみました。その前はdipを使って開発環境をDocker化していました。
参考: Dev Containerでの開発ガイド - Railsガイド
しかし今回のリニューアルでは、enno.jp開発環境をDockerとdevcontainerなしで構築しなおしました。
きっかけは、昨年のRails World 2025でDHHが「開発環境のDocker化は面倒を増やす」「開発環境のDocker化はRDBMSだけでいい」と発言していたのを見たことでした。
再現可能なローカル開発環境をDockerなしで構築するために、以下の記事にも書いたmise(mise-en-plus)を使いました。RubyやLintツール、環境変数をmise setupだけで一発構築できるのは便利ですね😋。
5: テストはminitestのみ、システムテストなし
テストはminitestのみとしました。
test
├── components
│ ├── patterns
│ ├── previews
│ ├── proofreads
│ └── shared
├── controllers
│ ├── inflections_controller_test.rb
│ ├── patterns_controller_redos_test.rb
│ ├── patterns_controller_test.rb
│ └── proofreads_controller_test.rb
├── fixtures
│ ├── files
│ ├── inflections.yml
│ └── patterns.yml
├── helpers
├── initializers
│ └── warning_test.rb
├── integration
│ └── buffer_management_stability_test.rb
├── jobs
├── models
│ ├── concerns
│ ├── inflection_test.rb
│ ├── pattern_redos_test.rb
│ ├── pattern_test.rb
│ └── text_matcher_test.rb
└── test_helper.rb
参考: Rails テスティングガイド - Railsガイド
また、システムテストやcapybaraを使わないことにしました。これもDHHの以下の発言がきっかけで、システムテストの遅さに辟易していたことが主な理由です。
代わりにコンポーネントのテストを書いています。
システムテストがないおかげでテストは快速です。
$ bin/rails test
≈ tailwindcss v4.1.18
Done in 125ms
Running 172 tests in parallel using 8 processes
Run options: --seed 54695
# Running:
............................................................................................................................................................................
Finished in 1.986801s, 86.5713 runs/s, 406.6839 assertions/s.
172 runs, 808 assertions, 0 failures, 0 errors, 0 skips
現時点の課題は、Stimulusをテストする方法です。
6: ViewComponent v4とTailwind v4でコンポーネント化
ViewComponentとv4とTailwindがともにv4になったことでやっと諸々落ち着いたので、これをベースにコンポーネント化しました。
見てのとおり、コンポーネントで使うStimulusコントローラもここに置いています。
app/components
├── application_component.rb
├── patterns
│ ├── category_select_component.html.erb
│ ├── category_select_component.rb
│ ├── category_select_controller.js
...
├── proofreads
│ ├── error_list_component.html.erb
│ ├── error_list_component.rb
│ ├── error_list_controller.js
│ ├── error_list_scrollback_controller.js
...
└── shared
├── copy_button_component.html.erb
├── copy_button_component.rb
├── copy_button_component_controller.js
...
├── dialog_component.html.erb
└── dialog_component.rb
なおApplicationComponentはViewComponent::Baseを継承しただけで中身はまだ空です。
Lookbookのプレビュー
ViewComponentにプレビュー用エンジンであるLookbookを組み合わせています。
プレビューはAI(もっぱらCopilot経由)で書かせ、test/components/previews/以下に配置しています。
localhost:3000/lookbook/で以下のようにコンポーネントをプレビューできます。
なお、最初に何をコンポーネント化するか迷っているのであれば、ぜひダイアログボックスをコンポーネント化することをおすすめします。
Tailwindベースのデザインシステムとアフォーダンス
「デザインシステム」としてのデザイントークンをapp/assets/tailwind/application.css↓に登録し、これを元にデザインの統一化を図っています。私はデザインは当然素人なのですが、今後Webデザイナーに依頼することがあってもよいようにしてみました。
@theme {
/* フォント - 日本語最適化(Inter + システムフォント) */
--font-sans:
"Inter",
-apple-system,
BlinkMacSystemFont,
"Hiragino Sans",
"Hiragino Kaku Gothic ProN",
"Yu Gothic UI",
"Meiryo",
"Segoe UI",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji";
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
/* Primary(Custom Blue) */
--color-primary-50: #f0f9ff;
--color-primary-100: #e0f2fe;
--color-primary-200: #bae6fd;
--color-primary-300: #7dd3fc;
--color-primary-400: #55ADF9;
--color-primary-500: #4BA3F8;
--color-primary-600: #3D8AE9;
--color-primary-700: #0369a1;
...
}
デザインシステムについて詳しくは以下の記事をどうぞ。
また、必要に応じてapp/assets/tailwind/application.cssに「アフォーダンス」を登録して、要素にまたがる基本スタイルを抽象化しました。「アフォーダンス」は常に通常のTailwindユーティリティクラスで上書きできます。
@utility af-btn {
:where(&) {
@apply inline-flex items-center justify-center px-4 py-2 text-sm leading-5 font-semibold whitespace-nowrap border border-transparent rounded-2xl shadow-sm shrink-0 transition-[color,background-color,border-color,text-decoration-color,fill,stroke] duration-300 ease-in-out;
}
@variant focus-visible {
:where(&) {
@apply outline-2 outline-transparent outline-offset-2 ring-2 ring-offset-2 ring-offset-white;
}
}
}
7: recheckツールによるReDoS予防
enno.jpには9,500件を超える日本語エラー検出用の正規表現が登録されています。
これらの正規表現からReDoSを徹底的に排除したかったので、以下のrecheck(Scala言語で書かれています)をGoでラップしてCLIで使えるようにし、全件のReDoS検査を実施しました。
- Playground | recheck -- ここで正規表現のReDoSを検査できます
バックトラックに極力依存しないよう、普段から*や+や|を極力使わない正規表現を心がけていたおかげで、検査の結果、ReDoSのリスクがある正規表現は9,500件のうち数件しかありませんでした🎉。
以下はReDoS検査用のgorecheck実行の様子です。
さらに、enno.jpのメンテナンス画面でも個別にReDoSを検査できるようにしました。
参考: Ruby 3.2 で ReDoS を排除する - RubyKaigi 2023
8: パフォーマンス
以下はGoogleのPageSpeed Insightsでリニューアル後のenno.jpを測定した結果です。こんなハイスコアを出せたの初めて😂(もっとも広告を表示する前の状態ですが)。
コンポーネントを適切に導入できたこと、各種バリデータの出すissueを1つ1つ修正したのが効いたようです。JSのパフォーマンスについては、Tailwindを見直してレスポンシブの再計算を減らしたのも効きました。
- PageSpeed Insights
- Ready to check - Nu Html Checker
- OGP確認:facebook、twitter、LINE、はてなのシェア時の画像・文章を表示 | ラッコツールズ🔧








