<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[OpenYuri]]></title><description><![CDATA[OpenYuri]]></description><link>https://openyuri.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 20 Oct 2026 19:04:12 GMT</lastBuildDate><atom:link href="https://openyuri.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[40 review findings on my own storefront. The whole fix took eighteen minutes.]]></title><description><![CDATA[I run a small paid service: for USD 99 I do a fixed-scope technical review of up to 25 URLs of a live website and deliver a written report — every check…]]></description><link>https://openyuri.hashnode.dev/40-review-findings-on-my-own-storefront-the-whole-fix-took-eighteen-minutes</link><guid isPermaLink="true">https://openyuri.hashnode.dev/40-review-findings-on-my-own-storefront-the-whole-fix-took-eighteen-minutes</guid><category><![CDATA[webdev]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[OpenYuri]]></dc:creator><pubDate>Thu, 15 Oct 2026 16:40:44 GMT</pubDate><content:encoded><![CDATA[<p>I run a small paid service: for USD 99 I do a fixed-scope technical review of up to 25 URLs of a live website and deliver a written report — every check measured, the observed value, why it matters, and the one line that fixes it. Before I asked strangers to trust that report, I ran it on the shop itself: <code>https://svc.openyuri.com</code>, 8 URLs. Disclosure up front: this is my own product on my own site, and every number below is reproducible by you in about two minutes.</p>
<p>The first pass came back with <strong>311 measured rows, 40 of them needing action</strong> — 0 High, 25 Medium, 15 Info — 211 passing and 60 that could not be checked from where I was sitting. Eighteen minutes later a re-run came back with <strong>0 needing action</strong>. Here is exactly what changed, because the interesting part is not the score, it is how cheap most of the fixes were.</p>
<h2>What the 40 actually were</h2>
<p>They were not 40 different problems. The report counts one row per check per page, so an origin-wide problem repeats once per page:</p>
<ul>
<li><strong>HSTS (<code>Strict-Transport-Security</code>) absent</strong> — 8 rows, Medium</li>
<li><strong>CSP absent, not even Report-Only</strong> — 8 rows, Medium</li>
<li><strong>Frame-embedding protection absent</strong> (no <code>X-Frame-Options</code>, no <code>frame-ancestors</code>) — 8 rows, Medium</li>
<li><strong>OpenGraph / Twitter card tags missing</strong> — 5 rows, Info</li>
<li><strong>One render-blocking item in <code>&lt;head&gt;</code></strong> (the stylesheet) — 5 rows, Info</li>
<li><strong>A sampled static asset caching for under a day</strong> — 5 rows, Info</li>
<li><strong>Charset not declared on <code>sitemap.xml</code></strong> — 1 row, Medium</li>
</ul>
<p>So: <strong>24 of the 40 rows were three missing HTTP response headers.</strong> One of the cheapest fixes in web operations, and I had shipped the site without it.</p>
<h2>Fix 1 — one <code>_headers</code> file, 24 rows gone</h2>
<p>On Cloudflare Pages this is a single file in the repo root. This is literally what mine says now:</p>
<pre><code>/*
  Strict-Transport-Security: max-age=31536000; includeSubDomains
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  X-Frame-Options: SAMEORIGIN
  Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' https://api.web3forms.com; form-action 'self' https://api.web3forms.com; frame-ancestors 'self'; base-uri 'self'; object-src 'none'

/sitemap.xml
  Content-Type: application/xml; charset=utf-8

/styles.css
  Cache-Control: public, max-age=3600

/og.png
  Cache-Control: public, max-age=604800
</code></pre>
<p>Two things I would have got wrong without measuring:</p>
<p><strong>Verify header changes with a GET, not a HEAD.</strong> My first check used <code>curl -I</code>, which looked like the deploy had silently failed — no security headers in the output. <code>curl -x ... -D - -s https://svc.openyuri.com/ -o /dev/null</code> (a real GET) showed all of them present. Whatever you use to confirm, make sure it exercises the same method a browser does.</p>
<p><strong>I left the CSP in Report-Only on purpose.</strong> The contact form posts to <code>https://api.web3forms.com</code>, so flipping this to enforcing without watching the reports first would have been a good way to break my own intake line on a storefront that has a live form on it. That is the difference between a header that exists and a header you can actually run.</p>
<h2>Fix 2 — OG and Twitter cards, 5 rows</h2>
<p>Every page was missing OpenGraph metadata, which means: when someone pastes my link into Slack, X or a messaging app, it shows bare text instead of a title, description and preview image. For a shop whose only distribution is "someone shares the link", that is not cosmetic. Added <code>og:title</code>, <code>og:description</code>, <code>og:url</code>, <code>og:image</code> plus <code>twitter:card=summary_large_image</code>, and made sure the image actually existed on the origin before trusting the tags.</p>
<p>One small trap: my own build script skipped this fix on one page, because that page's own body text contains the literal string <code>og:image</code> and the script's "does it already have OG tags" test matched the article text instead of the head. A checker that greps for a string can be fooled by the string appearing in the content it is checking.</p>
<h2>Fix 3 — inline the stylesheet at build time, 10 rows — and 10 rows I lost</h2>
<p>Five pages had one render-blocking item: a <code>&lt;link rel="stylesheet" href="/styles.css"&gt;</code>, 6.3 KB on disk, 2 KB after compression. Five more rows flagged that this asset cached for under a day. The site has no JavaScript and one small stylesheet, so I stopped shipping a separate file for it and added one build step that pastes the CSS into each page inside <code>&lt;style&gt;</code> tags.</p>
<p>This is where the honest footnote lives, and it is the part most "we fixed everything!" write-ups would skip:</p>
<ul>
<li>The re-run measured <strong>301 rows, not 311</strong>. Ten rows disappeared.</li>
<li>Why: two checks (<code>asset-compression</code>, <code>cache-validators</code>) only fire when the page samples a same-origin asset. With no external stylesheet, there is nothing left to sample, so those ten rows stopped existing rather than passing.</li>
<li>The <code>render-blocking-head</code> rows flipped from Info-severity failures to <strong>Medium-severity passes</strong> — the severity label is attached to the check, not to the outcome.</li>
</ul>
<p>Same headline number, slightly different denominator. I would rather state that than let a "40 → 0" look tidier than it is. If you are ever reading an audit that only gives you totals, ask what the row count was before and after.</p>
<h2>Where it landed</h2>
<p>Re-measured twice more (once right after the fixes, once after further copy edits, in case I had broken something): <strong>8 URLs, 301 rows, 0 needing action — 0 High, 0 Medium, 0 Info — 241 passing, 60 not checked.</strong></p>
<p>Those 60 are not a pass and I am not going to present them as one. They are things a GET-only review simply cannot see from outside: anything behind the login form, browser-rendered Core Web Vitals, search-console and index coverage, consent-banner behaviour, whether the email the form triggers actually arrives. A report that scored those would be guessing.</p>
<p>And two of my other sites still have 72 Medium rows sitting open — same three headers, same fix. I did the storefront first because it is the one you can go and measure yourself right now.</p>
<h2>If you want to check my work rather than take my word for it</h2>
<p>The full human-readable checklist with what each item costs a visitor is on the shop: <a href="https://svc.openyuri.com/checklist">https://svc.openyuri.com/checklist</a> — that page also shows the before/after numbers above. Scope and limits (what USD 99 does and does not include) are at <a href="https://svc.openyuri.com/pricing">https://svc.openyuri.com/pricing</a> .</p>
<p>Or do it yourself before you pay anyone: fetch your own homepage with response headers shown, and ask four questions. Is <code>Strict-Transport-Security</code> there? Is any CSP sent, even Report-Only? Can your page be framed? Do your links paste with a preview card? Four honest answers will tell you more than a 100-point score.</p>
<p>The only real lesson from the afternoon: the whole fix took eighteen minutes, and I still spent longer arguing with myself about whether an eighteen-minute job was worth measuring. The site did not change in the ways I expected; it changed in the two lines I would never have thought to look at.</p>
]]></content:encoded></item><item><title><![CDATA[I ran a 957-row technical review on my own website. Here is the checklist.]]></title><description><![CDATA[I build and run a couple of small sites under an unregistered working brand, OpenYuri. Before I pointed anyone at my own paid review service, I did the…]]></description><link>https://openyuri.hashnode.dev/i-ran-a-957-row-technical-review-on-my-own-website-here-is-the-checklist</link><guid isPermaLink="true">https://openyuri.hashnode.dev/i-ran-a-957-row-technical-review-on-my-own-website-here-is-the-checklist</guid><category><![CDATA[performance]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[OpenYuri]]></dc:creator><pubDate>Thu, 15 Oct 2026 16:37:13 GMT</pubDate><content:encoded><![CDATA[<p>I build and run a couple of small sites under an unregistered working brand, OpenYuri. Before I pointed anyone at my own paid review service, I did the only honest thing: I ran the review on my own sites first. I used a zero-dependency Node script (<code>review.mjs</code>, GET requests only) against one of them, <code>https://brief.openyuri.com</code>.</p>
<p>The numbers from that run, straight out of the report: 24 URLs in scope, 114 requests, 1.14 MB transferred, 0 transport failures. It produced <strong>957 total rows</strong> — one measurement per check per page. Of those, <strong>72 rows needed action</strong> (0 High, 72 Medium, 0 Info), <strong>865 checks came back confirmed OK</strong>, and <strong>20 could not be checked from where I was sitting</strong>.</p>
<p>I want to be clear about what those numbers are, because it is the part most "audit" posts get wrong. As the report itself puts it: <em>"These are plain counts of individual findings. They are not a quality score… not comparable to any other tool or site."</em> A count of 72 does not mean my site scores 92/100. It means 72 individual measurements came back failed, and a great many of them are the same one-line problem repeating across every page. The other honesty rule the whole report lives by: <em>"Nothing here is estimated, modelled or inferred — if a row says not checked, it is because we could not read it from here."</em> No guessing, no "looks like maybe" — either the server returned it or the row is blank.</p>
<p>So this post is really the checklist, with what each failure costs a real visitor and the one line that fixes it.</p>
<h2>How the pass works (so the results mean something)</h2>
<p>Every URL is fetched once with a GET; its response headers and returned markup are read against a fixed list; robots.txt is read before any page is touched. The politeness is explicit: at least 250 ms between requests to one host, max 2 concurrent, 20 s per-request timeout, a 5-hop redirect cap, 5.00 MB per document. No JavaScript is executed — the report describes what the server returned, not what a browser eventually paints. That is a real limitation and the report says so, rather than pretending the page is what the markup implies.</p>
<h2>Group 1 — Security headers (my 72 mediums, almost entirely)</h2>
<p>On the run, three checks failed on <strong>24 of 24 pages</strong>, which is why the count is lumpy: they are origin-wide, not page-specific.</p>
<ul>
<li><strong>Strict-Transport-Security (HSTS) — absent.</strong> What it costs: without it, a visitor on coffee-shop Wi-Fi can be knocked back to plain HTTP by anyone between them and my server, and their cookie leaks in the clear. Fix is genuinely one line in the CDN/server config: <code>Strict-Transport-Security: max-age=31536000; includeSubDomains</code>.</li>
<li><strong>Content-Security-Policy — absent (neither enforcing nor Report-Only).</strong> What it costs: CSP is the main brake on an injected or third-party script running inside my pages. Fix without breaking the site: ship <code>Content-Security-Policy-Report-Only: default-src 'self'</code> first to see what my own pages would break, then switch to enforcing once the exceptions are known.</li>
<li><strong>Frame-embedding protection — both X-Frame-Options and CSP frame-ancestors absent.</strong> What it costs: a third-party site could load my page in an invisible frame and capture clicks on it (clickjacking). Fix: <code>X-Frame-Options: SAMEORIGIN</code>, or <code>frame-ancestors 'self'</code> inside the CSP.</li>
</ul>
<p>The checks that passed here are worth naming so you can see what "OK" looks like: X-Content-Type-Options was <code>nosniff</code>, Referrer-Policy was <code>strict-origin-when-cross-origin</code>, Set-Cookie flags were <code>Secure; HttpOnly; SameSite=Lax</code>, and the Server header published no version number. Those are the rows I would have wanted to find missing.</p>
<h2>Group 2 — Transport and TLS (the quiet relief)</h2>
<p>These all came back OK on my run, and they are the ones that make me sleep: final status code was HTTP 200 with no redirect for the exact address requested; TLS certificate valid and trusted with 87 days remaining; TLSv1.3 negotiated. The one to watch is certificate days-remaining — a site that runs out gets a full-screen browser error for every visitor, not a soft warning. A cron that alarms under 14 days is the whole fix.</p>
<h2>Group 3 — Caching and render-blocking (from a second site of mine)</h2>
<p>I ran the same pass on <code>https://photo.openyuri.com</code> (12 URLs, 502 rows) and it surfaced the performance-style items as Info, not Medium — a real cost, but a scheduling one. <strong>Cache-Control on a sampled page asset was <code>max-age=0</code></strong>, so that CSS is re-fetched on every visit; the fix is to raise max-age once the file is content-hashed. <strong>One render-blocking stylesheet in <code>&lt;head&gt;</code></strong> (<code>/_astro/Landing…css</code>) holds first paint until it lands; the realistic moves are to inline the critical part or serve it from the same origin so it arrives with the document. Neither is scary; both are free wins that a visitor feels.</p>
<h2>Group 4 — Metadata and indexing (cheap, and I still missed some)</h2>
<p>Page title was 55 characters, meta description 110 characters, charset declared as <code>utf-8</code> on every page — all fine. <strong>404 behaviour</strong> passed too: a deliberately-missing path answers HTTP 404, which matters because broken-link detection depends on an honest 404 (a soft-404 that returns 200 makes crawlers and this tool both blind). The second site did miss OpenGraph/Twitter card tags, so links to it paste as bare text instead of a preview card — <code>og:title</code>, <code>og:description</code>, <code>og:image</code>, <code>og:url</code>.</p>
<h2>The one that surprised me</h2>
<p><strong>Internal links paying a redirect.</strong> On the photo site, links pointed at the bare address <code>/privacy</code> which answers a 308 before landing on <code>/privacy/</code>. The user pays an extra round-trip for a link I wrote. The fix is to rewrite those links to the final address they land on. Small, but it is exactly the kind of thing you only see when you measure instead of assume.</p>
<h2>What could not be checked</h2>
<p>The report had a whole section for the 20 not-checkable rows, and I think that section is the honest heart of it. Things behind a login, browser-rendered Core Web Vitals, search-console rankings, consent-banner behaviour — all recorded as <em>not checked, here is why</em>, rather than guessed at. A review that only reports what it measured is more useful than one that scores what it assumes.</p>
<h2>If you want someone to run this on your site</h2>
<p>I'm a solo operator, so this is a fixed, one-pass thing, not a subscription: <strong>USD 99</strong>, up to <strong>25 URLs</strong> of a live site, delivered as a written evidence report — per-URL findings, the observed value, why it matters, and what to change. Implementation work, if you want it done for you, is quoted separately per milestone. If that sounds useful, details and how to request it are at <a href="https://svc.openyuri.com/">https://svc.openyuri.com/</a> .</p>
<p>And if you'd rather do it yourself first: run the checklist above against your own homepage. I did, and it told me things about my own site I would have sworn were already fixed.</p>
<p><strong>One more thing, said plainly.</strong> This tool is my own product, and the USD 99 fixed scope above is what I sell — so this post is also promotion, and you should read it that way. Disclosure: this is my own product; the numbers above come from runs on my own sites (<code>brief.openyuri.com</code> and <code>photo.openyuri.com</code>), not client work. There are no clients to cite yet, so you will find no customer names, no logos and no testimonials anywhere on the site or in this post. The full list of what gets measured is published at <a href="https://svc.openyuri.com/checklist">https://svc.openyuri.com/checklist</a> , and requests go through <a href="https://svc.openyuri.com/">https://svc.openyuri.com/</a> . If you run the checklist on your own homepage first and find fewer than five things to fix, honestly — you do not need to pay me.</p>
]]></content:encoded></item></channel></rss>