<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Veselý</title>
    <description>The latest articles on DEV Community by David Veselý (@davidvesely).</description>
    <link>https://dev.to/davidvesely</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1388892%2F8add4f38-fa8b-4e5f-bf02-002fa5e8f35f.JPG</url>
      <title>DEV Community: David Veselý</title>
      <link>https://dev.to/davidvesely</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davidvesely"/>
    <language>en</language>
    <item>
      <title>Generate Images and Videos From Claude Code for Free</title>
      <dc:creator>David Veselý</dc:creator>
      <pubDate>Mon, 04 May 2026 14:09:24 +0000</pubDate>
      <link>https://dev.to/davidvesely/generate-images-and-videos-from-claude-code-for-free-pio</link>
      <guid>https://dev.to/davidvesely/generate-images-and-videos-from-claude-code-for-free-pio</guid>
      <description>&lt;p&gt;&lt;em&gt;One API key, hundreds of models, $5 a month free, enough for plenty of images.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hero images for landing pages, screenshots for app docs, short clips for social. There's always something to generate. None of it is heavy work, but every job used to mean tabbing out of Claude Code. I'd rather not.&lt;/p&gt;

&lt;p&gt;Vercel's AI Gateway already solves the model side. One API key gets you a long catalog of models with no token markup. The missing piece was a CLI for terminal-driven workflows. Vercel shipped that recently as &lt;a href="https://github.com/vercel-labs/ai-cli" rel="noopener noreferrer"&gt;&lt;code&gt;ai-cli&lt;/code&gt;&lt;/a&gt;, with a Claude Code skill alongside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ai&lt;/code&gt; is a thin CLI around the Vercel AI Gateway endpoints. Three commands cover most of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ai text &lt;span class="s2"&gt;"explain quicksort in three bullets"&lt;/span&gt;
ai image &lt;span class="s2"&gt;"a minimalist mountain logo"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; cover.png
ai video &lt;span class="s2"&gt;"a wave crashing on rocks at sunset"&lt;/span&gt; &lt;span class="nt"&gt;--duration&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Text streams to stdout. Images and videos save to disk; videos take a few minutes per job.&lt;/p&gt;

&lt;p&gt;The companion Claude Code skill is the part that matters more day-to-day:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/vercel-labs/ai-cli &lt;span class="nt"&gt;--skill&lt;/span&gt; ai-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that's installed, Claude reaches for the CLI automatically when I ask for an image or a clip in plain English. I don't have to remember the command syntax or the model id. Claude picks the model and tells me where the file landed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup, end to end
&lt;/h2&gt;

&lt;p&gt;Five minutes from zero to a working &lt;code&gt;ai&lt;/code&gt; command.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a Vercel account
&lt;/h3&gt;

&lt;p&gt;If you don't have one already, sign up at &lt;a href="https://vercel.com/signup" rel="noopener noreferrer"&gt;vercel.com/signup&lt;/a&gt;. The free Hobby plan is enough. AI Gateway works on every tier and the $5 monthly credit applies to free accounts too.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Get an AI Gateway API key
&lt;/h3&gt;

&lt;p&gt;Open the &lt;a href="https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys&amp;amp;title=AI+Gateway+API+Keys" rel="noopener noreferrer"&gt;AI Gateway API Keys page&lt;/a&gt; in your Vercel dashboard. Click &lt;strong&gt;Create Key&lt;/strong&gt;, give it a name (e.g. &lt;code&gt;ai-cli&lt;/code&gt;), and copy the value that starts with &lt;code&gt;vck_&lt;/code&gt;. You only see the full key once, so paste it somewhere safe before closing the modal.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Install the CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; ai-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pnpm add -g ai-cli&lt;/code&gt; and &lt;code&gt;bun add -g ai-cli&lt;/code&gt; work too. Source: &lt;a href="https://github.com/vercel-labs/ai-cli" rel="noopener noreferrer"&gt;github.com/vercel-labs/ai-cli&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Export your key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AI_GATEWAY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vck_your_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add it to &lt;code&gt;~/.zshrc&lt;/code&gt; or &lt;code&gt;~/.bashrc&lt;/code&gt; so it survives a new shell. The CLI is env-var-only by design, no config file and no login flow.&lt;/p&gt;

