{"id":40064,"date":"2026-05-26T07:00:00","date_gmt":"2026-05-26T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=40064"},"modified":"2026-05-20T15:35:56","modified_gmt":"2026-05-20T22:35:56","slug":"blog-cover-images-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js","title":{"rendered":"Standardize Your Blog Cover Images With Next.js and Cloudinary"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Blog grids are much more visually appealing when every cover image is the same shape, but uploaded images rarely match. One image may be wide, another may be tall. Another may be square. When those images are arranged in a grid, the layout can look uneven.<\/p>\n<p>In this guide, you\u2019ll build a <strong>blog cover image manager<\/strong> in Next.js with Cloudinary. The app will let you upload a blog cover image, store the returned <code>public_id<\/code>, and use Cloudinary transformations to display it in a clean <strong>16:9 format<\/strong>.<\/p>\n<ul>\n<li>\n<strong>Live Demo:<\/strong> <a href=\"https:\/\/blog-cover-manager.vercel.app\/\">https:\/\/blog-cover-manager.vercel.app\/<\/a>\n<\/li>\n<li>\n<strong>GitHub Repo:<\/strong> <a href=\"https:\/\/github.com\/musebe\/blog-cover-manager\">https:\/\/github.com\/musebe\/blog-cover-manager<\/a>\n<\/li>\n<\/ul>\n<h3>What You\u2019ll Use<\/h3>\n<p>To achieve a professional, consistent look, we\u2019ll leverage:<\/p>\n<ul>\n<li>Cloudinary Upload Widget for seamless uploads.<\/li>\n<li>\n<code>c_fill<\/code> to crop covers into the same shape.<\/li>\n<li>\n<code>g_auto<\/code> to keep the subject in view automatically.<\/li>\n<li>Automatic format and quality for optimized delivery.<\/li>\n<li>Loading skeletons to maintain UI stability while images load.<\/li>\n<\/ul>\n<p>By the end, your blog grid will have consistent cover images without manually editing each upload.<\/p>\n<h2>Crafting the Perfect Blog Cover<\/h2>\n<p>Blog cover images often come in different shapes if you don\u2019t standardize them.<\/p>\n<p>When placed in the same blog grid, the layout can look <strong>uneven<\/strong>. Cards may have different heights, covers may stretch, and key parts of an image may get cut off. The loading experience can also feel rough if the image space is not planned well.<\/p>\n<h3>The Fix: Uniform 16:9 Frames<\/h3>\n<p>For this project, the fix is to make every blog cover fit the same <strong>16:9 frame<\/strong> by using two powerful Cloudinary features:<\/p>\n<ul>\n<li>\n<code>c_fill<\/code> (Fill Crop) crops and resizes an image so it fills the exact space you define.<\/li>\n<li>\n<code>g_auto<\/code> (Auto Gravity)helps Cloudinary detect the main subject and keep it in view during the cropping process.<\/li>\n<\/ul>\n<p>The result is a portrait photo, square graphic, and wide banner that appear as clean, consistent blog covers in the same grid.<\/p>\n<h2>What You\u2019ll Build<\/h2>\n<p>Your blog cover image manager app will include:<\/p>\n<ul>\n<li>An admin upload page.<\/li>\n<li>A Cloudinary Upload Widget button.<\/li>\n<li>A blog card grid.<\/li>\n<li>Clean 16:9 cover images.<\/li>\n<li>Loading skeletons while images load.<\/li>\n<\/ul>\n<p>A user uploads a cover image from the admin page. Cloudinary returns upload data, including the image <code>public_id<\/code>. The app then passes that <code>public_id<\/code> into a blog card.<\/p>\n<p>Inside the card, Cloudinary transforms the image with <code>c_fill<\/code> and <code>g_auto<\/code>. This makes every cover fit the same 16:9 space while keeping the main subject visible. The result is a cleaner blog grid. Each card keeps the same shape, even when the uploaded images have different sizes.<\/p>\n<h2>Project Setup<\/h2>\n<p>Start by creating a new Next.js project.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npx<\/span> <span class=\"hljs-selector-tag\">create-next-app<\/span><span class=\"hljs-keyword\">@latest<\/span> blog-cover-manager\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Move into the project folder:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">cd blog-cover-manager\n<\/code><\/span><\/pre>\n<p>Install the Cloudinary packages:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-keyword\">@cloudinary<\/span>\/react @cloudinary\/url-gen\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next, create a <code>.env.local<\/code> file in the project root.<\/p>\n<pre class=\"js-syntax-highlighted\"><code>NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name\nNEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET=your_unsigned_upload_preset\n<\/code><\/pre>\n<p>Use <code>NEXT_PUBLIC_<\/code> because the upload widget runs in the browser. Your cloud name comes from your <a href=\"https:\/\/cloudinary.com\/users\/login\">Cloudinary dashboard<\/a>. The <a href=\"https:\/\/cloudinary.com\/documentation\/upload_presets\">upload preset<\/a> should be unsigned for this beginner version.<\/p>\n<p>This setup gives the app what it needs to upload images from the browser and display optimized covers with Cloudinary.<\/p>\n<h2>Set Up the Cloudinary Upload Widget<\/h2>\n<p>Next, set up the Cloudinary Upload Widget. This gives users a ready-made upload modal, so you don\u2019t have to build the upload UI from scratch. First, add the widget script to your app.<\/p>\n<p>For Next.js, you can load it with the <code>Script<\/code> component:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Script <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/script\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">CloudinaryWidgetScript<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Script<\/span>\n      <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/upload-widget.cloudinary.com\/global\/all.js\"<\/span>\n      <span class=\"hljs-attr\">strategy<\/span>=<span class=\"hljs-string\">\"afterInteractive\"<\/span>\n    \/&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The widget needs two values from your environment file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> cloudName = process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME;\n<span class=\"hljs-keyword\">const<\/span> uploadPreset = process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then, when the upload succeeds, Cloudinary returns image data.<\/p>\n<p>The most important value is the <code>public_id<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">onUploadSuccess={(result) =&gt; {\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Public ID:\"<\/span>, result.public_id);\n}}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You\u2019ll use this <code>public_id<\/code> later to render the blog cover image.<\/p>\n<p>This is better than saving the full image URL. The <code>public_id<\/code> lets Cloudinary create different image sizes and crops from the same asset.<\/p>\n<h2>Build the Admin Upload Page<\/h2>\n<p>The admin page is the gateway for your content. It ties together three critical components into a single workflow:<\/p>\n<ul>\n<li>The Upload Widget handles the direct-to-Cloudinary file transfer.<\/li>\n<li>The Image Preview displays the transformed 16:9 version instantly.<\/li>\n<li>The Blog Post Form captures metadata (e.g., title, excerpt, and author).<\/li>\n<\/ul>\n<h3>Handling the Upload<\/h3>\n<p>When an image upload succeeds, the app captures the <code>public_id<\/code> and immediately generates a preview using your transformation logic:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleUploadSuccess<\/span>(<span class=\"hljs-params\">result: CloudinaryUploadResult<\/span>) <\/span>{\n  setPublicId(result.public_id); <span class=\"hljs-comment\">\/\/ Store the ID, not the full URL<\/span>\n  setPreviewUrl(buildCoverUrl(result.public_id));\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>So, why store the <code>public_id<\/code>? It\u2019s a best practice to <strong>store the <code>public_id<\/code>, not the full URL<\/strong>. By saving only the ID, your covers remain flexible. If you decide to change the crop, aspect ratio, or image quality later, you only need to update the <code>buildCoverUrl<\/code> function rather than migrating every entry in your database.<\/p>\n<h3>The Publishing Workflow<\/h3>\n<p>The admin form collects all the necessary blog details to ensure a complete card:<\/p>\n<ul>\n<li>Content includes Title and Excerpt.<\/li>\n<li>Metadata includes Author, Category, and Read Time.<\/li>\n<\/ul>\n<p>Once you hit submit, the post is saved, and the router redirects you to the homepage. The new post will appear in the grid instantly, featuring a perfectly formatted 16:9 cover image.<\/p>\n<blockquote>\n<p>View the full implementation of the admin dashboard in <a href=\"https:\/\/github.com\/musebe\/blog-cover-manager\/blob\/main\/src\/app\/admin\/page.tsx\">src\/app\/admin\/page.tsx<\/a>.<\/p>\n<\/blockquote>\n<h2>Create the Blog Cover Transformation<\/h2>\n<p>The secret to a clean blog grid lies in a single helper function: <code>buildCoverUrl<\/code>. Instead of manually editing images, you can use this function to apply a chain of transformations at delivery time to ensure every cover matches the layout perfectly.<\/p>\n<blockquote>\n<p>View the full implementation in <a href=\"https:\/\/github.com\/musebe\/blog-cover-manager\/blob\/main\/src\/lib\/cloudinary.ts\"><strong><code>src\/lib\/cloudinary.ts<\/code><\/strong><\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">buildCoverUrl<\/span>(<span class=\"hljs-params\">publicId: string<\/span>): <span class=\"hljs-title\">string<\/span> <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> transforms = &#91;\n    <span class=\"hljs-string\">\"c_fill\"<\/span>,   <span class=\"hljs-comment\">\/\/ Fill the frame<\/span>\n    <span class=\"hljs-string\">\"ar_16:9\"<\/span>,  <span class=\"hljs-comment\">\/\/ Lock the shape<\/span>\n    <span class=\"hljs-string\">\"g_auto\"<\/span>,   <span class=\"hljs-comment\">\/\/ Focus on the subject<\/span>\n    <span class=\"hljs-string\">\"w_800\"<\/span>,    <span class=\"hljs-comment\">\/\/ Set the width<\/span>\n    <span class=\"hljs-string\">\"q_auto\"<\/span>,   <span class=\"hljs-comment\">\/\/ Compress quality<\/span>\n    <span class=\"hljs-string\">\"f_auto\"<\/span>,   <span class=\"hljs-comment\">\/\/ Choose best format<\/span>\n  ].join(<span class=\"hljs-string\">\",\"<\/span>);\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`https:\/\/res.cloudinary.com\/<span class=\"hljs-subst\">${CLOUD_NAME}<\/span>\/image\/upload\/<span class=\"hljs-subst\">${transforms}<\/span>\/<span class=\"hljs-subst\">${publicId}<\/span>`<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>How the Magic Works<\/h3>\n<p>Each parameter in the URL performs a specific task to optimize the image:<\/p>\n<ul>\n<li>\n<code>c_fill<\/code> (Crop Fill) resizes and crops the image to completely fill the requested dimensions without stretching or distortion.<\/li>\n<li>\n<code>ar_16:9<\/code> (Aspect Ratio) forces the image into a standard 16:9 blog cover shape, ensuring every card in your grid is the same height.<\/li>\n<li>\n<code>g_auto<\/code> (Auto Gravity) uses AI to detect the most important part of the image \u2014 like a person\u2019s face or a central object \u2014 and keeps it centered during the crop.\n&#8211;<code>w_800<\/code> (Width) resizes the image to 800px wide, which is ideal for standard blog cards.\n&#8211;<code>q_auto<\/code> (Auto Quality) automatically compresses the file size to the lowest possible weight while maintaining visual quality.<\/li>\n<li>\n<code>f_auto<\/code> (Auto Format) detects the user\u2019s browser and serves the best format (like WebP or AVIF) for faster loading.<\/li>\n<\/ul>\n<p>By moving the transformation logic to a URL helper, you gain flexibility. Whether your original upload is a tall portrait, a square graphic, or a wide banner, the blog card receives a consistent, high-quality <strong>16:9 cover<\/strong> every time.<\/p>\n<h2>Build the Blog Card Component<\/h2>\n<p>The blog card is the final destination for your transformed cover image. It takes the stored <code>public_id<\/code>, runs it through the <code>buildCoverUrl<\/code> helper, and renders the result using the optimized <code>next\/image<\/code> component.<\/p>\n<blockquote>\n<p>View the full implementation here: <a href=\"https:\/\/github.com\/musebe\/blog-cover-manager\/blob\/main\/src\/components\/blog\/BlogCard.tsx\"><code>src\/components\/blog\/BlogCard.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h3>Locking the Layout<\/h3>\n<p>To prevent layout shifts and ensure consistency, the card wraps the image in an <code>AspectRatio<\/code> component. This ensures that every card in your grid occupies the exact same amount of vertical space, regardless of the user\u2019s screen size.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AspectRatio<\/span> <span class=\"hljs-attr\">ratio<\/span>=<span class=\"hljs-string\">{16<\/span> \/ <span class=\"hljs-attr\">9<\/span>}&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n    <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{buildCoverUrl(post.publicId)}<\/span>\n    <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{post.title}<\/span>\n    <span class=\"hljs-attr\">fill<\/span>\n    <span class=\"hljs-attr\">priority<\/span>=<span class=\"hljs-string\">{priority}<\/span>\n    <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"object-cover\"<\/span>\n  \/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">AspectRatio<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Key Technical Details<\/h3>\n<ul>\n<li>The <code>AspectRatio<\/code> utility holds the 16:9 shape even before the image finishes loading, preventing the grid from \u201cjumping.\u201d<\/li>\n<li>\n<code>fill<\/code> and <code>object-cover<\/code> are props that allow the image to expand and fill its container completely.<\/li>\n<li>Because the card uses <code>buildCoverUrl(post.publicId)<\/code>, it guarantees that every image is delivered with the same cropping and optimization rules.<\/li>\n<li>The component includes a gradient overlay and frosted-glass badges. These design elements ensure that text like categories and read times remain legible, no matter how busy or bright the background image is.<\/li>\n<\/ul>\n<p>The beauty of this component is its <strong>indifference to the original source<\/strong>. The card doesn\u2019t need to know if the author uploaded a high-res landscape or a smartphone portrait; it simply asks for a <code>public_id<\/code> and receives a perfectly formatted <strong>16:9 cover<\/strong> every time.<\/p>\n<h2>Add Loading Skeletons<\/h2>\n<p>To create a professional user experience, your grid should maintain its structure even before the images have finished downloading. This prevents \u201clayout shift,\u201d where the page content jumps around as images pop into place.<\/p>\n<blockquote>\n<p>View the full implementation here: <a href=\"https:\/\/github.com\/musebe\/blog-cover-manager\/blob\/main\/src\/components\/blog\/CoverSkeleton.tsx\"><code>src\/components\/blog\/CoverSkeleton.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h3>Creating the Placeholder<\/h3>\n<p>The skeleton component uses the exact same aspect ratio as your Cloudinary-transformed covers. This ensures the placeholder occupies the same physical footprint as the final image.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { AspectRatio } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/ui\/aspect-ratio\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Skeleton } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/ui\/skeleton\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">CoverSkeleton<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AspectRatio<\/span> <span class=\"hljs-attr\">ratio<\/span>=<span class=\"hljs-string\">{16<\/span> \/ <span class=\"hljs-attr\">9<\/span>}&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Skeleton<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full h-full\"<\/span> \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">AspectRatio<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Implementing the Smooth Fade-In<\/h3>\n<p>In the <code>BlogCard<\/code> component, you can manage the transition between the skeleton and the image using a simple state variable. By keeping the image in the DOM but hidden until it\u2019s ready, you\u2019ll achieve a seamless transition.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ Only show the skeleton if the image hasn't loaded<\/span>\n{!loaded &amp;&amp; <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CoverSkeleton<\/span> \/&gt;<\/span><\/span>}\n\n&lt;Image\n  src={coverUrl}\n  fill\n  className={loaded ? <span class=\"hljs-string\">\"opacity-100\"<\/span> : <span class=\"hljs-string\">\"opacity-0\"<\/span>}\n  onLoad={() =&gt; setLoaded(<span class=\"hljs-literal\">true<\/span>)} <span class=\"hljs-comment\">\/\/ Trigger the switch<\/span>\n\/&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>A smooth fade-in effect ensures:<\/p>\n<ul>\n<li>Users see a stable card layout immediately, which reduces perceived loading time.<\/li>\n<li>Zero layout shift, because the <code>CoverSkeleton<\/code> will match the <code>16:9<\/code> ratio of the rest of the blog card (title, excerpt, etc.).<\/li>\n<li>A more polished transition than the default browser loading behavior, thanks to fading the image in from <code>opacity-0<\/code> once <code>loaded<\/code> is true.<\/li>\n<\/ul>\n<h2>Optimize the First Blog Cover for LCP<\/h2>\n<p>The first image in your blog grid is the most critical for performance. As the largest visible element during initial page load, it directly impacts <strong>Largest Contentful Paint (LCP);<\/strong> a core metric the browser uses to measure how fast your page feels to a user.<\/p>\n<blockquote>\n<p>View the grid implementation here: <a href=\"https:\/\/github.com\/musebe\/blog-cover-manager\/blob\/main\/src\/components\/blog\/BlogGrid.tsx\"><code>src\/components\/blog\/BlogGrid.tsx<\/code><\/a><\/p>\n<\/blockquote>\n<h3>Implementing the Priority Prop<\/h3>\n<p>To optimize this, the <code>BlogGrid<\/code> component identifies the first post in the array (index <code>0<\/code>) and passes a <code>priority<\/code> prop only to that specific card.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">{posts.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">post, i<\/span>) =&gt;<\/span> (\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">BlogCard<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{post.id}<\/span> <span class=\"hljs-attr\">post<\/span>=<span class=\"hljs-string\">{post}<\/span> <span class=\"hljs-attr\">priority<\/span>=<span class=\"hljs-string\">{i<\/span> === <span class=\"hljs-string\">0}<\/span> \/&gt;<\/span><\/span>\n))}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The <code>BlogCard<\/code> then passes this value directly into the <code>next\/image<\/code> component:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{coverUrl}<\/span>\n  <span class=\"hljs-attr\">fill<\/span>\n  <span class=\"hljs-attr\">priority<\/span>=<span class=\"hljs-string\">{priority}<\/span>\n  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"object-cover\"<\/span>\n\/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This strategy works because:<\/p>\n<ul>\n<li>By setting <code>priority={true}<\/code> for the first image, you tell Next.js to treat it as a high-priority resource. It will be loaded immediately instead of being deferred.<\/li>\n<li>Only the first cover receives this treatment. The remaining images in the grid load lazily as the user scrolls, which saves bandwidth and keeps the initial load lightweight.<\/li>\n<li>This targeted optimization significantly reduces LCP time, ensuring your blog looks \u201cready\u201d to the user as quickly as possible.<\/li>\n<\/ul>\n<p>The takeaway is you don\u2019t need to force every image to load at once to have a fast site. By prioritizing just the first visible cover, you\u2019ll ensure a fast \u201cabove-the-fold\u201d experience while maintaining the efficiency of lazy loading for the rest of your content.<\/p>\n<h2>Test the Blog Cover Grid<\/h2>\n<p>With the logic and components in place, you can now test the full upload-to-grid workflow. This confirms that the automation is handling various image types correctly.<\/p>\n<blockquote>\n<p>View the live demo here: <a href=\"https:\/\/blog-cover-manager.vercel.app\/\">https:\/\/blog-cover-manager.vercel.app\/<\/a><\/p>\n<\/blockquote>\n<h3>Verification Steps<\/h3>\n<p>To see the system in action, try uploading several different image shapes: a portrait photo, wide banner, and square screenshot.<\/p>\n<p>Despite the different original dimensions, every item should appear as a perfectly aligned <strong>16:9 blog cover<\/strong> within the grid.<\/p>\n<h3>Inspecting the Automation<\/h3>\n<p>You can verify that the heavy lifting is happening on the fly by inspecting an image in your browser\u2019s developer tools. Look at the image URL; you should see the following transformation string:<\/p>\n<p><code>c_fill,ar_16:9,g_auto,w_800,q_auto,f_auto<\/code><\/p>\n<p>This string confirms that the cover isn\u2019t being manually edited or resized before upload. The original image is stored once, and Cloudinary generates the specific version required by the blog grid only when a browser requests that URL.<\/p>\n<h3>Cloudinary Transformation Rules<\/h3>\n<ul>\n<li>\n<strong>Store the <code>public_id<\/code>, not the full URL.<\/strong> Keeping only the ID ensures your data remains flexible if you decide to change transformation parameters later.<\/li>\n<li>\n<strong>Use <code>c_fill<\/code> for fixed layouts.<\/strong> This ensures every cover fits the designated space without distortion.<\/li>\n<li>\n<strong>Lock the aspect ratio with <code>ar_16:9<\/code>.<\/strong> This keeps all blog cards even and aligned in the grid.<\/li>\n<li>\n<strong>Enable <code>g_auto<\/code> (Auto Gravity).<\/strong> This allows Cloudinary\u2019s AI to keep the most important subject in frame during the crop.<\/li>\n<li>\n<strong>Optimize delivery with <code>q_auto<\/code> and <code>f_auto<\/code>.<\/strong> These handle compression and format selection automatically for faster page loads.<\/li>\n<\/ul>\n<h3>UI and Performance Standards<\/h3>\n<ul>\n<li>\n<strong>Implement loading skeletons.<\/strong> Using a placeholder that matches the 16:9 ratio prevents \u201cjanky\u201d layout shifts while images are downloading.<\/li>\n<li>\n<strong>Prioritize the first blog cover.<\/strong> Passing the <code>priority<\/code> prop to the first visible image improves your LCP (Largest Contentful Paint) score.<\/li>\n<li>\n<strong>Centralize logic in <code>buildCoverUrl<\/code>.<\/strong> Keeping transformation rules in one helper file makes the system easier to maintain and update.<\/li>\n<\/ul>\n<h3>The Result<\/h3>\n<p>By following these practices, your blog grid stays perfectly aligned regardless of the source material. Whether a contributor uploads a tall portrait, a square screenshot, or a wide banner, the system automatically delivers a uniform, high-quality <strong>16:9 cover<\/strong> every time.<\/p>\n<blockquote>\n<p>View the core logic reference in <a href=\"https:\/\/github.com\/musebe\/blog-cover-manager\/blob\/main\/src\/lib\/cloudinary.ts\"><strong><code>src\/lib\/cloudinary.ts<\/code><\/strong><\/a><\/p>\n<\/blockquote>\n<h2>Conclusion<\/h2>\n<p>Blog cover images can come in any shape, but your grid doesn\u2019t have to show that mess.<\/p>\n<p>In this project, you used Next.js and Cloudinary to turn mixed uploads into clean 16:9 blog covers. You built an admin upload page, stored the returned <code>public_id<\/code>, and used <code>buildCoverUrl<\/code> to apply the same transformation rules to every cover. Cloudinary handled the crop, aspect ratio, smart gravity, format, and quality. Next.js handled the page, card layout, image rendering, and loading behavior.<\/p>\n<p>The result is a blog grid that stays neat, even when the uploaded images are tall, wide, or square. Sign up for a <a href=\"https:\/\/cloudinary.com\/users\/register_free\">free Cloudinary account today<\/a> and start building your own project.<\/p>\n<ul>\n<li>\n<strong>Live Demo:<\/strong> <a href=\"https:\/\/blog-cover-manager.vercel.app\/\">https:\/\/blog-cover-manager.vercel.app\/<\/a>\n<\/li>\n<li>\n<strong>GitHub repo:<\/strong> <a href=\"https:\/\/github.com\/musebe\/blog-cover-manager\">https:\/\/github.com\/musebe\/blog-cover-manager<\/a>\n<\/li>\n<\/ul>\n<style>\n    .faqs {padding: 30px 60px; margin-top: 40px;background: var(--color-background-offset);border-radius: 20px;}\n    #frequently_asked_questions {margin-bottom: 20px;}\n    .question {margin-bottom: 20px;}\n<\/style>\n<div class=\"faqs\">\n<h2>Frequently Asked Questions<\/h2>\n<div class=\"question\">\n<p><b>Why should I store the Cloudinary public_id instead of the full URL?<\/b><\/p>\nStoring the public_id gives you maximum flexibility. If you decide to change your crop, aspect ratio, or image quality settings later, you only need to update your URL helper function instead of migrating every entry in your database.\n<\/div>\n<div class=\"question\">\n<p><b>What do c_fill and g_auto do for blog covers?<\/b><\/p>\nThese transformations work together to create uniform layouts. c_fill resizes the image to fill your specified frame without distortion. g_auto uses AI to detect the main subject and ensure it stays centered during the cropping process.\n<\/div>\n<div class=\"question\">\n<p><b>How do loading skeletons prevent layout shift?<\/b><\/p>\nSkeletons act as placeholders that match the exact 16:9 shape of your final images. They reserve the physical space on the page before the image finishes loading. This keeps your blog content stable and prevents the layout from jumping.\n<\/div>\n<div class=\"question\">\n<p><b>Why is the priority prop important for the first blog cover?<\/b><\/p>\nThe first image in your grid is often the Largest Contentful Paint (LCP) element. Using the priority prop tells Next.js to download that specific image immediately. This improves your site performance scores and makes the page feel faster to users.\n<\/div>\n<div class=\"question\">\n<p><b>Do I need to manually resize images before uploading them to my blog?<\/b><\/p>\nNo. Cloudinary handles all resizing and cropping dynamically through URL parameters. You can upload images of any original size or shape and the system will automatically transform them into the required 16:9 covers at delivery time.\n<\/div>\n<\/div>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":40065,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[165,373],"class_list":["post-40064","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-image-transformation","tag-upload"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Build a Next.js Blog Cover Uploader: 16:9 Cloudinary Guide<\/title>\n<meta name=\"description\" content=\"Learn how to build a consistent blog cover manager in Next.js. Use Cloudinary AI powered cropping and transformations to ensure a uniform 16:9 grid layout every time.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Standardize Your Blog Cover Images With Next.js and Cloudinary\" \/>\n<meta property=\"og:description\" content=\"Learn how to build a consistent blog cover manager in Next.js. Use Cloudinary AI powered cropping and transformations to ensure a uniform 16:9 grid layout every time.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-05-26T14:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Standardize Your Blog Cover Images With Next.js and Cloudinary\",\"datePublished\":\"2026-05-26T14:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js\"},\"wordCount\":10,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA\",\"keywords\":[\"Image Transformation\",\"Upload\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2026\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js\",\"url\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js\",\"name\":\"Build a Next.js Blog Cover Uploader: 16:9 Cloudinary Guide\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA\",\"datePublished\":\"2026-05-26T14:00:00+00:00\",\"description\":\"Learn how to build a consistent blog cover manager in Next.js. Use Cloudinary AI powered cropping and transformations to ensure a uniform 16:9 grid layout every time.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Standardize Your Blog Cover Images With Next.js and Cloudinary\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Build a Next.js Blog Cover Uploader: 16:9 Cloudinary Guide","description":"Learn how to build a consistent blog cover manager in Next.js. Use Cloudinary AI powered cropping and transformations to ensure a uniform 16:9 grid layout every time.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js","og_locale":"en_US","og_type":"article","og_title":"Standardize Your Blog Cover Images With Next.js and Cloudinary","og_description":"Learn how to build a consistent blog cover manager in Next.js. Use Cloudinary AI powered cropping and transformations to ensure a uniform 16:9 grid layout every time.","og_url":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js","og_site_name":"Cloudinary Blog","article_published_time":"2026-05-26T14:00:00+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Standardize Your Blog Cover Images With Next.js and Cloudinary","datePublished":"2026-05-26T14:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js"},"wordCount":10,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA","keywords":["Image Transformation","Upload"],"inLanguage":"en-US","copyrightYear":"2026","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js","url":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js","name":"Build a Next.js Blog Cover Uploader: 16:9 Cloudinary Guide","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA","datePublished":"2026-05-26T14:00:00+00:00","description":"Learn how to build a consistent blog cover manager in Next.js. Use Cloudinary AI powered cropping and transformations to ensure a uniform 16:9 grid layout every time.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/blog-cover-images-next-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Standardize Your Blog Cover Images With Next.js and Cloudinary"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1779312766\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary\/Blog_Build_a_Next.js_Blog_Cover_Image_Uploader_with_Cloudinary.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/40064","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=40064"}],"version-history":[{"count":2,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/40064\/revisions"}],"predecessor-version":[{"id":40067,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/40064\/revisions\/40067"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/40065"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=40064"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=40064"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=40064"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}