Webサイトの制作と運用

Framerで作ったサイトを自前ホスティングに移した。何が壊れて、どう直したか

ノーコードで作ったサイトを静的サイトとして自前ホスティングに移す作業を、自社サイトで実際にやりました。壊れた箇所と直し方、移行しない方がいい場合を書いています。

このサイトは Framer で作っていました。2026年5月に、中身をそのままローカルへ移して Vercel に載せ替えました。ページ数は2枚だけのサイトですが、それでも一度では終わらず、壊れた箇所を1つずつ直していく作業になりました。

何が壊れて、どう直したか。同じことをやろうとしている人が判断できるように、実際に踏んだ順で書きます。

なぜ移したか

理由は3つありました。

1つは、払っているものと使っているものが噛み合わなくなったことです。ノーコードツールの価値は管理画面での編集にありますが、こちらはコードを書く人間しかいません。使わない編集環境を借り続けている状態でした。

2つめは、手を入れられる範囲の狭さです。構造化データを細かく制御したい、フォームの送信先を自前のエンドポイントに変えたい、といった要求が、ノーコードの管理画面の外に出てしまう。自社サイトを実験台にできない状態は、開発を売っている会社としては不便でした。

3つめは、記事を載せる予定があったことです。この記事もその一部です。ブログを足すとなると、テンプレートの制御を自分たちで持っておきたい。

やったこと

1. 実体を全部ローカルに落とす

「HTMLを保存」では足りません。Framer が吐くページは、rolldown ベースの ESM モジュールを <script type="module"> で読み込む構成になっています。ランタイム本体、React、モーション、Framer の共有ライブラリ、そのページ専用のバンドル。これらが相互に import "./xxx.mjs" で依存し合っていて、1つでも欠けるとハイドレーションが止まります。

止まると何が起きるか。見た目は出るのに、スクロール連動のアニメーションもメニューの開閉も動かない、という状態になります。ぱっと見では気づきにくい壊れ方をします。

そこで、HTMLから import を再帰的に辿って、依存を全部ダウンロードするスクリプトを書きました。最終的に落ちてきたのは、JSバンドルが7ファイル、画像とSVGが21ファイル、フォントが17ファイル。合計で約9MBでした。

2. 画像のクエリ違いを別ファイルとして扱う

ここで一番時間を使いました。

Framer の CDN は、画像URLのクエリで実時間リサイズをします。?width=2160&height=1536&scale-down-to=512 のような形です。同じ画像IDでも、クエリが違えば中身の違う画像が返ってきます。 画面幅ごとに違うクエリが使われるので、デスクトップだけ見て「全部落とせた」と判断すると、モバイルで画像が出ません。

解決策は単純で、クエリ文字列をハッシュ化してファイル名に混ぜました。<画像ID>_q<ハッシュ8桁>.png という形です。結果として、同じ画像IDで4つのファイルが並ぶこともありました。

これを見つけたのは、複数の画面幅で実際にブラウザを開いて、最後までスクロールさせる検証を書いてからです。遅延読み込みの画像は、スクロールしないとリクエストが飛びません。ページを開いただけの検証では素通りします。

3. フォームの送信先を差し替える

お問い合わせフォームは Framer のバックエンドに送信されます。契約を切ると当然止まるので、ここを自前に置き換える必要がありました。

問題は、送信ハンドラがバンドルされたJSの中に閉じ込められていることです。グローバル関数を上書きする、といった手が使えません。

とった方法は、document レベルでキャプチャフェーズの submit リスナーを仕込むことでした。イベントがフォーム要素に届く前に横取りして、preventDefault()stopImmediatePropagation() で Framer 側の登録済みハンドラを止め、自前のエンドポイントに JSON で投げ直します。

送信先には Vercel の Serverless Function を1つ置きました。受信通知は Slack の Incoming Webhook に流しています。メールにしなかったのは、送信ドメインの認証やメール基盤の用意が要らないからです。

4. 隠しフィールドの罠に気づく