&lt;p&gt;If you're already inside a Vercel-linked project, you can skip the static key entirely — &lt;code&gt;npx vercel link&lt;/code&gt; followed by &lt;code&gt;vc env pull&lt;/code&gt; fetches a short-lived OIDC token into &lt;code&gt;.env.local&lt;/code&gt;, and the CLI picks it up automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Add the Claude Code skill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/vercel-labs/ai-cli &lt;span class="nt"&gt;--skill&lt;/span&gt; ai-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the piece that lets Claude Code reach for &lt;code&gt;ai&lt;/code&gt; automatically when you ask for an image or video in plain English. Without it the CLI still works. You just have to call it directly instead of describing what you want.&lt;/p&gt;

&lt;p&gt;That's it. Open Claude Code and try one of the prompts below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real prompts I run
&lt;/h2&gt;

&lt;p&gt;Drop these straight into Claude Code. The skill routes them to &lt;code&gt;ai&lt;/code&gt; under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Hero image for a landing page
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /ai-cli to generate a wide hero image for a developer-tools landing page — an abstract topographic map of overlapping data layers, deep navy with thin teal contour lines, photorealistic editorial style. Save as &lt;code&gt;public/hero.png&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GPT-Image-2 is the default and handles this in seconds. Drop it into &lt;code&gt;public/&lt;/code&gt;, reference it from your hero section, ship.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Logo or poster with text inside the image
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /ai-cli with &lt;code&gt;openai/gpt-image-2&lt;/code&gt; to generate a minimalist logo for "Envelope.so" — serif wordmark on parchment, terracotta accent dot. Save as &lt;code&gt;logo.png&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GPT-Image-2 is the one I reach for when the image has to render text correctly. Imagen and Flux still mangle longer words.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Four OG card backgrounds for a blog post
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /ai-cli to generate four open-graph background variations (1200x630) for a blog post titled "We migrated 100M rows in an afternoon" — bold editorial composition, dark background, terracotta accent shapes, leave the top-left clear for a title overlay. Save them in &lt;code&gt;og/&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The CLI auto-suffixes the filenames so you get &lt;code&gt;og-1.png&lt;/code&gt; through &lt;code&gt;og-4.png&lt;/code&gt;. I overlay the post title in code at build time, so the model only has to generate a clean background. Cheap iteration loop when nothing in the first batch is right.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Sharpest quality, specific model
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /ai-cli with &lt;code&gt;bfl/flux-2-pro&lt;/code&gt; to generate a cover image for a Medium article on AI tooling — a stylized 3D rendering of stacked translucent terminal panes floating in space, soft volumetric light, editorial composition. Save as &lt;code&gt;output/cover.png&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the image is going on Medium and I want it to actually look professional, Flux-2-Pro is worth the extra cents.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Same prompt, three models in one command
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /ai-cli with &lt;code&gt;-m "openai/gpt-image-2,bfl/flux-2-pro,xai/grok-imagine-image"&lt;/code&gt; to generate the same section banner: an abstract editorial pattern of overlapping circles in deep navy and terracotta, soft volumetric light, no text. Save them in &lt;code&gt;compare/&lt;/code&gt; and open all three so I can pick.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Comma-separated &lt;code&gt;-m&lt;/code&gt; is built in. The CLI runs all three in parallel and the slowest one is the bottleneck. A few cents to A/B three aesthetics is the easiest way to decide which model deserves a longer batch — useful when I'm about to render twenty assets in the same style.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Pipeline: image straight into video
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /ai-cli to generate a still of a stylized 3D dashboard mockup — flat dark UI with brand-colored chart shapes, then pipe it into &lt;code&gt;ai video&lt;/code&gt; to add a slow camera drift across the surface, 4 seconds. Save the video as &lt;code&gt;marketing/dashboard-pan.mp4&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;ai image "..." | ai video "..."&lt;/code&gt; is a native pattern. The image streams as raw binary and the video command picks it up as a reference frame. Cleaner than saving an intermediate file and re-loading it, and exactly the move I reach for when I need a quick marketing clip from a static asset.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Short social-media video
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /ai-cli to generate a 5-second vertical clip (9:16, 720p) for a product launch teaser — minimalist liquid-metal animation morphing into a logo silhouette, dark background, clean editorial style. Save as &lt;code&gt;social/launch-teaser.mp4&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This one needs the $10 top-up. Default Seedance 2.0 handles motion well and can render audio; switch to &lt;code&gt;bytedance/seedance-2.0-fast&lt;/code&gt; for the cheaper variant or &lt;code&gt;google/veo-3.1-fast-generate-001&lt;/code&gt; for higher fidelity. See the pricing section below for the catch.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Browse what's available
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /ai-cli to list every image model with its current rate, sorted by price.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The skill knows to call &lt;code&gt;ai models --type image&lt;/code&gt;. Useful before a batch job to confirm the catalog hasn't changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model lineup
&lt;/h2&gt;

