Skip to main content

Changelog

April 29, 2026

Fixed: MCP endpoint no longer triggers PHP warnings

JSON-RPC POST /mcp requests were generating a PHP Warning: Undefined array key "action" in kernel.php because the routing code assumed every POST has a form-encoded action field. Added an isset() guard so MCP and API JSON payloads pass through cleanly.

New: Algolia-powered site search

The header search field is now connected to Algolia. Type 2+ characters to see instant results across all converters, tools, and guides (767 pages indexed in 7 languages). Keyboard navigation with ↑/↓/Enter/Escape and ⌘K shortcut to focus. Results show type badges (Converter, Tool, Guide) and category tags.

New: Redesigned site header (v2)

Simplified navigation: Logo, Converters mega-menu (combined Convert + Tools), search bar, theme switcher, language switcher. On mobile: clean hamburger drawer with search and category links. The old 9-slot header replaced with 5 focused elements.

New: Guides section on homepage

A new “Guides & Tutorials” section appears on the homepage with 6 latest articles from diverse categories (AI Image, AI Audio, Video, Image, etc.). Fully translated across all 7 languages.

Improved: Code blocks now use dark theme in both light and dark mode

Inline code snippets and syntax-highlighted code blocks now consistently use a dark background (Catppuccin Mocha) regardless of the site theme. This matches the industry standard (GitHub, VS Code) and improves readability in light mode.

April 22, 2026

Fixed: Screaming Frog audit: 300+ internal 404s cleaned across converter cross-links, CTAs, and state picker

SEO crawl surfaced seven distinct 4xx causes. Cleared all of them:

  • Dead “related converter” links — 15 slugs (pdf-to-tiff, mp3-to-mp4, video-to-gif, rotate-video, xlsx-to-json, csv-to-pdf, csv-to-json, pdf-to-mobi, flac-to-ogg, mobi-to-pdf, pdf-to-word, avi-to-mp3, add-border, mobi-to-epub, png-to-tiff) referenced in src/data/pages/<parent>/{en,de,fr,es,it,pt,nl}.php but never built. Removed 123 stale array entries across 7 languages.
  • Remaining double-prefixed CTAs (/de/de/speech-to-text, /de/de/resize-image, etc.) — the 2026-04-17 sweep missed 12 templates that used a ternary variant $_langPrefix ? "/{$_langPrefix}" . $_p[...] : $_p[...]. Stripped the legacy prefix logic from resize-image/{facebook,pinterest,youtube,linkedin}, speech-to-text/{transcribe-audio,audio-to-text,transcribe-interview,transcribe-podcast,video-to-subtitles}, and vocal-remover/{isolate-vocals,karaoke-maker,separate-drums}.
  • State-picker 404s on /random-phone-number — the landing page linked to 50 per-state URLs (/random-phone-number/alabama etc.) that were never routed. Converted the block into interactive buttons that set the dropdown, trigger generation, and scroll to the output — no broken URLs, same UX.
  • API error docs pointed to /api/reference/convert (non-existent sub-page). Fixed all 7 language files to use the #convert anchor on the existing /api/reference page.
  • Escaped-quote hrefs in pdf-page-remover/de.phphref=\"/de/merge-pdf\" inside a single-quoted PHP string emitted literal backslash-quotes, producing unusable hrefs like /de/\"/de/merge-pdf\". Replaced with proper double quotes.
  • Cross-language body link to untranslated articlem4a-to-wav/wav-quality-settings linked to /xx/m4a-to-wav/soxr-resampler in all 6 non-English languages, but the article only lives under mp3-to-wav in English. Stripped the broken link wrapper, kept the phrase inline.

Net result: ~300 internal 404 URLs cleared with minimal template changes; no new converter pages built (links simply removed where the target didn't exist). Cloudflare /cdn-cgi/l/email-protection 404s (1431 inlinks) are a CF email-obfuscation artefact and need to be ignored at the crawler level, not fixed in code.

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/ and converter/ folders (was simple/ only). Previously /de/random-address served 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/reference without a locale prefix on every converter page. Now wrapped in clv_i18n_link().
  • Changelog pagination links /changelog?page=N missed 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) and author (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 — a strip_tags() call was flattening every answer to plain text before. All structured data blocks were also switched from hand-built string concatenation to json_encode with proper JSON_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 (initializetools/listtools/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.

Request a Feature

0 / 2000