<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Bora Ocaker — Notes from the workbench</title>
    <link>https://boraocaker.com/en/blog</link>
    <atom:link href="https://boraocaker.com/rss.xml" rel="self" type="application/rss+xml" />
    <description>Short technical notes about React, performance, GSAP and the messy reality of shipping web work.</description>
    <language>en-US</language>
    <lastBuildDate>Tue, 02 Jun 2026 15:10:32 GMT</lastBuildDate>
    <generator>Hand-rolled at boraocaker.com</generator>
    <item>
      <title>Generating OG images at build time with Satori</title>
      <link>https://boraocaker.com/en/blog/og-images-with-satori</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/og-images-with-satori</guid>
      <pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate>
      <description>Every post on this site gets its own 1200×630 social card, rendered from JSX to PNG at build time with Satori and resvg. No headless Chrome, no screenshot service, no runtime cost.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/og-images-with-satori.png" type="image/png" />
      <category>og</category>
      <category>satori</category>
      <category>build</category>
      <category>seo</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>Prerendering a Vite SPA for SEO without going SSR</title>
      <link>https://boraocaker.com/en/blog/prerendering-a-vite-spa</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/prerendering-a-vite-spa</guid>
      <pubDate>Sun, 31 May 2026 00:00:00 GMT</pubDate>
      <description>My site is a client-rendered React SPA. To a crawler that&apos;s a near-empty body. Here&apos;s the 200-line post-build script that writes real HTML per route — without adopting Next or a server.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/prerendering-a-vite-spa.png" type="image/png" />
      <category>seo</category>
      <category>vite</category>
      <category>prerender</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>A markdown blog with no CMS — just import.meta.glob</title>
      <link>https://boraocaker.com/en/blog/markdown-blog-with-vite-glob</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/markdown-blog-with-vite-glob</guid>
      <pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate>
      <description>This blog has no database, no headless CMS, no content API. Posts are markdown files in a folder, pulled into the bundle at build time by one Vite feature. Here&apos;s the whole setup.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/markdown-blog-with-vite-glob.png" type="image/png" />
      <category>vite</category>
      <category>markdown</category>
      <category>react</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>The 700 KB I never shipped — trimming a syntax highlighter</title>
      <link>https://boraocaker.com/en/blog/trimming-syntax-highlighter</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/trimming-syntax-highlighter</guid>
      <pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate>
      <description>react-syntax-highlighter will happily bundle every Prism grammar it ships with. That&apos;s hundreds of kilobytes of languages I&apos;ll never use. Here&apos;s how I registered only the handful this blog needs.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/trimming-syntax-highlighter.png" type="image/png" />
      <category>performance</category>
      <category>bundle</category>
      <category>react</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>Hand-rolling RSS and JSON feeds at build time</title>
      <link>https://boraocaker.com/en/blog/hand-rolling-rss-and-json-feeds</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/hand-rolling-rss-and-json-feeds</guid>
      <pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate>
      <description>RSS isn&apos;t dead, it&apos;s just quiet. Adding a feed to this blog took one build script, no library, and about an hour of reading two specs. Here&apos;s what I learned writing RSS 2.0 and JSON Feed by hand.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/hand-rolling-rss-and-json-feeds.png" type="image/png" />
      <category>rss</category>
      <category>build</category>
      <category>seo</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>Adding i18n to a React site without breaking the router</title>
      <link>https://boraocaker.com/en/blog/react-i18n-without-routing</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/react-i18n-without-routing</guid>
      <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
      <description>How I added Turkish to my portfolio in an afternoon — react-i18next, a fixed top-left toggle, and zero new dependencies on the router.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/react-i18n-without-routing.png" type="image/png" />
      <category>react</category>
      <category>i18n</category>
      <category>vite</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>One attribute that cut my hero LCP in half</title>
      <link>https://boraocaker.com/en/blog/hero-lcp-fetchpriority</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/hero-lcp-fetchpriority</guid>
      <pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate>
      <description>A background-image div is invisible to the preload scanner. An &lt;img&gt; with fetchpriority=&quot;high&quot; isn&apos;t. Here&apos;s what changed when I made the swap.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/hero-lcp-fetchpriority.png" type="image/png" />
      <category>performance</category>
      <category>lighthouse</category>
      <category>react</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>The 80 KB I removed from my critical path with one observer</title>
      <link>https://boraocaker.com/en/blog/lazy-third-party-scripts</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/lazy-third-party-scripts</guid>
      <pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate>
      <description>hCaptcha and Web3Forms only matter when someone actually submits the contact form. Here&apos;s how I delayed both until the form was on screen — and what that did to my Lighthouse score.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/lazy-third-party-scripts.png" type="image/png" />
      <category>performance</category>
      <category>react</category>
      <category>lighthouse</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>Syncing Lenis smooth scroll with GSAP ScrollTrigger</title>
      <link>https://boraocaker.com/en/blog/lenis-gsap-scrolltrigger</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/lenis-gsap-scrolltrigger</guid>
      <pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate>
      <description>Lenis hijacks the scroll position. ScrollTrigger reads the scroll position. If you don&apos;t tell them about each other, your scrubbed animations drift exactly enough to feel broken.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/lenis-gsap-scrolltrigger.png" type="image/png" />
      <category>gsap</category>
      <category>lenis</category>
      <category>scroll</category>
      <category>react</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>When WebGL is wrong — and a CSS gradient is right</title>
      <link>https://boraocaker.com/en/blog/webgl-vs-css-mobile</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/webgl-vs-css-mobile</guid>
      <pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate>
      <description>My projects page used an animated WebGL ASCII grid as a background. It looked great. On a 3-year-old Android, it ate the battery and dropped frames. Here&apos;s what I replaced it with on mobile, and why I&apos;m not feeling guilty about it.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/webgl-vs-css-mobile.png" type="image/png" />
      <category>performance</category>
      <category>webgl</category>
      <category>css</category>
      <category>mobile</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
    <item>
      <title>Things I built and deleted in the same week</title>
      <link>https://boraocaker.com/en/blog/things-i-built-and-deleted</link>
      <guid isPermaLink="true">https://boraocaker.com/en/blog/things-i-built-and-deleted</guid>
      <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
      <description>A short list of features I shipped to a branch, lived with for a day or two, and then quietly removed. Each one taught me something about what this portfolio is actually for.</description>
      <enclosure url="https://boraocaker.com/og/blog/en/things-i-built-and-deleted.png" type="image/png" />
      <category>design</category>
      <category>restraint</category>
      <category>decisions</category>
      <author>boraocaker38@gmail.com (Bora Ocaker)</author>
    </item>
  </channel>
</rss>
