{"id":14966,"date":"2026-08-20T21:12:59","date_gmt":"2026-08-20T19:12:59","guid":{"rendered":"https:\/\/shortpixel.com/blog\/?p=14966"},"modified":"2026-08-20T21:20:22","modified_gmt":"2026-08-20T19:20:22","slug":"image-optimization-in-headless-wordpress-with-next-js","status":"publish","type":"post","link":"https:\/\/shortpixel.com\/blog\/image-optimization-in-headless-wordpress-with-next-js\/","title":{"rendered":"Image Optimization in Headless WordPress With Next.js"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Headless WordPress solves a lot of problems. Images aren&#8217;t one of them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You keep the editor your team already knows, you get a Next.js frontend that feels instant, and everyone&#8217;s happy, right up until someone runs Lighthouse on a post with a hero image and a gallery.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s what nobody mentions when you decouple: <strong>most of WordPress&#8217;s responsive image delivery happens at render time.<\/strong> And in a headless setup, WordPress doesn&#8217;t render anything anymore. It hands over JSON and walks away.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What actually breaks<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Your media library is fine. Uploads work, thumbnails get generated, the REST API and WPGraphQL still return image URLs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What breaks is the <strong>delivery layer<\/strong>, every mechanism that depends on WordPress producing HTML.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Mechanism<\/strong><\/td><td><strong>Works headless?<\/strong><\/td><td><strong>Why<\/strong><\/td><\/tr><tr><td>ShortPixel Image Optimizer compressing your library<\/td><td>\u2705<\/td><td>Runs on upload, server-side. Nothing to do with rendering.<\/td><\/tr><tr><td>WebP\/AVIF served via .htaccess<\/td><td>\u2705, if configured at the origin<\/td><td>Negotiated on the image request itself, not on the HTML.<\/td><\/tr><tr><td>&lt;picture&gt; tag replacement<\/td><td>\u274c<\/td><td>It rewrites WordPress&#8217;s HTML output. There isn&#8217;t any.<\/td><\/tr><tr><td>Automatic CDN URL rewriting in the plugin<\/td><td>\u274c<\/td><td>Same reason. The rewrite hooks into page output.<\/td><\/tr><tr><td>Adaptive Images on-the-fly DOM rewriting<\/td><td>\u274c<\/td><td>Its JavaScript runs on the WordPress-rendered page.<\/td><\/tr><tr><td>The Adaptive Images <strong>API<\/strong><\/td><td>\u2705<\/td><td>Plain URLs. Stack-agnostic by design.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">That last row is the one to hold on to.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The practical version: install an optimization plugin on a headless WordPress, check the frontend, see no difference, conclude the plugin is broken. It isn&#8217;t. It&#8217;s optimizing files that a Next.js app requests in a way the plugin never gets to influence.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>The<\/strong> <strong>next\/image<\/strong> <strong>trap<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Next.js has a great &lt;Image&gt; component, and the natural assumption is that it handles everything. It doesn&#8217;t.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>It isn&#8217;t free.<\/strong> On Vercel, image optimization is metered. Self-hosted, you&#8217;re running sharp on your own CPU with a cache directory you now have to monitor and clean up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>It can&#8217;t undo a bad original.<\/strong> next\/image re-encodes whatever WordPress hands it. If an editor uploaded a 6 MB phone photo, you get a smaller version of a 6 MB phone photo, and your storage, backups, staging syncs, and every cold origin fetch still carry the full-size file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The default optimizer doesn&#8217;t work with static export.<\/strong> Under output: &#8216;export&#8217;, Next.js can&#8217;t use its built-in Image Optimization API, because optimization happens on demand as users request images rather than at build time. The build errors out. Your two documented ways out are unoptimized: true, which gives up on optimization entirely, or a custom loader backed by an external service. Hold that thought.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>It only covers what you render through it.<\/strong> Anything inside dangerouslySetInnerHTML bypasses it completely. More on that below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lazy loading, srcset generation, aspect-ratio reservation: all worth keeping. It just isn&#8217;t a strategy on its own.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Two layers, two jobs<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Layer 1 \u2014 the library on disk.<\/strong> Every file in wp-content\/uploads should already be as small as it reasonably can be. When we <a href=\"https:\/\/shortpixel.com\/blog\/batch-compress-11000-images\/\">compressed 11,000 images<\/a> through the ShortPixel API, the library went from 536.6 MB to 287.2 MB, a 46.5% reduction on mixed photographic content. That&#8217;s the baseline you leave on the table by skipping this layer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Layer 2 \u2014 delivery.<\/strong> Per-request resizing and format negotiation. A visitor on a 390px phone shouldn&#8217;t download a 1920px image, and if their browser takes AVIF they should get AVIF.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a normal WordPress site the two layers blur together, because whatever compresses your files is also wired into WordPress&#8217;s output. Headless breaks that coupling, so you configure them separately.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step 1: Optimize the media library<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Install ShortPixel Image Optimizer and run a bulk optimization. Three settings matter here:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Compression level.<\/strong> Lossy for most sites, Glossy for portfolios and product shots where clients pin you on detail.<\/li>\n\n\n\n<li><strong>Resize large images.<\/strong> Cap the maximum dimension at 2048px. Where editors upload straight from a DSLR, this does more than compression.<\/li>\n\n\n\n<li><strong>Skip the<\/strong> <strong>&lt;picture&gt;<\/strong> <strong>tag delivery method.<\/strong> It adds a script to a page that doesn&#8217;t exist.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Keep backups on for the first run. Always.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step 2: Audit the sizes WordPress generates<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">WordPress generates multiple resized copies of every upload. A site with 1,000 images often has 5,000 to 15,000 files on disk once you count them all.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In a headless setup where every frontend image is resized on demand, <strong>many of those may be redundant.<\/strong> Next.js asks for the widths it needs, the CDN produces them, and the medium_large copy on your disk may never be requested by anyone.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">May. Not definitely.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ mu-plugin or functions.php on the WordPress side\nadd_filter('intermediate_image_sizes_advanced', function ($sizes) {\n    unset($sizes&#91;'medium_large'], $sizes&#91;'1536x1536'], $sizes&#91;'2048x2048']);\n    return $sizes;\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Audit before you disable. WordPress uses intermediate sizes to build responsive markup, and the editor, feeds, and any other client consuming the same CMS may depend on them. Keep thumbnail regardless \u2014 the admin media grid uses it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One dependency to flag now: WordPress builds the srcset for images inside post content out of these exact sizes. Strip them and that markup gets thinner. Which route you take depends on how you handle content images.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step 3: Pull the right data out of WordPress<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Three things per image: the URL, the intrinsic dimensions, and the alt text. Dimensions are non-negotiable, without them next\/image can&#8217;t reserve space and you ship layout shift.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>query PostBySlug($slug: ID!) {\n  post(id: $slug, idType: SLUG) {\n    title\n    featuredImage {\n      node {\n        sourceUrl\n        altText\n        mediaDetails {\n          width\n          height\n        }\n      }\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On REST, the same data lives at \/wp-json\/wp\/v2\/media\/&lt;id&gt; under source_url, alt_text, and media_details.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two details worth getting right. <strong>Request the full-size<\/strong> <strong>sourceUrl<\/strong><strong>, not a named thumbnail size<\/strong>, you want WordPress&#8217;s <a href=\"https:\/\/shortpixel.com\/blog\/what-is-the-scaled-image-in-your-wordpress-media-library-and-do-you-still-need-the-original\/\">largest web-facing<\/a> version as the base for every transformation, because resizing an already-recompressed 300px thumbnail looks exactly like what it is. And carry altText through as WordPress gives it to you: informative images need a real text alternative, decorative ones should keep alt=&#8221;&#8221;, and for the ones that do carry meaning, vague alt text is increasingly unhelpful now that <a href=\"https:\/\/shortpixel.com\/blog\/how-ai-read-your-website-images\/\">AI crawlers<\/a> can interpret the image itself.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Step 4: Point<\/strong> <strong>next\/image<\/strong> <strong>at ShortPixel<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Adaptive Images API is a URL-based transformation API, documented and usable from any stack, and its shape maps almost perfectly onto what a Next.js loader needs to produce.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One setup step first, and it&#8217;s the one that trips people up: <strong>the domain hosting your images has to be associated with your ShortPixel account.<\/strong> Log in, open <a href=\"https:\/\/shortpixel.com\/login\/associated-domains\">Associate Domains<\/a>, add it. In a headless build that&#8217;s your WordPress install, cms.example.com, or your S3 bucket, not your Next.js frontend.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The URL looks like this:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/cdn.shortpixel.ai\/client\/to_auto,w_800,q_glossy,ret_img\/https:\/\/cms.example.com\/wp-content\/uploads\/2026\/03\/photo.jpg\" target=\"_blank\" rel=\"noopener\">https:\/\/cdn.shortpixel.ai\/client\/to_auto,w_800,q_glossy,ret_img\/https:\/\/cms.example.com\/wp-content\/uploads\/2026\/03\/photo.jpg<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Four parts: the CDN host, an arbitrary alphanumeric cache key, the transformation parameters, and the absolute URL of your original. The parameters worth knowing are w_ and h_ for dimensions, q_lossy \/ q_glossy \/ q_lossless for compression, ret_img to redirect to the original while a variant is still generating, and to_auto, which serves AVIF where supported, WebP where not, and the original format otherwise, the whole format-negotiation problem in nine characters.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now the loader:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ lib\/shortpixel-loader.js\n'use client'\n\nconst CDN = 'https:\/\/cdn.shortpixel.ai'\nconst CACHE_KEY = 'headlesswp' \/\/ any alphanumeric string; keep it stable\n\nexport default function shortPixelLoader({ src, width }) {\n  const absolute = src.startsWith('http')\n    ? src\n    : `${process.env.NEXT_PUBLIC_WP_URL}${src}`\n\n  \/\/ The CDN can't reach your machine, so don't transform in dev\n  if (process.env.NODE_ENV === 'development') return absolute\n\n  const params = &#91;'to_auto', `w_${width}`, 'q_glossy', 'ret_img'].join(',')\n\n  return `${CDN}\/${CACHE_KEY}\/${params}\/${absolute}`\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The cache key has to be alphanumeric, no hyphens. And note what the loader <em>doesn&#8217;t<\/em> do: it ignores quality. ShortPixel takes named quality levels rather than a 1\u2013100 number, so there&#8217;s nothing sensible to map onto, and Next.js 16 makes the mismatch worse, images.qualities now defaults to [75] and coerces anything outside that allowlist to the nearest entry. Set the compression level once in the loader and leave the quality prop off your components.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wire it up:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ next.config.js\nmodule.exports = {\n  images: {\n    loader: 'custom',\n    loaderFile: '.\/lib\/shortpixel-loader.js',\n    \/\/ Trim the default breakpoints, every extra width is another variant\n    deviceSizes: &#91;640, 828, 1080, 1280, 1920],\n    imageSizes: &#91;256, 384],\n  },\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">With a custom loader, requests never touch \/_next\/image. Next generates the srcset, calls your loader once per width, and the browser fetches straight from the CDN. No sharp, no \/_next\/image disk cache, no Vercel image transformation usage, and it works under output: &#8216;export&#8217;. Not that images become free, ShortPixel meters its own usage, but you&#8217;re swapping a metered resource you have to operate for one you don&#8217;t.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then the component:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import Image from 'next\/image'\n\nexport function FeaturedImage({ image }) {\n  return (\n    &lt;Image\n      src={image.sourceUrl}\n      alt={image.altText || ''}\n      width={image.mediaDetails.width}\n      height={image.mediaDetails.height}\n      sizes=\"(max-width: 768px) 100vw, 720px\"\n      fetchPriority=\"high\"\n    \/&gt;\n  )\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Two things there. priority is gone, Next.js 16 deprecated it in favor of preload, but the docs are clear that in most cases you want loading=&#8221;eager&#8221; or fetchPriority=&#8221;high&#8221; instead, with preload reserved for an unambiguous LCP element you need discovered before the parser reaches the &lt;body&gt;. And do not skip sizes: without it, Next assumes the image spans the full viewport and requests widths sized for a 4K monitor. It&#8217;s the most common reason a &#8220;properly optimized&#8221; headless site still ships oversized images.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Building headless and don&#8217;t want a plugin in the loop?<\/strong> The ShortPixel Adaptive Images API works with any stack, Next.js, Nuxt, Astro, a plain static site. There&#8217;s also a Node.js client and an Express middleware if you&#8217;d rather optimize inside your own pipeline. <a href=\"https:\/\/shortpixel.com\/free-sign-up\">Grab an API key and try it.<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>The images inside your post content<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Featured images are the easy case. The hard one is post.content,&nbsp; a blob of HTML dropped into the page with dangerouslySetInnerHTML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Those images bypass next\/image completely, and on a long article they&#8217;re usually the bulk of the page weight. What they don&#8217;t necessarily lack is responsive markup: rendered content still runs through the_content, where wp_filter_content_tags() adds srcset, sizes, loading, decoding, and dimensions to any image it can match back to the media library. So that HTML may already be in reasonable shape. What it isn&#8217;t is <em>yours<\/em>, Next controls none of it, and none of those URLs pass through your loader.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two ways to fix that, pulling in opposite directions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Parse and replace.<\/strong> Swap &lt;img&gt; nodes for real Image components with html-react-parser:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import parse from 'html-react-parser'\nimport Image from 'next\/image'\n\nconst options = {\n  replace: (node) =&gt; {\n    if (node.name !== 'img') return\n    const { src, alt, width, height } = node.attribs\n    return (\n      &lt;Image\n        src={src}\n        alt={alt || ''}\n        width={Number(width) || 1200}\n        height={Number(height) || 800}\n        sizes=\"(max-width: 768px) 100vw, 720px\"\n      \/&gt;\n    )\n  },\n}\n\nexport function PostBody({ html }) {\n  return &lt;div className=\"prose\"&gt;{parse(html, options)}&lt;\/div&gt;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">One pipeline for everything, but you&#8217;re rebuilding WordPress&#8217;s markup from attributes that aren&#8217;t always there. widthand height go missing on older content, and a fallback guess is a layout shift.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Or keep the markup and rewrite the URLs inside it.<\/strong> Transform the HTML at fetch time and point every uploads URL at ShortPixel, including each one inside srcset:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ lib\/rewrite-content-images.js\nconst CDN = 'https:\/\/cdn.shortpixel.ai\/headlesswp'\nconst PARAMS = 'to_auto,q_glossy,ret_img'\n\nconst UPLOADS = \/https?:\\\/\\\/cms\\.example\\.com\\\/wp-content\\\/uploads\\\/&#91;^\\s\"']+\/g\n\nexport function rewriteContentImages(html) {\n  return html.replace(UPLOADS, (url) =&gt; `${CDN}\/${PARAMS}\/${url}`)\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">One pass covers src and srcset together, since both hold plain uploads URLs, and [^\\s&#8221;&#8216;]+ stops at whitespace so a srcset entry like photo-1024&#215;683.jpg 1024w keeps its width descriptor. Note there&#8217;s no w_ here, and that&#8217;s the point: WordPress already generated a file per size and already told the browser which is which, so the descriptors do the sizing while ShortPixel compresses and format-negotiates whichever one gets picked.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The trade-off ties straight back to Step 2,&nbsp; this route <strong>depends<\/strong> on those intermediate sizes existing. So it&#8217;s a genuine fork. Parse-and-replace if you want one pipeline and you&#8217;re willing to fix your content&#8217;s missing attributes; URL rewriting if you&#8217;d rather trust WordPress&#8217;s responsive markup and keep the thumbnails that feed it. What doesn&#8217;t work is stripping the sizes <em>and<\/em> leaving content images alone.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Don&#8217;t forget your<\/strong> <strong>public\/<\/strong> <strong>folder<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Your logo, OG images, icons, and the hero illustration your designer exported at 2400px don&#8217;t live in WordPress, so nothing in this pipeline touches them. It&#8217;s easy to miss, because the WordPress side looks perfectly configured while your heaviest asset is an unoptimized PNG in \/public. Add a compression step to CI, the ShortPixel command-line tool and the <a href=\"https:\/\/github.com\/short-pixel-optimizer\/shortpixel-sh\" target=\"_blank\" rel=\"noopener\">shortpixel-optimize.sh<\/a> script both handle a folder in one pass.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Verifying it works<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open DevTools, go to the Network tab, filter by Img, reload, and check three things:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>The request URL<\/strong> goes to the CDN, not to your WordPress origin or \/_next\/image.<\/li>\n\n\n\n<li><strong>The<\/strong> <strong>content-type<\/strong> is image\/avif or image\/webp. One caveat: ret_img means the first request can redirect to the original while the variant is still generating, so a JPEG on the first hit isn&#8217;t necessarily a problem. Give it time and retry. If Chrome keeps getting the original format after that, check to_auto, your domain association, and the CDN URL before blaming the browser.<\/li>\n\n\n\n<li><strong>The transferred size<\/strong> is meaningfully smaller than the file in your media library, and the w_ value roughly matches the rendered width. If you asked for w_1920 on a phone-width layout, your sizes attribute is wrong.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Then run Lighthouse. LCP is where this work shows up, and the fix is usually a few hundred kilobytes rather than a few hundred milliseconds of JavaScript.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>FAQs<\/strong><\/h2>\n\n\n<div id=\"rank-math-faq\" class=\"rank-math-block\">\n<div class=\"rank-math-list \">\n<div id=\"faq-question-1787252512702\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Do I still need a WordPress optimization plugin if Next.js is optimizing images?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>Yes, for a reason that isn&#8217;t about page speed. Next.js optimizes what gets delivered; the plugin optimizes what gets stored. Without it, your disk, backups, staging syncs, and migrations carry full-size originals forever.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1787252567436\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Does this work with static export?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>It&#8217;s arguably where it works best. Under output: &#8216;export&#8217; the built-in optimizer isn&#8217;t available and the build errors out, and a custom loader is one of the two fixes Next.js documents, the other being unoptimized: true, which just gives up. The loader only builds URL strings, so it&#8217;s perfectly happy at build time.<\/p>\n\n<\/div>\n<\/div>\n<div id=\"faq-question-1787252581803\" class=\"rank-math-list-item\">\n<h3 class=\"rank-math-question \"><strong>Will the CDN work if my WordPress install isn&#8217;t public?<\/strong><\/h3>\n<div class=\"rank-math-answer \">\n\n<p>No. The transformation service fetches your original over HTTP, so if WordPress sits behind basic auth, an IP allowlist, or a VPN, it can&#8217;t reach the file. Expose the uploads directory publicly, or move your media to public object storage.<\/p>\n\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n<div class=\"wp-block-group has-border-color has-palette-color-4-border-color has-palette-color-6-background-color has-background is-vertical is-content-justification-left is-layout-flex wp-container-core-group-is-layout-02a0377d wp-block-group-is-layout-flex\" style=\"border-width:1px;border-radius:20px;margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60);padding-top:var(--wp--preset--spacing--60);padding-right:var(--wp--preset--spacing--60);padding-bottom:var(--wp--preset--spacing--60);padding-left:var(--wp--preset--spacing--60)\">\n<h3 id=\"cta-heading\" class=\"wp-block-heading\" style=\"margin-top:0;margin-right:0;margin-bottom:0;margin-left:0;padding-top:0;padding-right:0;padding-bottom:0;padding-left:0\"><strong>Try ShortPixel on WordPress for free!<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Easily optimize your pictures and generate WebP\/AVIF in bulk using ShortPixel Image Optimizer.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/wordpress.org\/plugins\/shortpixel-image-optimiser\/\" target=\"_blank\" rel=\"noreferrer noopener\">Get Started<\/a><\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Headless WordPress solves a lot of problems. Images aren&#8217;t one of them. You keep the editor your team already knows, you get a Next.js frontend that feels instant, and everyone&#8217;s happy, right up until someone runs Lighthouse on a post with a hero image and a gallery. Here&#8217;s what nobody mentions when you decouple: most [&hellip;]<\/p>\n","protected":false},"author":37,"featured_media":14967,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_members_access_role":[],"_members_access_error":""},"categories":[11],"tags":[],"class_list":["post-14966","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-image-optimization"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/posts\/14966","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/users\/37"}],"replies":[{"embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/comments?post=14966"}],"version-history":[{"count":3,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/posts\/14966\/revisions"}],"predecessor-version":[{"id":14971,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/posts\/14966\/revisions\/14971"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/media\/14967"}],"wp:attachment":[{"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/media?parent=14966"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/categories?post=14966"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shortpixel.com\/blog\/wp-json\/wp\/v2\/tags?post=14966"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}