&lt;p&gt;Defaults are picked for price-to-quality, not for headlines. Text goes to &lt;code&gt;openai/gpt-5.5&lt;/code&gt;. Image goes to &lt;code&gt;openai/gpt-image-2&lt;/code&gt;. Video goes to &lt;code&gt;bytedance/seedance-2.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Override with &lt;code&gt;-m&lt;/code&gt; when the job calls for it. I reach for &lt;code&gt;bfl/flux-2-pro&lt;/code&gt; when the image needs to be sharper than gpt-image-2 handles, and &lt;code&gt;google/imagen-4.0-fast-generate-001&lt;/code&gt; (Imagen 4 Fast) for cheap iteration. For video, Seedance 2.0 already produces clean motion and supports audio; &lt;code&gt;google/veo-3.1-fast-generate-001&lt;/code&gt; is the premium option when the budget allows.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ai models --type image&lt;/code&gt; lists every image model with its current rate. Same for &lt;code&gt;--type video&lt;/code&gt; and &lt;code&gt;--type text&lt;/code&gt;. Pricing comes straight from Vercel.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "free" actually means
&lt;/h2&gt;

&lt;p&gt;Per the &lt;a href="https://vercel.com/docs/ai-gateway/pricing" rel="noopener noreferrer"&gt;AI Gateway pricing docs&lt;/a&gt;, every Vercel team account, Hobby plans included, gets $5 of AI Gateway credits per month. A gpt-image-2 render costs a few cents. A &lt;code&gt;flux-2-pro&lt;/code&gt; run is more. You can do real work inside that $5 envelope before the meter starts ticking.&lt;/p&gt;

&lt;p&gt;Video is the catch. Vercel requires a $10 minimum top-up to unlock video generation, and per the pricing docs, &lt;strong&gt;once you purchase any credits your account transitions to the paid tier and the monthly $5 free credit no longer applies.&lt;/strong&gt; So topping up for Veo or Seedance also costs you the free image budget. For an image-only workflow on the free tier the line is simple. Want video, and the math flips.&lt;/p&gt;

&lt;p&gt;For me that's fine. Most days I'm in image-only territory and the bill is zero. When I need a clip I pay for it like any other API.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;ai models --type image&lt;/code&gt; (or &lt;code&gt;--type video&lt;/code&gt;) for live per-token rates straight from Vercel; pricing changes often enough that I don't quote specific cents in writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this beats tab-switching
&lt;/h2&gt;

&lt;p&gt;The image lands in the repo, ready to commit. No web-app round trip, no download-and-rename dance.&lt;/p&gt;

&lt;p&gt;One billing surface is the other underrated win. Want to try Veo? Normally you'd spin up a Google Cloud project with billing enabled. Want Flux? BFL account, separate card. Want gpt-image-2? OpenAI billing. Vercel sits in front of all of them, so the credit card lives in exactly one place. Monthly spend is in one dashboard, the budget cap is in one dashboard, and revoking the key kills every model at once.&lt;/p&gt;

