Our team once inherited the front end of a Sitecore site that had been in production for ten years. Pages were slow to load on phones, scoring just 34 out of 100 on Google’s mobile performance test. The main navigation menus couldn’t be opened with a keyboard or announced by a screen reader, meaning the site wouldn't comply with accessibility standards. For internal teams, it also meant small edits became risky enough that a two-hour task could stretch into two days.
Problems like these can cost a business more than simply developer time. They also hurt user experience and search performance.
So what did we do?
We didn’t rewrite it. Instead, we refactored the presentation layer in place and brought the mobile Lighthouse score to 78 without pausing the client’s publishing work.
In this article, we’ll cover what we found, how each problem showed up for users, the order we fixed things in, and the results so far.
Key Takeaways
-
Ten years of front-end debt can leave a platform slow on mobile devices, inaccessible to keyboard and screen reader users, exposed to unpatched dependency vulnerabilities, and risky to change. Slow pages also weaken Core Web Vitals, which Google uses as a ranking signal.
-
Performance waste in legacy front ends usually comes from global bundles that ship every script and stylesheet to every page, whether that page uses them or not. On CMS platforms like Sitecore, rendering-level asset registration fixes this by loading a component’s JavaScript and CSS only when that component appears on the page.
-
When the CMS and backend are stable, refactoring the presentation layer is usually safer and faster than rebuilding. Start with linters in warning mode, high-severity dependency patches, and accessibility fixes, and leave the toolchain upgrade until bundles are smaller and regressions are easier to catch.
-
Oshyn modernizes legacy front ends by sequencing fixes by user impact so improvements can ship continuously without pausing publishing work.
What Does Outdated Front-End Tooling Cost Users?
Outdated front-end tooling forces visitors to download far more code than each page actually uses. On phones and slower connections, that leads to slower load times and screens that stop responding. Google also measures how quickly pages respond as part of its ranking factors, so the same problem can pull down search rankings.
In our case, the build ran on Gulp 3 and Node 10, which reached end of life in April 2021. Underneath Bootstrap 3 sat jQuery 1.11, vendored as a static file with no package manager tracking it.
Modern pipelines compress assets and send visitors only the code the current page needs. This one did neither.
Every page shipped 1.8 MB of JavaScript and 420 KB of CSS. Chrome DevTools Coverage showed that 78% of that code went unused on a typical visit, and the mobile Lighthouse performance score was 34.
Interaction to Next Paint (INP) was 420 ms in our field data, more than double Google's 200 ms threshold for a good user experience. For a visitor on an older phone, that meant tapping a button and waiting almost half a second before anything happened. INP is also one of Google’s Core Web Vitals, so a score that far over the threshold counts against the site in search rankings, and slow pages are harder for AI search tools to crawl and cite.
How !important Overrides and Missing Design Tokens Hurt the CSS
When a site’s styling rules pile up for years without shared standards, design changes slow down and become riskier. Developers end up forcing new styles over old ones until nobody can predict which rule will actually show up on the page.
Over the past ten years, dozens of developers had added conflicting rules to the stylesheets. There were no shared design tokens for spacing, typography, or color. So, when a new style collided with an older one, the fix was to append !important to override everything else. We found 412 of them across the site’s stylesheets.
For the browser, 420 KB of mostly unused CSS meant more parsing up front and heavier style recalculation whenever scripts changed the page.
For the team, every !important forced the next developer to reach for a more specific selector or another !important, so changing a font size meant first working out which competing rule actually applied.

Why Does Monolithic JavaScript Make Every Fix Risky?
When all of a site’s code is bundled together instead of split into separate parts, a small change in one area can break something completely unrelated.
On this site, the scripts handling business rules, data loading, and animations all lived in the same files, some of which were more than a thousand lines long. It's like one employee doing the books, working the grill, and waving the sign out front at the same time. Something eventually burns.
With no boundaries between those concerns, a change in one place could break something unrelated, and nothing in the toolchain would catch it.

The Ten-Minute Fix That Took a Full Day
A few weeks into the project, one of our engineers sat down to adjust the alignment on a simple sign-in button up in the main navigation. It looked like a ten-minute cosmetic tweak. It ended up eating an entire workday.
The original script attached a listener to the entire document and checked whether any action occurred near the account menu.
A listener is a small piece of code that waits for an action, like a click or a key press, and then runs whatever it was set up to do. To nudge the button into line, our engineer moved that wrapper element up a notch in the page layout. That tiny adjustment caused the global listener to intercept clicks across the nearby password toggle and login submission controls. The form simply swallowed every tap without producing a single error in the browser console. Nobody realized anything was broken until someone tried logging in to a test account and noticed the sign-in button was completely unresponsive.
Failures like this are why developers grow cautious around old code. A cosmetic edit to one element broke login because a script elsewhere was listening to the whole page.
Before:

After:

Top JS Files by Bloat
| Rank |
File |
Lines |
Size |
Primary Issue |
Recommended Fix |
| 1 |
forms.js |
1,195 |
37.6 KB |
Monolithic form logic bundled across all pages |
Split code per route; load only when form tags exist |
| 2 |
sliders.js |
1,122 |
32.2 KB |
Over 30 duplicated carousel initialization blocks |
Replace with a single parameterized carousel initializer |
| 3 |
utils.js |
615 |
21.4 KB |
Unscoped global helpers and state listeners |
Scope utility helpers and adopt explicit imports |
| 4 |
search.js |
387 |
15.3 KB |
Mixed duties across network calls, suggestion popups, and card rendering |
Decouple search fetching from visual presentation |
| Total |
4 files |
3,319 |
106.5 KB |
Redundant, unscoped, and globally executed script logic |
Modular architecture with tree shaking and scoped bundles |
Why Global Bundles Hurt Performance on Every Page
Loading the code for every feature on every page is a common shortcut on CMS-driven sites, and it slows down even the simplest pages. This site runs on Sitecore, where authors can place almost any component on any page. Rather than track which components a page actually rendered, the original build took the safe route. It bundled all carousel, form validator, and modal scripts into a single global payload. Every visitor downloaded, parsed, and executed that bundle before the page settled, even to read a two-sentence return policy.
The fix is rendering-level asset registration. Instead of loading all scripts up front, our view files register their specific JS and CSS bundles in the pipeline only when Sitecore actually renders that block. A simple informational page loads only basic typography and a header, while the heavy carousel script stays behind until an author drops a slider onto the layout.