フォームのHTMLを読んでいて見つけたのですが、Framer のフォームには aria-hiddentransform: scale(0) で隠されたダミー入力が11個仕込まれていました。websitecompanymessagesubjecttitledescription といった、いかにもボットが埋めそうな名前です。

スパム対策の仕掛けです。人間には見えないので空のまま送信され、自動入力するボットだけが値を入れてしまう。

自前のエンドポイントでも同じ判定を実装しました。これらのフィールドに値が入っていたら、送信せずに 200 を返します。エラーを返すと、攻撃側に判定ロジックの存在が伝わるので、静かに無視する方を選びました。

5. エディタのオーバーレイの読み込みを止める

Framer が吐くHTMLには、エディタの「Edit」オーバーレイを読み込むスクリプトが残っています。ログインしていなければ何もしないので害はありませんが、使っていないツールへの通信が残ることになります。バンドル側に手を入れて無効化しました。

解析用のビーコンも残置されていました。これはブロックされても無害なので、そのままにしています。

移して何が変わったか

手を入れられる範囲が広がりました。 構造化データを各ページに入れ、llms.txt を置き、sitemap を自動生成する仕組みを載せました。生成AIの検索から見つけてもらうための整備(LLMO対策)は、テンプレートを自分たちで持っていないと手が出せない部分が多く、これは移行の副次的な効果として大きかった。この記事が載っているブログの仕組みも、移行したから作れたものです。

更新の手軽さは失いました。 管理画面で文字を直して公開ボタンを押す、ということはできなくなります。今はテキストエディタとコマンドを触れる人しか更新できません。ここは明確なトレードオフです。

移行してから見つけた問題

移行そのものは終わったのですが、記事ページを作る段になって1つ見つかりました。

ローカルに落としたフォント(Noto Sans JP)が、サブセット化されていない全字種のTrueTypeファイルだったのです。1ウェイトあたり5.3MBあり、3ウェイト分で約16MBを配信していました。

トップページは非同期で読み込んでいるので表示自体は止まりませんが、記事のような読み物のページで同じことをやるわけにはいきません。記事ページでは、unicode-range で分割された配信を使うようにしました。ページ内で実際に使われている文字を含むサブセットだけがダウンロードされます。

ミラーは「見た目が同じかどうか」で検証しがちですが、転送量は見た目に出ません。 移行の直後に一度、ネットワークタブの合計を見ておくべきでした。

移行しない方がいい場合

3つあります。

更新頻度が高く、更新する人が非エンジニアの場合。 ここが最大の判断軸です。週に何度も文言を直すサイトで、直せる人が管理画面しか触れないなら、月額を払い続ける方が安く済みます。人件費の方が高い。

ノーコード側の機能に依存している場合。 CMSコレクション、会員機能、A/Bテストといった、管理画面が提供する動的な仕組みを使っているなら、移行はそれらを自前で作り直す話になります。移行ではなく再構築です。

デザインを頻繁に変える場合。 ミラーした静的サイトは、元のノーコードツールとの同期が切れます。デザインを変えたければ、コードを直すことになります。

逆に、ページ構成が安定していて、更新が月に数回で、手を入れたい箇所がツールの外にある。この条件が揃っているなら、移行は検討に値します。Nue のサイトはそれに当てはまりました。

やるなら押さえること

  • 複数の画面幅で、最後までスクロールする検証を書く。 遅延読み込みの画像とクエリ違いは、これでしか見つかりません
  • フォームの送信を実際に通す。 見た目の再現だけで完了と判断すると、問い合わせが届かない状態で数日走ります
  • 転送量を測る。 見た目が同じでも中身は同じとは限りません
  • 正規URLを1箇所に集約する。 canonical、og:url、sitemap、構造化データが別々の場所から出ていると、あとでずれます。ずれると検索結果から消えます
  • 元のツールの契約は、動作確認が全部終わるまで切らない。 比較対象が消えると、何が壊れているのか判断できなくなります

ノーコードから自前ホスティングへの移行や、その後の運用設計については受託開発で承っています。移行しない方がいいと判断した場合は、そう伝えます。