Back to list
初任職に就き、ホムページが極めて遅く読み込まれた話
I Joined My First Job and the Homepage Took Forever to Load
Translated: 2026/3/21 6:00:55
Japanese Translation
最初の本格的なフロントエンドの仕事に携わることを振り返ると、まだ痛覚を感じている。私は完全にコーディングの初心者ではなかった—コンピュータサイエンスの学位を持っており、多くのフルスタックの学校でのプロジェクト経験があった。しかしプロフェッショナルとしての仕事?ゼロの経験だ。プロダクショントラフィックもないし、パフォーマンスのバジェットもないし、本物のユーザーが離脱するのを QA が厳しく監視する状況もなかった。入社した時には、EC サイトのホムページが恐ろしく遅かった。QA は週に何回も同じメッセージを送信していた:「ホムページのラグがまた起きた😭」「ヒーロセクションとプロダクトグリッドが非常に時間がかかる」「3 秒以上の遅延によりユーザーが離脱している」といった内容だ。私はレポートを開き、それらの辛抱できないウォーターフォールグラフを見ながら、ただ痺れを切った感じを味わうしかなかった。スタックのドキュメントだけを見ると現代的で完璧なようだ:S3 と CloudFront を使い、シングルページアプリ(SPA)を構築した。「モジュラー化」するために、推奨事項やバナーなどのセクションには iframe を使用していた。理論上は即座に使えるものでした。実際には、重く脆い問題だった。私は何かおかしいことに気づいていたが、それを説明する言葉彙がまだなかった:全ての iframe は、私たちのモノリシックなアプリケーションが隠してきた巨大で冗長な依存グラフを暴露していた。単一の SPA は最初構築する際に清潔に見えます。1 つのリポジトリ、1 つのデプロイ。もし機能を他で使用したいなら、iframe は実用的なショートカットに見える:アプリを包み、取り込み、迅速にリリースする。それは罠だ。iframe は単に UI を埋め込むだけでなく、ランタイムを埋め込む。私達は単に「バナー」をページに落とすのではなく、巨大な React エコシステム全体を小さなウィンドウに落としました。全ての「モジュラー」セクションは、ブラウザに我らの巨大なストアアプリの完全なインスタンスを再起動させようとしていた。システムは技術的には現代的でありながら、アーキテクチャ的に過負荷となっており、iframes はこれを同時に作り出したわけではなく、それを無視できない状態を作ったのである。これが一般的に痛みが最初に現れる場所だ。ユーザーが単純なプロモーションページを開くと、ブラウザはストア全体アプリケーションをダウンロードする:プロダクトリストロジック、プロダクト詳細ロジック、イベントハンドリング、ルーティング、共有コンポーネント—そしてその間に積み重なって静かに蓄積したその他何もかも。それはあなたに、単一の小さなキiosk を訪問するだけのために、全マートのコストを支えさせます。モノリシックな SPA は、通常、その_bundle_ がユーザーが実際に閲覧しているページのサイズではなく、機能の数に応じて成長するため、この問題を引き起こします。実際には、これが以下に導きます:より大きな JavaScript パイロード、より長い解析と実行時間、より遅い最初のインタラクション、より多くのメモリ圧力、弱いデバイスに対するメモリ圧力の増加。iframe はこの不一致を可視化しました。小さな、目的のために構築されたプロモーション表面をロードする代わりに、ブラウザは埋め込みコンテキストの中でフルアプリケーションを初期化する必要があった。ユーザーは 1 つの機能を視覚化するだけで、機械はフルシステムの仕事を処理する。私たちは 1 つの大きなシステムを、いくつかの小さなものだと偽っていた。コアの性能の嘘:UI は小さいように見えるが、ランタイムのコストは大きく、iframe アーキテクチャはそれによりこの嘘を暴露し、1 つのページ上でそのコストを何回も支払うように強制した。2 つ目の失敗モードはあまり目立たず、そのためチームはしばしばそれを見逃し続けます。クローラビリティの問題は、クライアントレンダリングした SPA アーキテクチャから始まった。これは区別する価値がある:SPA はデフォルトでコンテンツを不可視化し、iframe はそれを複数の埋め込みコンテキストに分散させることで修正をより困難にした。しばらく間、私は主要な問題は「性能」の一般的な意味にあることを仮定した。だから、私の目撃した通常の疑惑を探した:ネットワークの遅延、画像サイズ、CDN ヘッダー、キャッシュの動作。それらは重要だった—しかしそれらではない全物語だった。より深い問題は、アーキテクチャの形状だった。アプリは、1 つのデプロイユニットの中に詰め込まれた多すぎる責任、多すぎた共有コード、多すぎる表面を持っていた。iframe はこの問題を作り出したわけではなく、それを暴露した。多すぎるインスタンスを必要とさせることで、我々のモノリシックなアプリケーションを複数回起動させた。1 つの大きなシステムを、我々はいくつかの小さなものだと pretend していた。それは過ちだった。送信者ではなくメッセージを責めること:私に答えはまだなかったことを認め、システムを直すための時間を求めることだった。それより、その症状に消火するだけではなかった。私は私のマネージャーへ提案した:サイトに食事を与える。曖昧な「Let's」という言葉だけでなく、具体的なアクションが必要だった。
Original Content
Thinking back to my first real frontend job still stings.
I wasn't a total coding newbie—I had a CS degree and plenty of full-stack school projects. But professional work? Zero experience. No production traffic, no performance budgets, and no QA breathing down my neck about real users bouncing.
When I joined, the e-commerce homepage felt brutally slow. QA kept dropping the same messages week after week:
"Homepage lagging again 😭"
"Hero + product grid take forever."
"Users are leaving because of the 3s+ delay."
I'd open the reports, see those agonizing waterfalls, and just feel paralyzed.
The stack looked modern on paper: S3 + CloudFront + Single Page App (SPA). To make it "modular," we used iframes for sections like recommendations or banners. In theory, it was plug-and-play. In practice, it was a heavy, fragile mess.
I could sense something was off, but I didn't have the vocabulary yet to explain why: every iframe was exposing a massive, redundant dependency graph that our monolith had hidden.
A single SPA feels clean when you first build it. One repo, one deployment. If you need to reuse a feature elsewhere, an iframe seems like a pragmatic shortcut: wrap the app, drop it in, ship fast.
That's the trap. An iframe doesn't just embed a UI; it embeds a runtime. We weren't just dropping a "banner" into the page; we were dropping an entire massive React ecosystem into a tiny window. Every "modular" section was forcing the browser to boot up a whole new instance of our giant store app.
The system was technically modern and architecturally overloaded at the same time. The iframes didn't create this overload—they made it impossible to ignore.
This is usually where the pain shows up first.
A user opens a simple promotion page, but the browser ends up downloading the entire store application: product listing logic, product detail logic, event handling, routing, shared components — and whatever else had quietly accreted over time.
That means you pay the cost of the whole mall just to visit one kiosk.
A monolithic SPA often creates this problem because its bundle grows with the number of features, not with the size of the page the user is actually viewing. In practice, that leads to:
larger JavaScript payloads
longer parse and execution time
slower first interaction
more memory pressure on weaker devices
The iframe made the mismatch visible. Instead of loading a tiny, purpose-built promotion surface, the browser had to initialize a full app inside an embedded context. The user sees one feature, but the machine does full-system work. We had one big system masquerading as several smaller ones.
Core performance lie: the UI looks small, but the runtime cost is large—and the iframe architecture exposed this lie by forcing us to pay that cost multiple times on one page.
The second failure mode is less visible, which is why teams often ignore it for too long.
The crawlability problem started with our client-rendered SPA architecture.
That's the distinction worth making: the SPA made content invisible by default. The iframe made it harder to fix by scattering content across multiple embedded contexts.
For a while, I assumed the main problem was "performance" in the generic sense.
So I looked for the usual suspects: network latency, image sizes, CDN headers, cache behavior. Those mattered — but they were not the whole story.
The deeper issue was architectural shape.
The app had too many responsibilities, too much shared code, and too many surfaces crammed inside one deployment unit. The iframe didn't create this problem—it revealed it by making us instantiate our monolith multiple times. It exposed one big system that we had been pretending was several smaller ones.
That was the mistake: blaming the messenger instead of the message.
I had to admit I didn't have the answer yet — and then ask for time to fix the system instead of just firefighting its symptoms.
So I went to my manager with a proposal: give the site a diet.
Not a vague "let's optimize." A real plan.
reduce the bundle
remove unused modules
split pages into smaller pieces
improve the build pipeline
stop making every page carry the weight of every other page
That conversation mattered as much as the code changes.
Sometimes ownership starts when you stop waiting for permission to understand the system.
I want to be honest about the scope here.
Think of it as load-bearing surgery before the real renovation.
We stripped away modules that were bundled but never used.
This sounds mundane, but dead code isn't harmless. It still has to be discovered, bundled, transmitted, parsed, and often evaluated. Every unused import is a small tax on runtime.
We introduced SWC to improve build speed.
That didn't just save developer time. It changed behavior. When builds are slow, people batch changes, avoid refactors, and hesitate to split code. When builds are fast, iteration feels cheaper. That makes architectural cleanup more realistic.
SWC didn't directly shrink our bytes, but it gave us the 10x faster build loops we needed to aggressively experiment with code-splitting without losing our minds.
Instead of making every page carry the whole app, we split features into smaller delivery units.
The rule was simple: if a user doesn't need a feature to view the current page, don't make them download it now.
This is where the unit of deployment matters. If the app surface is large, the bundle shouldn't pretend otherwise.
The transformation wasn't subtle.
Deployment time dropped from 10 minutes to about 2.
Loading time fell from more than 3 seconds to under 1 second.
Those aren't vanity metrics. They change how teams work. Faster deploys mean faster feedback. Faster feedback means more confidence. More confidence leads to better architecture decisions because people are no longer afraid to touch the code.
And better architecture decisions compound.
Here's the real takeaway.
If your app is big enough to contain multiple product experiences, promotion systems, and routing behaviors, then embedding it via iframe for modular reuse is usually the wrong default—not because iframes are bad, but because they force you to pay the full cost of your monolith multiple times.
Not because iframes are inherently bad.
Not because SPAs are inherently bad.
But because a bloated monolith embedded multiple times on one page creates a multiplicative performance disaster.
A better architecture usually does one or more of these:
serves distinct product experiences as distinct pages with properly split bundles
uses SSR or prerendering where crawlability matters
splits bundles by route and by feature so no page carries weight it doesn't need
keeps iframes for truly isolated embeds with minimal shared dependencies, not as a way to reuse a full application
reduces shared runtime between unrelated surfaces
The goal is simple: make the browser load only what the user needs, let search engines see what matters, and make failures local instead of global.
That's the bet I'd make again.
Not that every frontend should be micro-frontends.
Not that every SPA should be thrown out.
But that when your app starts behaving like a whole ecosystem, the right move is to make the monolith small enough that embedding it doesn't break your performance budget.
The hardest part of this project wasn't the bundle size or the deployment time. It was admitting that the architecture was making the load-time slower and the product worse.
Once I stopped treating the problem as a series of isolated bugs, I could see the pattern: too much code, too much coupling, and an iframe strategy that multiplied those sins instead of solving them.
I used AI assistance for this post, but the experiences and ideas are genuine.