April 21, 2026
Fixed: Duplicate canonicals removed on language homepages
Every page was emitting its canonical URL twice — once as an HTML <link rel="canonical"> tag, and once as an HTTP Link: <…>; rel="canonical" response header. On regular converter pages the two URLs happened to match, but on the language-root homepages /de/ /fr/ /es/ /pt/ /nl/ /it/ the HTTP header stripped the trailing slash (because basemodule::get_canonical_url() rtrims it) while the HTML tag kept it — so Ahrefs and other crawlers saw two different canonicals for the same URL (https://cleverutils.com/de vs https://cleverutils.com/de/). Removed the redundant HTTP header entirely; every template already emits the HTML canonical via blog.php or its own head file, and Google recommends a single canonical signal per page.
April 17, 2026
Fixed: SEO shield: noindex mixed-language converter URLs whose body is still English
Audit found that 217 of 238 converter/tool pages under src/data/pages/<slug>/<lang>.php were auto-generated by deep-merging the English master into a translation stub — meta and H1 were localised but the body was ≥90% byte-identical to English. On /es/png-to-svg, /nl/gif-to-png, /de/jpg-converter and similar URLs, Google saw a Spanish <title> with an English body: a classic mixed-language signal that depresses rankings across the whole language cluster.
Introduced src/data/i18n-body-translated.php as an explicit allowlist: 21 converter slugs verified as properly translated (e.g. gif-to-mp4, compress-pdf, pdf-to-docx, merge-pdf, heic-to-jpg) stay indexable under all 6 non-English URLs; every other converter slug emits <meta name="robots" content="noindex,follow"> on /es/ /de/ /fr/ /pt/ /nl/ /it/ and is removed from the hreflang cluster and the language-switcher dropdown. Articles are allowed by default (240 of 243 audited as properly translated) with a small block-list for the 3 untranslated ones under /png-to-jpg/. The shield lifts per-slug as translations land — remove the slug from the allowlist file and redeploy.
Fixed: Comprehensive multi-language audit — FR/ES/PT/NL/IT now match /de/ quality
After yesterday's /de/ deep-fix, audited the other five languages exhaustively. Found and fixed:
- Template engine guard extended to
post/andconverter/folders (wassimple/only). Previously/de/random-addressserved the English template under a German URL, classic duplicate-content trap. Now returns a proper 404 when no translation exists. - API “Reference” button in the api-snippet widget linked to
/api/referencewithout a locale prefix on every converter page. Now wrapped inclv_i18n_link(). - Changelog pagination links
/changelog?page=Nmissed the prefix on non-English locales; now prefixed when a translation exists. - /de/guides title was split mid-compound-word (“Konvertierungs — Anleitungen”) — rejoined to “Konvertierungs-Anleitungen”.
Final audit across all 6 locales × 13 key pages: 0 body-link leaks, 0 double-prefix links, 0 broken canonicals. Multi-language link health is now at parity with the German audit: 374 unique URLs each in /fr/, /es/, /pt/, /nl/, /it/ — all return 200.
Improved: Accessibility: skip-to-content link, lang-aware 404 page, aria-labels on icon buttons
Added a “Skip to main content” link as the first focusable element on every page — invisible until a keyboard user tabs to it, then springs to the top-left. Standard a11y best practice that lets screen-reader and keyboard users bypass the 200-link navbar. Also tagged <html lang="de"> etc. on the 404 page so screen readers announce the right language even when we haven't translated the 404 copy, and added aria-label to the API block Copy button (previously icon-only with no screen-reader text).
Improved: JSON-LD graph cleanup: WebSite + Organization now linked by @id
The homepage was emitting two Organization schema blocks — one from index.php with an abbreviated shape, and a richer one from footer.php (with foundingDate, knowsAbout, areaServed, availableLanguage). Removed the duplicate; the WebSite schema now references the footer's Organization via @id instead of inlining a thin copy, producing a valid single-entity schema graph on every page.
Fixed: Double-prefixed links removed (/de/de/... → /de/...)
282 converter and article body templates kept a legacy manual-prefix line like $_rhref = $_langPrefix ? "/{$_langPrefix}" . $_rel['href'] : $_rel['href'];. That code was correct before we added PageContent::rewriteHrefs(), but afterwards the href got prefixed twice — once at load time by the rewriter, once more at render by the inline logic — producing broken URLs like /de/de/gif-to-mp4 in the “Related converters” badge rows. Removed 428 instances of the duplicate prefix logic across the template tree.
Fixed: Navbar + footer unit-converter links no longer 404 on non-English pages
Navbar and footer hardcoded a prefix variable on every link including slugs that aren't translated (unit converters like /km-to-miles, /random-generators, /spinner, /state-abbreviations). On /de/ those rendered as /de/km-to-miles → 404 because German translations don't exist. Switched those specific links to clv_i18n_link() which only prefixes when a translation exists, so they now serve as plain English URLs on any page. Audit across 380 unique /de/* links discovered on hub + converter pages: all 380 return 200.
Fixed: 29 broken German + Dutch meta titles rewritten
Yesterday's bulk title-trim script split titles on the em-dash and the regular hyphen — which is fine for English but shreds German compound words like “Schwarz-Weiß” and “Audio-Konverter”. Result: 20 German and 9 Dutch titles ended up as word salad (e.g. “KI — Foto kolorieren — Weiß — CleverUtils.com”). Rewrote all 29 by hand with proper native-language titles under 60 characters; fixed the script to split on em-dash only for future runs.
Improved: 250 non-English meta titles + 64 descriptions trimmed to fit SERP
Same trim pass we did for the 74 English titles, now applied to French, German, Spanish, Portuguese, Dutch, and Italian content arrays. 250 titles (was 66–95 chars, now 30–60) and 64 descriptions (was 171–210, now 148–160) shortened across 291 files so the whole title + description visibly fits in Google's SERP rows in every language.
Fixed: Non-English pages now link to the right-language versions (i18n link leakage)
Before: on /de/ the navbar prefixed links correctly, but the homepage directory, footer “Company” column, hub pages (/pdf-converter, /audio-converter, /image-converter, etc.), and FAQ answers all linked back to English URLs. Five separate categories of hardcoded English href="/slug" were leaking across 200+ pages — Google saw German pages pointing at English pages for the same topic, weakening /de/ ranking signals and confusing users.
Fix: added a global clv_i18n_link() helper that prefixes a slug with /{lang}/ only when a translation exists (so unit converters like /celsius-to-fahrenheit stay English, since they aren't localized). Applied across 100+ templates (homepage directory, 5 hub pages, 90 converter body templates, footer, api-snippet widget, changelog). Content arrays in src/data/pages/*/*.php get rewritten on load by PageContent::rewriteHrefs(). Result: 0 English link leaks across all 7 languages × the 23 pages we audited (2,872 correctly-prefixed links per language).
Fixed: Canonical tags now point at the current locale instead of English
38 converter/tool head templates (/pdf-converter, /audio-converter, /about, /api, etc.) plus the homepage had <link rel="canonical"> hardcoded to the English URL. On /de/pdf-converter the canonical was https://cleverutils.com/pdf-converter instead of .../de/pdf-converter — classic multilingual SEO bug that tells Google the German page is a duplicate of the English one, causing it to de-index the localized version.
Removed the hardcoded canonicals; the shared blog.php layout now auto-injects a language-aware canonical based on REQUEST_URI. The homepage builds its canonical inline (it doesn't route through blog.php). Homepage also gets a localized og:image, og:url, and twitter:image per language.
Improved: 74 meta titles + 49 meta descriptions trimmed to fit Google's SERP display limit
Titles over ~60 characters and descriptions over ~160 characters get truncated mid-word in Google search results — the brand name gets cut off or the description ends in “...”. Audited all 481 content-array English titles + descriptions: 74 titles were 66–83 chars and 49 descriptions were 171–201 chars. Auto-trimmed them to 30–60 / 148–160 respectively, keeping the primary keyword + brand suffix; per-language titles in /de/, /fr/, etc. are untouched (same fix for those languages is a separate pass).
Improved: Self-hosted web fonts: no more Google Fonts round-trip, -200 ms before first paint
Inter (UI) and JetBrains Mono (code) are now served from /fonts/ on cleverutils.com instead of fonts.googleapis.com + fonts.gstatic.com. A full page load no longer needs four DNS lookups and a CORS round-trip to Google before the browser can render body text — the primary Inter subset (48 KB) is preloaded in the HTML head and served from the same origin with a 30-day cache header.
Uses variable font files (one WOFF2 per subset serves all weights 400-700), with unicode-range splitting Latin (48 KB, always loaded) from Latin-Extended (84 KB, only fetched when the page has French/German diacritics). English pages now pull ~50 KB of font data instead of ~130 KB through Google. Also removes Google's IP-logging side effect, relevant for GDPR compliance in EU markets.
Improved: Faster converter pages: converter widget refactored into a cacheable external bundle (-77 KB per page)
The converter widget JavaScript was previously inlined on every tool page — the same ~79 KB was repeated in every HTML response with no browser cache benefit. It is now a static, minified external file at /js/converter-widget.min.js (46 KB, 42% smaller after terser compression). Only a tiny ~800-byte i18n config stays inline per page. The file is served with Cache-Control: public, max-age=2592000, so a user's first converter page fetches the bundle once and every subsequent tool/converter page reuses it from cache.
Also added 30-day browser + Cloudflare edge cache for all static assets (CSS, JS, fonts, images). Favicons, theme CSS, and icon fonts now serve straight from Cloudflare's global edge on repeat visits. Combined impact on a typical converter page: HTML payload shrinks from ~275 KB to ~197 KB, and repeat visits skip ~60 KB of asset downloads entirely.
Improved: Richer Google results: Article schema, homepage SearchAction, and contact info now emitted as JSON-LD
Upgraded the site's structured data so Google can surface richer snippets in SERP and populate the Knowledge Panel for the brand:
- Article schema on every guide now includes
image(OG preview) andauthor(Organization), both required for the article rich result card. - Organization schema on the homepage gained
logo,contactPoint(support email + available languages), so Google can verify the brand entity. - WebSite schema gained
potentialAction: SearchAction— eligible for the sitelinks search box that appears under the brand result. - FAQ JSON-LD now preserves inline formatting (lists,
<strong>) in answer text — astrip_tags()call was flattening every answer to plain text before. All structured data blocks were also switched from hand-built string concatenation tojson_encodewith properJSON_HEX_*escaping, eliminating a latent bug where HTML entities leaked into JSON string literals.
Improved: Localized social previews: OG images in 7 languages
The dynamic OG image generator (og.php) now reads a language prefix from the URL and renders the tagline, keywords, and subtitle in the page's language. A page served from /fr/gif-to-mp4 gets an OG image with “Convertisseur en ligne gratuit” / “Rapide · Gratuit · Sans inscription”; German serves “Kostenloser Online-Konverter” / “Schnell · Kostenlos · Keine Anmeldung”; similarly for ES/PT/NL/IT. Cache keys include the language so each variant is stored separately; existing English OG filenames are unchanged.
Other SEO polish: HSTS header (max-age=1y; includeSubDomains; preload) now served on every response, Google Fonts preloaded to shave ~200ms off first paint, and resize-image / mp3-to-wav / heic-to-jpg meta titles + descriptions tightened to stay under Google's SERP truncation limits.
April 13, 2026
Fixed: MCP endpoint no longer rejects back-to-back JSON-RPC calls
The /mcp endpoint was false-positive rate-limiting compliant MCP clients. The MCP protocol pipelines JSON-RPC calls (initialize → tools/list → tools/call) within a single logical conversation, but the 1-second per-IP cooldown shared with the REST API was denying every second call in these pairs with HTTP 429. Observed in the wild from multiple MCP registry health-checkers pinging initialize + tools/list ~80ms apart. The cooldown is now skipped for /mcp; the burst (20/min), hourly (150/h), and daily (1000/day) tiers continue to guard against abuse.
April 12, 2026
New: Full multi-language support: 6 languages, 1428 translated pages
CleverUtils.com is now available in 6 languages: English, French, German, Spanish, Portuguese, and Dutch. Every page — all 238 converters, tools, and utility pages — has localized <title>, meta descriptions, <h1>, and OG/Twitter social tags in each language. The navbar, footer, and homepage are fully translated with no hardcoded English. Language switcher dropdown appears on every page. hreflang cross-links for SEO are auto-generated.
Architecture: shared templates + per-language content arrays (src/data/pages/<slug>/<lang>.php). Adding a new language or translating a new page requires only one content file — no markup duplication.
Improved: i18n infrastructure: Phase 2 Lang::_() refactor eliminates 13K lines of duplication
The navbar, footer, and homepage templates were refactored from 6 per-language copies each to a single shared template that reads all user-visible strings from Lang::_('key') calls into per-language locale files. Net result: 13,307 lines of duplicated markup eliminated. Adding a 7th language now takes ~30 minutes instead of ~5 hours.
April 10, 2026
Fixed: JPG/PNG/PDF to DXF landing pages had a broken upload widget
Found during a full-site frontend audit: the JPG to DXF, PNG to DXF, and PDF to DXF templates had unbalanced HTML — a leftover empty <div> with a stray </button> and two extra </div> closing tags from when the templates were generated by stripping the “Paste SVG Code” feature out of the SVG to DXF page. Bonus content fixes: the “Choose File” button still said “Choose SVG File” on all three pages, the placeholder file badge said .svg instead of .jpg/.png/.pdf, and the “What is JPG/PNG/PDF?” section described SVG instead of the actual format.
Improved: Open Graph meta tags are now auto-injected for every page
58 pages (mostly unit converters and a few utility pages) were missing og:title and og:description tags, which meant their Facebook/Twitter/LinkedIn share previews showed only the URL with no title or description text. The shared layout (blog.php) now derives og:title from the page <title> (stripped of the “— CleverUtils.com” suffix), og:description from the page meta description, and also auto-injects og:url, og:type, twitter:title, twitter:description, and twitter:card when the page head doesn't already define them. Pages that explicitly set these tags continue to override the auto-injection.
Fixed: All four DXF converters were silently broken since launch
SVG to DXF, JPG to DXF, PNG to DXF, and PDF to DXF were all returning "Conversion from X to dxf is not supported" for every upload, on both the public form and the REST API. The to_dxf handler (Inkscape for vectors, potrace+Inkscape for raster) was added on 7 April but the corresponding output format was never registered in the realConversions allow-list, so the upstream validation rejected every request before reaching the handler. Added dxf as a valid output for svg, jpg, jpeg, png, gif, bmp, webp, tiff, tif, eps, ai, and pdf. All four landing pages verified working on dev and prod.
Fixed: remove-audio-from-video API endpoint was rejecting same-format videos
/api/v1/tools/remove-audio-from-video returned "Conversion from mp4 to mp4 is not supported" because the preserve_ext alias correctly tried to keep the input format, but our realConversions allow-list never contained same-format video entries (mp4→mp4, mov→mov, etc.). Added the same-format entries for the main video container formats — this also enables recompression and speed-change without changing container format.
Fixed: REST API: three tools were unreachable through the dispatcher
The /api/v1/tools/qr-code-generator, /api/v1/tools/webpage-to-pdf, and /api/v1/tools/gif-maker endpoints were returning NO_FILE (qr/webpage) or CONVERSION_FAILED (gif-maker). The dispatcher in api.php hard-required _FILES['file'], but qr-code-generator takes text, webpage-to-pdf takes url, and gif-maker's handler reads _FILES['video'], not 'file'. Added no_file and file_field metadata to the TOOL_MAP entries; the dispatcher now skips the file check for text/URL tools and aliases the incoming file upload to the field the handler reads from. All three endpoints verified working on dev and prod.
Fixed: REST API: validation errors now return HTTP 400 instead of 500
Bad-input errors like "Please enter a password" (protect-pdf), "Please enter watermark text" (watermark-image), and "Invalid time range" (audio-cutter, trim-video) were being surfaced as HTTP 500 CONVERSION_FAILED, which made it impossible for clients to distinguish a server error from a missing parameter. Native handlers in convert.php emit user-facing strings without setting an HTTP status, so the API wrapper now classifies common validation message patterns and demotes them to HTTP 400 with a new VALIDATION error code.
Improved: REST API rate limiter no longer auto-bans on cooldown 429s
The 5-tier rate limiter would record any 429 (including the benign 1-second cooldown 429) toward a ban counter that triggered a 24-hour IP block at 10 events per hour. Sequential clients doing tool chaining could easily generate 10+ cooldown 429s during normal use and get banned. Cooldown 429s are now skipped from the ban counter entirely — only real abuse signals (burst, hourly, daily, per-endpoint caps) count toward escalation, and the threshold itself was raised from 10 to 30.
New: REST API: flip-image tool
Added /api/v1/tools/flip-image — mirrors an image horizontally (default), vertically, or both. Accepts file (any common image format) and an optional mode parameter (horizontal, vertical, both). Previously listed in the TOOL_MAP as NOT_IMPLEMENTED.
Fixed: iPhone HEVC MP4 files were rejected as invalid
iPhone videos saved as .mp4 with HEVC encoding were being rejected with "This file is not a valid MP4 file" at upload time, blocking conversion to MP3, WAV, GIF, and other formats. iPhone iCloud streaming optimization writes a wide placeholder atom at the start of the file followed immediately by a multi-megabyte mdat payload, with the ftyp and moov boxes appended at the end — perfectly valid ISO Base Media Format, but our magic-byte validator only accepted files where ftyp appears at offset 4. Validator now accepts any of the legal first-box types (ftyp, wide, free, skip, mdat, moov, pnot) for the entire mp4/mov/m4a/m4v/3gp family.
Fixed: XML Formatter page was returning a truncated empty response
The XML Formatter page was silently broken: HTTP 200 but the response stopped mid-render with no body content and no H1, because the sample-XML JavaScript literal contained <?xml ... ?> which PHP (with short_open_tag enabled) parsed as a PHP open/close tag and aborted with a syntax error. Sample XML is now built via String.fromCharCode so neither <? nor ?> appears in the PHP source. Found during a full-site rendering audit.
Fixed: EPUB and ebook tools robust against wrong file extensions
If a user uploaded an EPUB file with a different extension (e.g. book.docx), our magic-byte detection correctly identified it as EPUB and routed to the right handler — but pandoc and kindlegen then re-detected the format from the on-disk filename and failed. Now both epub_to_pdf and epub_to_mobi create a sibling symlink with the right .epub extension before invoking the external tool, so the conversion works regardless of how the user named their file.
Fixed: HTML to PDF was silently broken
The HTML to PDF tool had been silently producing empty PDFs (a directory listing of the input mount point) for some time. Same root cause as the EPUB fix earlier today: the intermediate sanitized HTML was written to PHP-FPM's sys_get_temp_dir(), which lives in a private mount namespace invisible to the host Docker daemon, so Chrome ended up rendering an empty bind mount. The html_to_pdf handler now writes its scratch HTML to the per-job upload directory, which both PHP and Docker can see. Found during a code-audit pass after the EPUB fix.
Fixed: EPUB to PDF and other ZIP-based document conversions
EPUB → PDF was failing for almost every user with a generic "source file could not be loaded" error (184 failed jobs in the last week). Two unrelated bugs combined: (1) our magic-byte detector returned docx for every ZIP-based file, so EPUBs were routed through LibreOffice as Word documents and crashed; (2) our Chrome PDF renderer mounted the input file at a path inside PHP-FPM's private temp namespace, which the host Docker daemon couldn't see, so Chrome ended up rendering an empty directory listing.
Detection now opens the ZIP and reads the OCF mimetype entry (EPUB, ODT, ODS, ODP) or looks for OOXML markers (DOCX, XLSX, PPTX). The PDF renderer now writes its intermediate HTML to the job's working directory instead of /tmp. Also fixed the Chrome bind-mount path so the file URL has a real .html extension.
New: Public REST API at /api
CleverUtils now ships with a free public REST API for programmatic file conversion. Convert images, audio, video, documents, and PDFs — plus AI tools like upscaling, background removal, vocal isolation, and speech-to-text — with a single HTTP request. Anonymous (no signup, no API keys), per-IP rate limiting, batch endpoint for up to 20 files, full documentation, and integration snippets on every relevant tool page.
Fixed: OG images now show CleverUtils.com brand
Social media share previews (Facebook, Twitter, LinkedIn) were still showing the old Convertio.com brand on some pages. The dynamic OG image generator (og.php) had already been updated to render CleverUtils.com, but cached JPEGs in images/og/ from before the rebrand were still being served. Purged 732 stale cache files locally and invalidated the corresponding URLs in Cloudflare so social platforms refetch the new branded images on next share.