How Custom Menus Broke Keyboard and Screen Reader Navigation
When menus aren’t built to accessibility standards, people who navigate with a keyboard or a screen reader can’t use them, and the site takes on compliance risk.
On this site, the menus were built with links rather than buttons. A screen reader announces a link as something that takes you to a new page, so users expected to leave that page. Instead, a menu opened that their software never announced, and the items inside couldn’t be reached with a keyboard. That fails two WCAG 2.1 criteria: 4.1.2 (Name, Role, Value), which requires controls to tell assistive technology what they are, and 2.1.1 (Keyboard), which requires everything on a page to work without a mouse.
Before:

After:

Native buttons are focusable, respond to Enter and Space, and announce their expanded state without extra scripting.
What Happens Without Linting or Automated Checks
No automated checks ran while engineers worked. Modern setups use linters such as ESLint and Stylelint to flag syntax errors, risky patterns, and problematic CSS before code leaves the editor. Here, broken code was saved and compiled without a warning.
An npm audit of the inherited dependency tree found 29 vulnerabilities, 7 rated high severity, including prototype pollution flaws in legacy libraries.
And because nobody knew which global script might break next, routine tasks regularly took several times longer than estimated.
From Symptom to Solution
| What users experience |
Root cause |
Fix |
| Scroll stutter and delayed tap responses |
Heavy main-thread JavaScript and uncoordinated DOM updates |
Scope listeners, batch DOM writes, split bundles |
| Slow first load on mobile |
Global JS and CSS bundles, unoptimized assets |
Route- or component-level splitting, unused CSS removal |
| Broken keyboard and screen reader navigation |
Links misused as buttons, no state attributes |
Native buttons with aria-expanded and aria-controls |
| Bugs from small, unrelated edits |
Document-level listeners, no linting |
Scoped listeners, ESLint and Stylelint in CI |
| Every style change takes longer than it should |
412 !important overrides, no design tokens |
Design tokens, then retire overrides file by file |
Refactor, Rebuild, or Replatform?
| Path |
Choose it when |
Tradeoff |
| Refactor |
The CMS and back end are stable; the debt sits in CSS, bundling, and isolated scripts |
Fast performance wins without disrupting publishing workflows |
| Rebuild |
The whole presentation layer depends on frameworks that block modern tooling |
Needs dedicated sprints, but clears the structural debt |
| Replatform |
The CMS or hosting architecture no longer meets business or scaling needs |
Large investment and long lead times; justified only when the foundation fails |
Once you know what’s wrong, the next decision is whether to fix the existing front end, rebuild it, or move to a new platform. Ultimately, the right choice depends on which parts of the site are still in good shape.
We chose to refactor. The CMS and back end were stable, and the debt was concentrated in the presentation layer. That allowed us to ship fixes without pausing the client's publishing work.
How We Prioritized the Fixes
-
Turn on linters in warning mode. You get a full catalog of defects without breaking a single deployment.
-
Patch high-severity dependencies. Known vulnerabilities are the one problem that carries risk every day you wait.
-
Replace links acting as buttons with native buttons. This restores keyboard and screen reader access and reduces compliance exposure right away.
-
Scope wide-reaching event listeners. Tying click handlers to the elements that own them stops the accidental regressions that make every ticket risky.
-
Introduce design tokens, then retire !important overrides. Tokens give developers a shared source of truth, so the overrides stop growing back.
-
Split monolithic bundles by route or component. Browsers download only the code that the current page renders.
-
Upgrade the toolchain last. Moving off Gulp 3 and Node 10 is safer once linting and smaller bundles are in place to catch what the migration breaks.
Results So Far
| Metric |
Before |
After |
| Lighthouse mobile performance |
34 |
78 |
| Interaction to Next Paint |
420 ms |
180 ms |
| JavaScript per page |
1.8 MB |
520 KB |
| CSS per page |
420 KB |
95 KB |
| !important declarations |
412 |
38 |
| High-severity vulnerabilities |
7 |
0 |
Where to Start If You’ve Inherited an Aging Front End
When a front end has been running for years, the instinct is often to start over. Before committing to that, a few quick checks will usually tell you more than a rebuild plan.
Don’t start with a rewrite. Turn on a linter in warning mode to catch code errors, run a coverage report to see how much code goes unused, and run npm audit to find security vulnerabilities in third-party libraries. Sort what those reports show by how much each issue affects users, and you have a roadmap.
Without a rebuild, we took this Sitecore site’s mobile performance score from 34 to 78, and the Interaction to Next Paint dropped from 420 ms to 180 ms, under Google’s 200 ms threshold. Visitors on older phones now get a noticeably faster site, and the stronger Core Web Vitals scores support both search rankings and AI visibility.
If your site has similar problems, Oshyn’s free Reliability Report scans it for performance, accessibility, discoverability, and security issues. From there, our Sitecore team can help you decide which fixes to tackle first. Reach out to talk through your platform.