&lt;p&gt;And when a new model ships, Vercel adds it to the catalog and the CLI picks it up.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I also wrote about &lt;a href="https://medium.com/@davidvesely/cd7419dd855d" rel="noopener noreferrer"&gt;running any agent skill once without installing it&lt;/a&gt;. Same one-shot-tooling instinct, different layer.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>cli</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How to Use Cursor's Composer 2, Gemini, Grok &amp; More in Claude Code as Another Dev</title>
      <dc:creator>David Veselý</dc:creator>
      <pubDate>Tue, 28 Apr 2026 19:19:16 +0000</pubDate>
      <link>https://dev.to/davidvesely/how-to-use-cursors-composer-2-gemini-grok-more-in-claude-code-as-another-dev-4cd1</link>
      <guid>https://dev.to/davidvesely/how-to-use-cursors-composer-2-gemini-grok-more-in-claude-code-as-another-dev-4cd1</guid>
      <description>&lt;p&gt;&lt;em&gt;Why I stopped trusting Claude to review Claude. 8 prompts I run instead.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Install Cursor's CLI, then &lt;code&gt;npx skills@latest add Vesely/skills/cursor-agent&lt;/code&gt;. In Claude Code, ask things like &lt;em&gt;"review this branch via cursor-agent with composer, gemini, and gpt-5.5 in parallel"&lt;/em&gt;. Three models look at the same problem at once. Claude merges what they find. Works with any model in &lt;code&gt;cursor-agent --list-models&lt;/code&gt;, not just those three.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Asking Claude to review the code Claude just wrote is like asking someone to grade their own homework. They will find what they were already looking for. Multi-model review loops are harder to fool because the sycophancy bias does not survive crossing providers.&lt;/p&gt;

&lt;p&gt;So I started fanning work out to other models from inside Claude Code. The skill wraps Cursor's headless &lt;code&gt;cursor-agent&lt;/code&gt; CLI. Claude Code starts the jobs in parallel, waits for the answers, then merges the useful parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;You need &lt;a href="https://cursor.com/cli" rel="noopener noreferrer"&gt;Cursor's CLI&lt;/a&gt; installed and a Cursor account. Install the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://cursor.com/install &lt;span class="nt"&gt;-fsS&lt;/span&gt; | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;agent&lt;/code&gt; once to log in. Then add the skill in Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills@latest add Vesely/skills/cursor-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skill defaults to read-only mode and never writes to your repo. A fetched answer can look but not touch.&lt;/p&gt;

&lt;h2&gt;
  
  
  8 patterns I actually use
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Code review before merge
&lt;/h3&gt;

&lt;p&gt;Anything I'm about to push, especially branches Claude wrote most of.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /cursor-agent to review the recent changes with composer-2, gemini-3.1-pro, and gpt-5.5 in parallel. Real issues only, &lt;code&gt;file:line&lt;/code&gt; references, no nitpicks. Merge the findings: what all three flagged, where they disagreed, what only one saw.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. "Did Claude actually do what it said?"
&lt;/h3&gt;

&lt;p&gt;When the summary feels too smooth.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ask /cursor-agent with grok to compare Claude's summary against the recent diff and test output. Flag anything in the summary the diff or tests don't support.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Architecture and implementation plan review
&lt;/h3&gt;

&lt;p&gt;Before letting Claude write a single line on something non-trivial.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ask /cursor-agent with gpt-5.5 in plan mode to critique the plan in this doc. Where am I over-engineering? What risks am I underweighting? Be blunt.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Security review across models
&lt;/h3&gt;

&lt;p&gt;Auth, user input, file paths, anything touching shell. Each model spots different attack surfaces.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /cursor-agent to security-review the recent changes with composer-2, gpt-5.5, and gemini-3.1-pro in parallel. What's the worst input a user could send? Where am I missing defensive checks? Real risks only, no theater. Merge what each model caught and where they disagreed.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. Copy and text review
&lt;/h3&gt;

