このサイトは 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-hidden と transform: scale(0) で隠されたダミー入力が11個仕込まれていました。website、company、message、subject、title、description といった、いかにもボットが埋めそうな名前です。
スパム対策の仕掛けです。人間には見えないので空のまま送信され、自動入力するボットだけが値を入れてしまう。
自前のエンドポイントでも同じ判定を実装しました。これらのフィールドに値が入っていたら、送信せずに 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、構造化データが別々の場所から出ていると、あとでずれます。ずれると検索結果から消えます
- 元のツールの契約は、動作確認が全部終わるまで切らない。 比較対象が消えると、何が壊れているのか判断できなくなります
ノーコードから自前ホスティングへの移行や、その後の運用設計については受託開発で承っています。移行しない方がいいと判断した場合は、そう伝えます。