Service Portal pages start fast and get slow over time. Every new widget, every additional script include, every extra REST call adds milliseconds. Without a budget, the portal team always says yes, and a year later the homepage takes seven seconds to render. A simple budget changes the conversation.
Set the budget in real numbers
Pick targets and write them down: time-to-first-byte under 400ms, largest contentful paint under 2 seconds, total page weight under 1.5MB, JavaScript execution under 800ms on a mid-tier laptop. These are not aspirational. Pages that miss them get rejected in code review until they fit. The exact numbers matter less than having them.
Measure before every release
Use the Chrome Lighthouse CLI in your release pipeline against a sub-prod portal page. Failing the budget blocks the release set from promoting. This is more reliable than asking developers to “test performance” because it is automated and unambiguous. The budget either fits or it does not.
Widget script work is the usual culprit
Widget server scripts run on every page load. A script that fires three GlideRecord queries to compute a small block of data adds roughly 150ms to every render. Cache the result in gs.cacheVariableSet if the data is the same for all users in a session, or in data only if the data is per-user and per-page. Re-querying for every render is the most common bloat pattern in old portals.
Lazy-load below the fold
Widgets below the visible area should not block initial render. Use ng-if controlled by an intersection observer or by a deferred $timeout to hold off on rendering until the user scrolls. The home page header should never wait for the news feed widget at the bottom to finish querying.
Bundle and minify custom UI scripts
Custom JavaScript loaded via UI Scripts or theme assets often ships unminified, ungzipped, and uncombined. A page with 12 small JS files takes longer than a page with one combined file of equivalent size due to HTTP overhead. Bundle aggressively and gzip-compress at the CDN if available.
Avoid client-side waterfalls
A widget that calls $http.get to load data, then calls another $http.get based on the first response, then renders, takes three serial round trips. Restructure as a single server script that returns the joined data shape the widget needs. Each saved round trip is roughly 80ms of perceived latency.
Trim the theme
Custom themes accumulate dead CSS over time. Run a CSS coverage report in Chrome DevTools against the most-used pages. Anything in the unused-bytes column over 30 percent is worth a cleanup pass. Smaller CSS means faster paint and faster style recalculation.
Track real user metrics, not just synthetic
Lighthouse runs on a controlled environment. Real users have slow networks and old laptops. Add a small performance.now() script that posts portal page load timing to a u_portal_perf table. Look at the 95th percentile, not the median; the median lies, the 95th percentile tells the truth.
What to do this week: pick three budget numbers, measure your top 10 portal pages against them, and identify the worst offender to fix first.