&lt;p&gt;Anything user-facing or public — landing pages, error messages, docs, emails.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ask /cursor-agent with gpt-5.5 to read the draft in this file. Where does it sound stiff or generic? Which sentences sound polished but say nothing? Suggest sharper variants for the weakest few.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  6. "Which variant should I pick?"
&lt;/h3&gt;

&lt;p&gt;Two or three options on the table and I'm fence-sitting.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ask /cursor-agent with gpt-5.5 to rank the three options in this doc. Which would you pick and why?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  7. "Are you really happy with that?"
&lt;/h3&gt;

&lt;p&gt;Claude says it's done and something feels slightly off. A workflow that almost reads right. Code that compiles but smells.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ask /cursor-agent with composer-2 to look at the recent output cold. I think it's wrong but I can't say why. What would you change?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  8. Stuck on an issue, need different ideas
&lt;/h3&gt;

&lt;p&gt;Thirty minutes of debugging and Claude and I are circling the same hypotheses. Kimi gives an off-axis read from outside the usual bubble.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use /cursor-agent with gemini-3.1-pro and kimi-k2.5 in parallel. Look at the error and what I've already tried in this thread. What are the top three things I might be missing? Don't repeat what I've ruled out.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  My go-to second models
&lt;/h2&gt;

&lt;p&gt;A few months in, my defaults have settled. GPT-5.5 became my go-to for both code review and copy, sharp feedback without padding. Composer 2 is the fast one, called whenever I'm impatient. Codex stays out on purpose; for GPT-5.x reviews I run the standalone &lt;code&gt;codex&lt;/code&gt; CLI directly.&lt;/p&gt;

&lt;p&gt;What's your favorite second opinion? Which model do you reach for on code review, and which on copy? Drop it in the comments. Curious where mine breaks down for someone else.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>vibecoding</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Fixing YouTube Error 153 in iOS Capacitor Apps: A Simple Proxy Solution</title>
      <dc:creator>David Veselý</dc:creator>
      <pubDate>Tue, 11 Nov 2025 09:51:13 +0000</pubDate>
      <link>https://dev.to/davidvesely/fixing-youtube-error-153-in-ios-capacitor-apps-a-simple-proxy-solution-607</link>
      <guid>https://dev.to/davidvesely/fixing-youtube-error-153-in-ios-capacitor-apps-a-simple-proxy-solution-607</guid>
      <description>&lt;p&gt;If you're building a mobile app with Capacitor and embedding YouTube videos, you might encounter the dreaded &lt;strong&gt;"Error 153: Video player configuration error"&lt;/strong&gt; when running your app on iOS. This error occurs specifically in iOS WebView environments and can be frustrating because the same code works perfectly fine in web browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03jxefq8xp6csuhoogcp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03jxefq8xp6csuhoogcp.webp" alt=" " width="690" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;YouTube videos embedded via &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; tags work flawlessly in Safari and other browsers, but fail with Error 153 when loaded inside an iOS WKWebView (which is what Capacitor uses). The root cause? &lt;strong&gt;Missing or incorrect HTTP Referer headers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;YouTube's security requirements expect proper referrer information to validate embed requests. iOS WKWebView doesn't automatically send these headers the same way regular browsers do, causing YouTube to reject the video loading request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common "Solutions" That Don't Work
&lt;/h2&gt;

&lt;p&gt;Before finding the right solution, you might try:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Adding &lt;code&gt;referrerpolicy&lt;/code&gt; attribute&lt;/strong&gt; - Helps in browsers, but not sufficient for iOS WebView&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adding meta referrer tags&lt;/strong&gt; - Partially effective, but unreliable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using &lt;code&gt;origin&lt;/code&gt; parameter&lt;/strong&gt; - Helps in some cases, but not consistent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modifying WKWebView configuration&lt;/strong&gt; - Requires native code changes and still unreliable&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Reliable Solution: HTML Proxy
&lt;/h2&gt;

