Research: Why does polskiekulinaria.pl produce imageurl = null?
Date: 2026-07-05
Researcher: Claude Sonnet 4.6
Git Commit: b54454e
Branch: uix-changes
Repository: spokospace/zapiszprzepis
Research Question
Tiramisu recipe from polskiekulinaria.pl was saved with image_url = null. Why does this happen and what needs to change?
Summary
The image pipeline has a single point of failure with no fallback: image comes exclusively from scraped.metadata?.ogImage returned by Firecrawl. If Firecrawl doesn’t populate that field for a given site, image_url stays null forever — no fallback to <img> tags, schema.org, or LLM extraction. polskiekulinaria.pl has no domain-specific handling and almost certainly doesn’t return a usable ogImage. The fix is to add a fallback image source.
Detailed Findings
Full pipeline: URL → image_url in DB
Entry point: src/app/(authenticated)/recipes/add-recipe-action.ts
- Validates URL, checks auth/dedup
- Inserts
recipe_sharesrow withstatus: 'pending' - Fires Inngest event
recipe/extract(async) and redirects
Inngest function (src/inngest/functions.ts):
Step A — Scrape
isBlogspotUrl()→falsefor polskiekulinaria.pl → takes the Firecrawl pathscrapeWithRetry()callshttps://api.firecrawl.dev/v1/scrapewithformats: ['markdown', 'html']andonlyMainContent: true; retries withfullContent: trueif markdown < 200 chars- Line ~117:
ogImage = scraped.metadata?.ogImage— the one and only image source
Step B — LLM extraction
gpt-4o-miniparses markdown into structured JSON (title,ingredients,steps,category, timing fields)- The LLM prompt does not ask for an image URL — image is never extracted by AI
Step C — DB write
image_url: ogImage ?? nullinserted intorecipesrow (~line 254)archiveImage()downloads the external URL, validates MIME + size, uploads to Supabase Storagerecipe-images/{userId}/{recipeId}.{ext}, overwritesimage_urlwith Storage public URL- If
ogImagewasnull→archiveImage()is never called →image_urlstaysnull
Image archive logic
src/lib/recipe-image-archive.ts
archiveImage() skips archiving and returns null when:
- HTTP response is non-200
Content-Typeis notimage/jpeg,image/png, orimage/webp(e.g.avif,gif,webp;charset=...)- Image > 5 MB
- Fetch times out (15 s)
- Supabase upload fails
This means: if Firecrawl returns a valid ogImage URL but archiveImage() rejects it, image_url still holds the raw external URL (not null). A completely null value in DB is therefore a reliable indicator that Firecrawl returned no ogImage at all.
Blogspot exception (only domain-specific logic)
src/inngest/functions.ts lines 83–94: if isBlogspotUrl() is true, uses Blogger JSON feed (fetchBloggerPost() from src/lib/blogger-feed.ts) which directly provides a post.image field. This bypasses Firecrawl for blogspot URLs. polskiekulinaria.pl does not benefit from this.
No domain-specific handling for polskiekulinaria.pl
Full-text search across the codebase returned zero matches for polskiekulinaria. The site goes through the generic web_blog Firecrawl path with no fallbacks.
Code References
src/inngest/functions.ts:117—ogImage = scraped.metadata?.ogImage(single image source)src/inngest/functions.ts:254—image_url: ogImage ?? null(DB insert)src/inngest/functions.ts:277–282—archiveImage()call after insertsrc/inngest/functions.ts:83–94— blogspot special-case (model for domain-specific paths)src/lib/firecrawl.ts:30–62—buildFirecrawlOptions()forweb_blogsrc/lib/recipe-image-archive.ts:12–60—archiveImage()with MIME/size guardssrc/lib/blogger-feed.ts— Blogger JSON feed integration (pattern to follow)
Architecture Insights
The pipeline is: single external source → archive. There is no retry or fallback layer between Firecrawl’s metadata and the DB write. Adding a fallback requires intercepting at line 117 and providing an alternative source when ogImage is undefined.
Candidate fallbacks in order of reliability:
- schema.org
RecipeJSON-LD — standard structured data on recipe sites; Firecrawl returnshtml, so the JSON-LD<script>block can be parsed forimagefield. Most modern recipe sites (including polskiekulinaria.pl likely) include this. - First
<img>from main content HTML — Firecrawl already returnshtmlin the same scrape response; pick the largest/first<img src>from the parsed HTML. Noisier but widely applicable. - OpenAI vision on screenshot — most expensive; not justified when HTML is available.
Option 1 (schema.org JSON-LD) is the highest-signal fallback: it’s semantically correct, doesn’t require additional Firecrawl calls, and the html field is already in the response.
Open Questions
- Does Firecrawl’s
metadatainclude any other image-adjacent fields besidesogImage(e.g.twitterImage)? Worth logging the fullmetadataobject for a polskiekulinaria.pl URL to confirm. - Is
avifa realistic Content-Type issue for polskiekulinaria.pl images? If the site serves.avif,archiveImage()would reject it — but sinceimage_urlisnull(not an external URL), this is secondary. - Should the schema.org fallback be applied only when
ogImageis absent, or always (treating schema.org as higher quality thanog:image)?