Improving development speed by renewing the build environment
Summary
| Perspective | Content |
|---|---|
| Issue | In a large-scale monorepo based on Webpack, slow HMR, heavy initial/CI builds, and unstable caching had become chronic. As a result, review queues formed easily, and it took a long time to reflect and verify changes. |
| Action | Gradually switched to Rspack, which is almost compatible with Webpack. Reorganized settings such as transforms, source maps, and code splitting, and redesigned the configuration so that caching works stably. Migrated with zero downtime while checking compatibility via automated tests and E2E. |
| Operation | Ran dual builds with Webpack / Rspack on each PR and automatically checked differences (especially bundle size). Visualized metrics such as build time and cache efficiency, and maintained a state where continuous improvement is possible. |
| Result / Outcome | Clearly improved perceived HMR speed and build time. (Example: incremental build -81%, initial build -67%). Reviews became less likely to stall, and the development cycle became lighter and more stable. Was able to gain benefits step by step without breaking existing mechanisms such as SSR / Storybook / Sentry. |
Background and issues
Product context
- Large-scale frontend based on Webpack 5 (monorepo, multiple apps / shared UI library)
- TypeScript + React. CSS uses both existing global CSS / Sass and Tailwind (
className/cx()based) - The build has many stages—TS transform → CSS → minification → source map—and surrounding tools (Sentry, etc.) were also optimized on the assumption of Webpack
Problems that surfaced
- HMR and initial builds were slow, reducing the iteration speed of UI changes
- CI builds were putting pressure on lead time from review to merge, pushing releases back
- Strong dependence on new joiners’ machines and hardware performance, increasing the burden on developer experience and onboarding
- As a result, release frequency and MTTR were negatively affected, and operational costs increased
Root causes of latency
- High transform cost due to Webpack’s single-threaded processing and multi-layered loaders
- Cache granularity was coarse, causing wide-ranging rebuilds even for small changes
- Cross-monorepo dependencies made it easy for “small changes → large rebuilds” to occur
Limits of existing improvements
- persistent cache / thread-loader / swc-loader, etc. had already been introduced, and local optimizations had been applied
- A full replacement with Vite / esbuild, etc. was high risk in terms of plugin compatibility and the ability to migrate step by step
Goals of improvement (non-functional requirements)
- Shorten the change → reflect cycle and increase the iteration speed of UI changes
- Eliminate waste in build processing and maintain a development environment that is consistently fast and stable both locally and in CI
- Build a configuration that can effectively leverage caching and localized builds so that small diffs do not trigger large rebuilds
- Have a migration procedure that can be introduced gradually and rolled back immediately if necessary
Approach
Basic policy
- Switched the build foundation from Webpack to Rspack in one shot.
- Did not adopt parallel operation; by unifying the build baseline, kept artifact consistency, CI settings, and cache design simple.
Why a one-shot switch was possible
- Automated tests and E2E tests were sufficiently in place, allowing mechanical verification of behavioral differences after the switch
- For major paths such as Storybook, SSR, i18n extraction, and monitoring integration, reproducible regression checks could be performed
- Prior to the switch, we inventoried dependencies in the existing build configuration and plugin usage locations, clarifying the impact scope of compatibility gaps
Why we chose a one-shot switch (and not parallel operation)
- Coexistence of Webpack and Rspack would increase operational load due to artifact difference verification, CI cache management, and duplicated dependencies
- In a monorepo environment, it tends to be more stable to unify on a single baseline rather than run in parallel
- We prioritized “improving without stopping,” choosing a form that clearly defines the switchover point and localizes the impact
Steps
- Reproduced the Rspack configuration to the point where functional parity with the Webpack configuration was achieved
- Performed diff verification on CI for Dev / Prod / E2E / Storybook
- Switched in one shot → immediately ran the full suite of automated tests + E2E to confirm safety
- Where issues appeared, applied localized fixes at the loader / plugin level
Results (Before / After comparison)
Measurement conditions
- Same machine, same branch, same commit (dependencies locked)
- Measurement targets: Dev (HMR/incremental), Prod (initial/incremental/source map), CI (full pipeline)
- Each metric is shown as an average value (P95 in parentheses)
List of metrics
| Metric | Before: Webpack | After: Rspack | Improvement | Notes |
|---|---|---|---|---|
| Incremental build (Dev) | 2.1s | 0.4s | -81% | 1 TS file changed |
| Initial build (Prod) | 60s | 20s | -67% | Minify+SourceMap |
Notes
- “Improvement” is unified as either reduction rate (%) or reduction time (seconds)
- For items with large variance, the worst case is noted in a footnote
- If there are compatibility differences (e.g., plugins not yet replaced), they are noted in the remarks
Qualitative effects (changes in developer experience and operations)
-
Increased number of UI change iterations
It became possible to make layout adjustments and fine interaction improvements without hesitation,
and the “think while running it” style returned. -
Less waiting around reviews, and conversations became more constructive
As the time spent “waiting for the build to finish” decreased, PRs started to be opened smaller and more frequently,
and discussions began to focus on specifications and design. -
Reduced onboarding burden for new joiners
The heaviness of the initial local build was resolved, lowering the psychological barrier to starting development. -
Shorter lead time for urgent responses (bug fixes)
The loop from fix → verification → deploy became lighter, leading to a practical improvement in MTTR. -
CI delays stopped being “something to worry about”
Within the team, the atmosphere shifted from “watching and waiting for CI” to “proceeding on the assumption that CI will pass.” -
Developer experience shifted from “exhausting” to “flow that doesn’t get interrupted”
Continuity of thought is maintained, making it easier to stay focused on the problem at hand.
Conclusion
This initiative did not aim to “make builds faster” as an end in itself, but rather focused on not interrupting the flow of development.
The reason we were able to choose a one-shot switch in a situation where optimization headroom for Webpack had hit a ceiling was that the foundation for regression checks via automated tests and E2E was already in place.
As a result, build processing became lighter and waiting time decreased.
However, the more essential change is that day-to-day decisions around UI changes, reviews, and verification can now be made “without hesitation.”
- Developers are not subjected to unnecessary interruptions while thinking.
- Small improvements are not hesitated over.
- Initial response in bug handling is quick.
This “good flow” directly connects to product quality, speed of change, and the team’s ability to continue improving over time.
Through this improvement, we were able to regain the “continuity” needed to move the product forward.
Related blog
Performance Optimization
Enhanced system response speed and stability through database and delivery route optimization.
Developer Productivity & Quality Automation
Maintained continuous development velocity through quality assurance automation and build pipeline improvements.
Enhanced User Experience
Improved usability and reliability from the user's perspective, including search experiences and reservation systems.