&lt;p&gt;The most robust solution is to create a &lt;strong&gt;simple HTML proxy file&lt;/strong&gt; that properly handles the referrer headers and embeds the YouTube video correctly. This approach works consistently across all iOS versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Proxy HTML File
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;youtube.html&lt;/code&gt; in your public/static folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0, viewport-fit=cover"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"referrer"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"strict-origin-when-cross-origin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;YouTube Video&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;#player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"player"&lt;/span&gt;
    &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;
    &lt;span class="na"&gt;allow=&lt;/span&gt;&lt;span class="s"&gt;"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"&lt;/span&gt;
    &lt;span class="na"&gt;referrerpolicy=&lt;/span&gt;&lt;span class="s"&gt;"strict-origin-when-cross-origin"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Parse URL parameters&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Get video ID (required)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;div style="color: white; padding: 20px; text-align: center;"&amp;gt;Error: Missing video ID parameter (?v=VIDEO_ID)&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Get optional parameters with defaults&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;autoplay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;autoplay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playlist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;controls&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modestbranding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modestbranding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playsinline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playsinline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Build YouTube embed URL parameters&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;embedParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;modestbranding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modestbranding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;playsinline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;playsinline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;enablejsapi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// Add loop and playlist if loop is enabled&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;embedParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;embedParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playlist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Add mute if enabled&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mute&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;embedParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Construct final URL&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;embedUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://www.youtube-nocookie.com/embed/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;embedParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Set iframe src&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;player&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;embedUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})();&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Update Your Embed URLs
&lt;/h3&gt;

&lt;p&gt;Change your YouTube embed URLs from direct YouTube links to your proxy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://www.youtube-nocookie.com/embed/VIDEO_ID?autoplay=1&amp;amp;loop=1&amp;amp;mute=1"&lt;/span&gt;
  &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://yourdomain.com/youtube.html?v=VIDEO_ID&amp;amp;autoplay=1&amp;amp;loop=1&amp;amp;mute=1"&lt;/span&gt;
  &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Vue/React components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://www.youtube-nocookie.com/embed/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?autoplay=1`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://yourdomain.com/youtube.html?v=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;autoplay=1`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Deploy and Test
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deploy the proxy file&lt;/strong&gt; to your production server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test in a browser&lt;/strong&gt; to ensure videos load correctly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebuild your Capacitor app&lt;/strong&gt; and test on iOS device&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify&lt;/strong&gt; that Error 153 is resolved&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;The proxy solution works because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Proper Domain Context&lt;/strong&gt;: The HTML file is served from your domain, establishing a valid origin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct Referrer Policy&lt;/strong&gt;: The meta tag and iframe attribute set the right referrer policy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YouTube-Compatible Headers&lt;/strong&gt;: The proxy constructs the embed URL with all necessary parameters including the &lt;code&gt;origin&lt;/code&gt; parameter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WKWebView Compatibility&lt;/strong&gt;: The setup works within iOS WebView's security constraints&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Additional Benefits
&lt;/h2&gt;

&lt;p&gt;Beyond fixing Error 153, this approach offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: Uses &lt;code&gt;youtube-nocookie.com&lt;/code&gt; domain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: Same behavior across all platforms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: Easy to add custom parameters or modify behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability&lt;/strong&gt;: Single file to update for all YouTube embeds&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While there are various workarounds for YouTube Error 153 in iOS apps, the HTML proxy solution is the most reliable and maintainable. It requires minimal setup, works consistently across iOS versions, and doesn't require native code modifications.&lt;/p&gt;

&lt;p&gt;If you're building a Capacitor app with YouTube embeds, save yourself the debugging time and implement this solution from the start.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you encountered YouTube Error 153 in your iOS app? What solution worked for you? Share your experience in the comments below!&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://capacitorjs.com/" rel="noopener noreferrer"&gt;Capacitor Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/youtube/iframe_api_reference" rel="noopener noreferrer"&gt;YouTube iFrame API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/webkit/wkwebview" rel="noopener noreferrer"&gt;iOS WKWebView Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>youtube</category>
      <category>html</category>
      <category>capacitor</category>
    </item>
  </channel>
</rss>
