Client work, performance and security
Speeding up two client sites
PageSpeed scores of 70 and 69 on phones. The causes: a page that stayed invisible until JavaScript loaded, a CSS compiler running in the browser, and a CSP that silently blocked the analytics and the ads.
Client work. Two production sites I built and maintain: Формат7, an online print shop (Next.js and Python), and Печати7, a stamp and seal order site (Flask). October 2026.
Why I looked
Both sites worked, took orders and never threw errors at customers. Then I ran them through Google PageSpeed Insights on a phone profile and got 70 and 69 for performance, with the main content of the page appearing after 7.6 and 6.1 seconds. For a shop that people open from a link in a messenger, that is the first impression.

I went through the reports item by item, reproduced each problem locally with Lighthouse, and found that most of the slowness came from a few decisions that looked harmless when they were made. Two of the findings were not about speed at all: on both sites the security policy I had written was silently breaking a third-party script.
Формат7: the whole site was invisible until JavaScript loaded
The Lighthouse trace said the largest element on the page had loaded in a fraction of a second but was painted about 5 seconds later. The image was there; the browser just was not allowed to show it.
The cause was a page transition wrapper in the root layout:
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
>
{children}
</motion.div>
Next.js renders this on the server with opacity: 0, so every page of the site arrived in the browser fully transparent and stayed that way until React had downloaded, hydrated and started the animation. On a slow phone connection that is seconds of a white screen. Worse, the animation never played where it was meant to: the root layout is not remounted when you move between pages, so the wrapper only ever ran once, on the first load, exactly where it hurt. I removed it, along with the entrance animation on the first screen.
Формат7: photos stored as PNG
Six product photos were PNG files of 0.3 to 1 MB each, 4.4 MB together. They are photographs with no transparency, so PNG was simply the wrong format. The catch was that their paths, like /products/photo.png, are stored in the database and used by the admin panel, so renaming the files would mean a data migration on a live shop.
Instead I converted them to WebP (343 KB for all six) and added a rewrite in the Next.js config, so a request for the old .png address is answered with the WebP file. Nothing in the database changed. I also recompressed the JPEG photos and stopped the phone version from downloading two large images that are only shown on desktop, by wrapping them in a <picture> element with a desktop-only source.
Формат7: the analytics counted nothing
While reading the browser console I noticed the Content Security Policy blocking mc.yandex.ru. Yandex Metrica, the analytics the shop relies on, was being loaded on every page and immediately refused by the site's own CSP. The site owner was looking at visitor statistics that the site had never been able to send.
The fix was one line in the policy, allowing the Metrica domains for scripts, images, connections and frames. It has a visible cost: Lighthouse's best practices score dropped from 92 to 75, because Metrica sets third-party cookies. That is a trade-off the business wants, so I kept it and wrote down why.
Печати7: a CSS compiler running in every visitor's browser
Печати7 loaded Tailwind CSS from cdn.tailwindcss.com. That is the Play CDN, a roughly 400 KB script that scans the page and generates the CSS inside the visitor's browser. Tailwind's own documentation says it is for prototyping, not production. It blocks rendering, and it was the biggest item in the render-blocking requests that PageSpeed estimated at 3.3 seconds of savings.
I compiled the styles ahead of time with the Tailwind CLI, using the same configuration that was inlined in the page. The result is a 29 KB CSS file served by the site itself. The Inter font and the Font Awesome icons were also loaded from Google and cdnjs; I moved them onto the site, made the icon stylesheet load without blocking the first paint, and removed four external origins from the CSP. The hero illustration, a 373 KB PNG shown at 320 pixels, became a 61 KB WebP.
Печати7: an ad block that never showed an ad
The template contained a Yandex Advertising Network block. Its loader script was not allowed by the CSP, so the browser refused it on every page view, and the only trace was an error in the console. The block had never displayed anything. I removed it; if ads are wanted, it is a one-line policy change to turn them on properly.

Accessibility
Both sites lost points for things that real users notice:
- Contrast. White text on the brand orange
#F97316has a contrast ratio of 2.8:1; the minimum for normal text is 4.5:1. I used a deeper orange,#C2410C(5.2:1), for the two places with white text on orange, and darkened the light grey headline words and small captions. - Buttons without names. The mobile menu button and the icon-only login link had no text a screen reader could announce.
- Heading order. Footer and feature headings skipped levels, so I fixed the tags without changing how they look.
- A false alarm worth fixing anyway. On Печати7, Lighthouse reported a white heading on a near-white background. In reality the heading sits on blue, but two blurred decorative circles were separate elements behind it and confused the contrast check. I redrew them as CSS gradients, which look the same and leave nothing for the checker to misread.
Results
Measured with Lighthouse 12 on the mobile profile, before (PageSpeed Insights on the live sites) and after (local run of the fixed code with the same settings):
| Performance | Accessibility | Best practices | SEO | |
|---|---|---|---|---|
| Формат7, before | 70 | 88 | 92 | 100 |
| Формат7, after | 92 | 100 | 75 | 100 |
| Печати7, before | 69 | 80 | 92 | 100 |
| Печати7, after | 82 | 100 | 100 | 100 |
On desktop both sites now score 99 for performance. The local Печати7 number is held back by my test server, which does not compress responses; in production the site sits behind Caddy with gzip and zstd.
What I took from it
- A security header can quietly break a feature. On both sites the CSP did its job and blocked a script nobody had allowed, and nobody noticed because nothing visibly failed. Now I check the console after every policy change, not only the page.
- Read the trace, not just the score. "Largest Contentful Paint 7.6 s" sounds like a slow image. The trace showed the image arriving almost at once and then waiting for permission to appear.
- Fix it where it can be fixed safely. Renaming files would have been the clean solution for the PNG photos, but on a live shop with paths in the database a rewrite rule got the same result with no migration.