{"id":40084,"date":"2026-06-11T07:00:00","date_gmt":"2026-06-11T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=40084"},"modified":"2026-06-16T10:30:09","modified_gmt":"2026-06-16T17:30:09","slug":"nextjs-secure-image-uploads","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads","title":{"rendered":"Secure Image Uploads in Next.js With Signed Uploads and Cloudinary"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Stop exposing your API Secret. Here\u2019s how to do image uploads the right way.<\/p>\n<ul>\n<li>\n<strong>Live demo:<\/strong> <a href=\"https:\/\/nextjs-secure-image-uploads.vercel.app\/\">nextjs-secure-image-uploads.vercel.app<\/a>\n<\/li>\n<li>\n<strong>Full source:<\/strong> <a href=\"https:\/\/github.com\/musebe\/nextjs-secure-image-uploads\">github.com\/musebe\/nextjs-secure-image-uploads<\/a>\n<\/li>\n<\/ul>\n<h2>The Problem Every Developer Runs Into<\/h2>\n<p>Production apps need to prove that an upload request came from your app, not a third party. The way to do that with Cloudinary is a <strong>server-generated signature<\/strong>. Your server signs the upload parameters with your API Secret before the browser sends the file to Cloudinary.<\/p>\n<p>This guide walks you through that pattern end to end. You\u2019ll build a <strong>production-ready<\/strong> secure upload system where:<\/p>\n<ul>\n<li>Your <code>CLOUDINARY_API_SECRET<\/code> <strong>never touches the browser<\/strong>.<\/li>\n<li>Every upload is authenticated with a <strong>server-side SHA-256 signature<\/strong>.<\/li>\n<li>A live gallery fetches images directly from Cloudinary.<\/li>\n<li>Images are transformed, optimised, and delivered at CDN speed.<\/li>\n<\/ul>\n<p>No prior Cloudinary experience needed.<\/p>\n<h2>What We\u2019re Building<\/h2>\n<pre class=\"js-syntax-highlighted\"><code>Signed upload flow\n\n1. Browser requests a signature.\n   Request: POST \/api\/sign\n\n2. Next.js signs the upload parameters.\n   The API Secret stays on the server.\n\n3. Browser receives the signature.\n   Response: { signature, apiKey }\n\n4. Browser uploads the image to Cloudinary.\n   The image goes directly to Cloudinary's CDN.\n\n5. Cloudinary verifies the signature.\n   If valid, Cloudinary stores the image.\n\nResult:\nYour server signs the request, but it never handles the image file.\n<\/code><\/pre>\n<p>The server signs. The browser uploads directly to Cloudinary\u2019s CDN. <strong>Your server is never in the file-transfer path<\/strong>, so it stays fast even under load.<\/p>\n<h2>Step 1: Create Your Next.js App<\/h2>\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> nextjs-secure-image-uploads \\\n  --typescript \\\n  --tailwind \\\n  --app \\\n  --src-dir\n\ncd nextjs-secure-image-uploads\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>Install the packages you need:<\/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-selector-tag\">cloudinary<\/span> <span class=\"hljs-selector-tag\">next-cloudinary<\/span> <span class=\"hljs-selector-tag\">sonner<\/span>\n<span class=\"hljs-selector-tag\">npx<\/span> <span class=\"hljs-selector-tag\">shadcn<\/span><span class=\"hljs-keyword\">@latest<\/span> init\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<ul>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/node_integration\"><strong>cloudinary<\/strong><\/a>, Node SDK for server-side signing, and the Admin API.<\/li>\n<li>\n<a href=\"https:\/\/next.cloudinary.dev\/\"><strong>next-cloudinary<\/strong><\/a>, React components like <code>CldUploadWidget<\/code> and <code>CldImage<\/code> built for Next.js.<\/li>\n<li>\n<a href=\"https:\/\/sonner.emilkowal.ski\/\"><strong>sonner<\/strong><\/a>, toast notifications.<\/li>\n<\/ul>\n<h2>Step 2: Create a Cloudinary Account and Upload Preset<\/h2>\n<blockquote>\n<p><strong>New to Cloudinary?<\/strong> <a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up free<\/a>, 25 monthly credits.<\/p>\n<\/blockquote>\n<h3>Create an Upload Preset<\/h3>\n<p>An upload preset is a saved set of upload rules. It can define the folder, transformations, and allowed formats.<\/p>\n<p>Setting it to <strong>Signed<\/strong> means Cloudinary will reject any upload that does not carry a valid server-generated signature.<\/p>\n<ol>\n<li>Log in to the <a href=\"https:\/\/console.cloudinary.com\/\">Cloudinary Console<\/a>.<\/li>\n<li>Click <strong>Settings<\/strong> (\u2699\ufe0f) \u2192 <strong>Upload<\/strong> \u2192 <strong>Upload presets<\/strong>.<\/li>\n<li>Click <strong>Add upload preset<\/strong>.<\/li>\n<li>Fill in:<\/li>\n<\/ol>\n<figure class=\"table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Preset name<\/td>\n<td><code>nextjs-secure-uploads<\/code><\/td>\n<\/tr>\n<tr>\n<td>Signing mode<\/td>\n<td><strong>Signed<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Folder<\/td>\n<td><code>nextjs-secure-demo<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n<p>Click <strong>Save<\/strong>.<\/p>\n<h3>Get Your API Credentials<\/h3>\n<p>Go to <strong>Dashboard<\/strong>, then copy your <strong>Cloud name<\/strong>, <strong>API key<\/strong>, and <strong>API secret<\/strong>.<\/p>\n<h2>Step 3: Environment Variables<\/h2>\n<p>Create <code>.env.local<\/code> at the project root.<\/p>\n<p><strong>Never commit this file.<\/strong><\/p>\n<pre class=\"js-syntax-highlighted\"><code>NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name\nNEXT_PUBLIC_CLOUDINARY_API_KEY=your_api_key\nCLOUDINARY_API_SECRET=your_api_secret\nNEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET=nextjs-secure-uploads\n<\/code><\/pre>\n<p>The rule is simple. Variables prefixed with <code>NEXT_PUBLIC_<\/code> are safe to expose because they end up in browser JavaScript.<\/p>\n<p><code>CLOUDINARY_API_SECRET<\/code> has no prefix, so Next.js keeps it server-only.<\/p>\n<h2>Step 4: The Signing Engine: <code>\/api\/sign<\/code><\/h2>\n<p>This is the heart of the whole system. One small Route Handler keeps your secret safe.<\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-secure-image-uploads\/blob\/main\/src\/app\/api\/sign\/route.ts\"><code>src\/app\/api\/sign\/route.ts<\/code><\/a><\/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> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">POST<\/span>(<span class=\"hljs-params\">request: Request<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { paramsToSign } = <span class=\"hljs-keyword\">await<\/span> request.json();\n\n  <span class=\"hljs-keyword\">const<\/span> signature = cloudinary.utils.api_sign_request(\n    paramsToSign,\n    process.env.CLOUDINARY_API_SECRET!\n  );\n\n  <span class=\"hljs-keyword\">return<\/span> Response.json({\n    signature,\n    <span class=\"hljs-attr\">apiKey<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,\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>That\u2019s it. The widget calls this endpoint. The server signs the parameters, then sends the signature back to the browser.<\/p>\n<p>The secret stays on the server, always.<\/p>\n<h2>Step 5: The Upload Widget<\/h2>\n<p><code>CldUploadWidget<\/code> from <code>next-cloudinary<\/code> handles the upload UI. You just tell it where to get a signature.<\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-secure-image-uploads\/blob\/main\/src\/components\/SecureUpload.tsx\"><code>src\/components\/SecureUpload.tsx<\/code><\/a><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">import { CldUploadWidget } from <span class=\"hljs-string\">\"next-cloudinary\"<\/span>;\n\n&lt;CldUploadWidget\n  signatureEndpoint=<span class=\"hljs-string\">\"\/api\/sign\"<\/span>\n  uploadPreset={process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET}\n  options={{\n    sources: &#91;<span class=\"hljs-string\">\"local\"<\/span>, <span class=\"hljs-string\">\"url\"<\/span>, <span class=\"hljs-string\">\"camera\"<\/span>],\n    multiple: <span class=\"hljs-keyword\">true<\/span>,\n    maxFileSize: <span class=\"hljs-number\">10<\/span>_000_000,\n    clientAllowedFormats: &#91;<span class=\"hljs-string\">\"jpg\"<\/span>, <span class=\"hljs-string\">\"png\"<\/span>, <span class=\"hljs-string\">\"webp\"<\/span>, <span class=\"hljs-string\">\"avif\"<\/span>],\n  }}\n  onSuccess={(results) =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> info = results.info;\n\n    <span class=\"hljs-comment\">\/\/ info.public_id<\/span>\n    <span class=\"hljs-comment\">\/\/ info.secure_url<\/span>\n    <span class=\"hljs-comment\">\/\/ info.width<\/span>\n    <span class=\"hljs-comment\">\/\/ info.height<\/span>\n  }}\n&gt;\n  {({ open }) =&gt; (\n    &lt;button type=<span class=\"hljs-string\">\"button\"<\/span> onClick={() =&gt; open()}&gt;\n      Upload Image\n    &lt;\/button&gt;\n  )}\n&lt;\/CldUploadWidget&gt;;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The widget automatically calls <code>\/api\/sign<\/code> before each upload. If the signature is missing or wrong, Cloudinary refuses the file.<\/p>\n<h2>Step 6: Fetch Your Gallery with the Admin API<\/h2>\n<p>Once images are in Cloudinary, you can search them server-side using the <a href=\"https:\/\/cloudinary.com\/documentation\/search_api\">Admin Search API<\/a>.<\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-secure-image-uploads\/blob\/main\/src\/app\/api\/gallery\/route.ts\"><code>src\/app\/api\/gallery\/route.ts<\/code><\/a><\/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\"><span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n  .expression(<span class=\"hljs-string\">\"folder=nextjs-secure-demo\"<\/span>)\n  .sort_by(<span class=\"hljs-string\">\"created_at\"<\/span>, <span class=\"hljs-string\">\"desc\"<\/span>)\n  .with_field(<span class=\"hljs-string\">\"tags\"<\/span>)\n  .max_results(<span class=\"hljs-number\">100<\/span>)\n  .execute();\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>This returns every image in your folder with full metadata, including dimensions, format, file size, tags, and public ID.<\/p>\n<p>Your gallery component maps over <code>result.resources<\/code> and passes them to <code>CldImage<\/code>.<\/p>\n<h2>Step 7: Display Images with <code>CldImage<\/code><\/h2>\n<p><a href=\"https:\/\/next.cloudinary.dev\/cldimage\/basic-usage\"><code>CldImage<\/code><\/a> is a drop-in replacement for Next.js <code>&lt;Image&gt;<\/code>. It works with Cloudinary\u2019s transformation URL syntax.<\/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-keyword\">import<\/span> { CldImage } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-cloudinary\"<\/span>;\n\n<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"nextjs-secure-demo\/my-photo\"<\/span>\n  <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{800}<\/span>\n  <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{600}<\/span>\n  <span class=\"hljs-attr\">format<\/span>=<span class=\"hljs-string\">\"auto\"<\/span>\n  <span class=\"hljs-attr\">quality<\/span>=<span class=\"hljs-string\">\"auto\"<\/span>\n  <span class=\"hljs-attr\">crop<\/span>=<span class=\"hljs-string\">{{<\/span>\n    <span class=\"hljs-attr\">type:<\/span> \"<span class=\"hljs-attr\">fill<\/span>\",\n    <span class=\"hljs-attr\">gravity:<\/span> \"<span class=\"hljs-attr\">auto<\/span>\",\n  }}\n  <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"My photo\"<\/span>\n\/&gt;<\/span><\/span>;\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>Here\u2019s what those values do:<\/p>\n<ul>\n<li>\n<code>src<\/code> uses the Cloudinary public ID.<\/li>\n<li>\n<code>format=&quot;auto&quot;<\/code> lets Cloudinary pick WebP, AVIF, or another best format.<\/li>\n<li>\n<code>quality=&quot;auto&quot;<\/code> balances file size and visual quality.<\/li>\n<li>\n<code>gravity=&quot;auto&quot;<\/code> uses smart crop to focus on the key part of the image.<\/li>\n<\/ul>\n<p><code>format=&quot;auto&quot;<\/code> and <code>quality=&quot;auto&quot;<\/code> alone can cut image weight by <strong>50 to 80%<\/strong> with no visual difference. The image is delivered from the nearest Cloudinary CDN edge.<\/p>\n<h2>The Real Power: Cloudinary Transformations<\/h2>\n<p>Every transformation happens on Cloudinary\u2019s CDN. No image-processing libraries in your app. No extra CPU on your server.<\/p>\n<p>You only change URL parameters.<\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/nextjs-secure-image-uploads\/blob\/main\/src\/components\/CloudinaryGallery.tsx\"><code>src\/components\/CloudinaryGallery.tsx<\/code><\/a><\/p>\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\">import<\/span> { getCldImageUrl } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-cloudinary\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/ Grayscale<\/span>\ngetCldImageUrl({\n  <span class=\"hljs-attr\">src<\/span>: publicId,\n  <span class=\"hljs-attr\">effects<\/span>: &#91;{ <span class=\"hljs-attr\">grayscale<\/span>: <span class=\"hljs-literal\">true<\/span> }],\n});\n\n<span class=\"hljs-comment\">\/\/ Sepia<\/span>\ngetCldImageUrl({\n  <span class=\"hljs-attr\">src<\/span>: publicId,\n  <span class=\"hljs-attr\">effects<\/span>: &#91;{ <span class=\"hljs-attr\">sepia<\/span>: <span class=\"hljs-string\">\"60\"<\/span> }],\n});\n\n<span class=\"hljs-comment\">\/\/ Auto-enhance: brightness, contrast, and saturation<\/span>\ngetCldImageUrl({\n  <span class=\"hljs-attr\">src<\/span>: publicId,\n  <span class=\"hljs-attr\">effects<\/span>: &#91;{ <span class=\"hljs-attr\">improve<\/span>: <span class=\"hljs-literal\">true<\/span> }],\n});\n\n<span class=\"hljs-comment\">\/\/ Sharpen<\/span>\ngetCldImageUrl({\n  <span class=\"hljs-attr\">src<\/span>: publicId,\n  <span class=\"hljs-attr\">effects<\/span>: &#91;{ <span class=\"hljs-attr\">sharpen<\/span>: <span class=\"hljs-string\">\"80\"<\/span> }],\n});\n\n<span class=\"hljs-comment\">\/\/ Vignette<\/span>\ngetCldImageUrl({\n  <span class=\"hljs-attr\">src<\/span>: publicId,\n  <span class=\"hljs-attr\">effects<\/span>: &#91;{ <span class=\"hljs-attr\">vignette<\/span>: <span class=\"hljs-string\">\"50\"<\/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<p>The demo app exposes these as one-click presets in the gallery lightbox. You can swap effects at runtime with zero re-uploads.<\/p>\n<h2>Bonus: Cloudinary Add-ons<\/h2>\n<p>Cloudinary\u2019s <a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_add_ons\">add-on marketplace<\/a> plugs AI and third-party services directly into your media pipeline.<\/p>\n<figure class=\"table-wrapper\"><table>\n<thead>\n<tr>\n<th>Add-on<\/th>\n<th>What it does<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_ai_background_removal_addon\">Cloudinary AI Background Removal<\/a><\/td>\n<td>Remove backgrounds in one URL parameter<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/cloudinary.com\/documentation\/google_auto_tagging_addon\">Google Auto Tagging<\/a><\/td>\n<td>Auto-tag images using Google Vision AI<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/cloudinary.com\/documentation\/aws_rekognition_ai_moderation_addon\">Amazon Rekognition<\/a><\/td>\n<td>Moderate uploads for explicit content<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/cloudinary.com\/documentation\/cloudinary_ai_upscale_addon\">Upscale<\/a><\/td>\n<td>4\u00d7 upscale with AI super-resolution<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/cloudinary.com\/documentation\/viesus_automatic_image_enhancement_addon\">Viesus Auto Enhance<\/a><\/td>\n<td>Automatic professional-grade photo enhancement<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n<p>Enable them in your Cloudinary console. Then call them via URL:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ Background removal, requires add-on<\/span>\ngetCldImageUrl({\n  <span class=\"hljs-attr\">src<\/span>: publicId,\n  <span class=\"hljs-attr\">removeBackground<\/span>: <span class=\"hljs-literal\">true<\/span>,\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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>No new SDK. No extra API call. Just a URL.<\/p>\n<h2>Bonus: Video Uploads Work the Same Way<\/h2>\n<p>The exact same signed upload pattern works for <strong>video<\/strong>. Change one option:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">&lt;CldUploadWidget\n  signatureEndpoint=<span class=\"hljs-string\">\"\/api\/sign\"<\/span>\n  uploadPreset=<span class=\"hljs-string\">\"your-video-preset\"<\/span>\n  options={{\n    resourceType: <span class=\"hljs-string\">\"video\"<\/span>,\n    sources: &#91;<span class=\"hljs-string\">\"local\"<\/span>, <span class=\"hljs-string\">\"camera\"<\/span>, <span class=\"hljs-string\">\"url\"<\/span>],\n  }}\n&gt;\n  ...\n&lt;\/CldUploadWidget&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Display the uploaded video with <a href=\"https:\/\/next.cloudinary.dev\/cldvideoplayer\/basic-usage\"><code>CldVideoPlayer<\/code><\/a>. You get adaptive streaming, subtitles, and Cloudinary\u2019s transformation suite.<\/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-keyword\">import<\/span> { CldVideoPlayer } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-cloudinary\"<\/span>;\n\n<span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldVideoPlayer<\/span>\n  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"your-video-public-id\"<\/span>\n  <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{1280}<\/span>\n  <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{720}<\/span>\n  <span class=\"hljs-attr\">transformation<\/span>=<span class=\"hljs-string\">{{<\/span>\n    <span class=\"hljs-attr\">quality:<\/span> \"<span class=\"hljs-attr\">auto<\/span>\",\n    <span class=\"hljs-attr\">fetch_format:<\/span> \"<span class=\"hljs-attr\">auto<\/span>\",\n  }}\n\/&gt;<\/span><\/span>;\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<h2>Deployment to Vercel<\/h2>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install -g vercel\nvercel\n<\/code><\/span><\/pre>\n<p>In your Vercel project, go to <strong>Settings \u2192 Environment Variables<\/strong>. Add the same four variables from your <code>.env.local<\/code>.<\/p>\n<p>Mark <code>CLOUDINARY_API_SECRET<\/code> as sensitive.<\/p>\n<p>That is your only production checklist item for security. The signing endpoint and secret handling are already correct.<\/p>\n<h2>What You\u2019ll Have Now<\/h2>\n<p>By following this guide, you\u2019ll have:<\/p>\n<ul>\n<li>A <strong>signed upload flow<\/strong>, no public presets and no exposed secrets.<\/li>\n<li>A <strong>server-side gallery<\/strong>, powered by Cloudinary\u2019s Admin Search API.<\/li>\n<li>\n<strong>On-the-fly transformations<\/strong>, format, quality, and effects through URLs.<\/li>\n<li>A UI built with <strong>shadcn\/ui + Tailwind v4<\/strong> that works in dark mode.<\/li>\n<\/ul>\n<p>This isn\u2019t a prototype pattern. This is how production apps should handle media from day one.<\/p>\n<p>Ready to start building with Cloudinary? <a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up<\/a> for a free account today.<\/p>\n<ul>\n<li>\n<strong>Full source code:<\/strong> <a href=\"https:\/\/github.com\/musebe\/nextjs-secure-image-uploads\">github.com\/musebe\/nextjs-secure-image-uploads<\/a>\n<\/li>\n<\/ul>\n<p><strong>Live demo:<\/strong> <a href=\"https:\/\/nextjs-secure-image-uploads.vercel.app\/\">nextjs-secure-image-uploads.vercel.app<\/a><\/p>\n<h2>Further Reading<\/h2>\n<ul>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/upload_images#authenticated_requests\">Cloudinary Signed Uploads documentation<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/next.cloudinary.dev\/clduploadwidget\/basic-usage\">next-cloudinary CldUploadWidget<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/transformation_reference\">Cloudinary Transformation reference<\/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 is it dangerous to perform unsigned uploads on production websites?<\/b><\/p>\nUnsigned uploads expose your upload presets directly to the client, which can allow unauthorized users to upload random files to your cloud storage. Production applications should always require server-side signatures to validate and restrict upload privileges securely.\n<\/div>\n<div class=\"question\">\n<p><b>How does the server-side signing flow keep the Cloudinary API secret safe?<\/b><\/p>\nThe private API secret remains solely on your secure backend environment. When a client initiates an upload, the browser requests a temporary cryptographic signature from a Next.js API route, which signs the upload parameters using the secret without ever exposing the key to the client.\n<\/div>\n<div class=\"question\">\n<p><b>What are the performance benefits of uploading directly to Cloudinary instead of proxying through a Next.js server?<\/b><\/p>\nUploading files directly to Cloudinary offloads the high-bandwidth file transfer workload from your server to a global delivery network. Your server only processes a lightweight cryptographic signature, which keeps your server responsive and fast even under heavy concurrent traffic.\n<\/div>\n<div class=\"question\">\n<p><b>How do Cloudinary transformations improve Next.js page performance?<\/b><\/p>\nYou can utilize next-cloudinary components to automatically compress quality and serve the most efficient modern format like AVIF or WebP on the fly. This dynamic optimization reduces payload sizes by 50% to 80% without any visible loss in visual quality.\n<\/div>\n<div class=\"question\">\n<p><b>Can I use the exact same signed upload architecture for video assets?<\/b><\/p>\nYes. The signing API endpoint functions identically for videos. You only need to configure the client-side upload widget to handle video resource types and render the resulting upload with a dedicated video player component.\n<\/div>\n<\/div>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":40087,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[212,264,373],"class_list":["post-40084","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-next-js","tag-security","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>Secure Next.js Image Uploads: Signed Cloudinary Guide<\/title>\n<meta name=\"description\" content=\"Learn how to build a secure image upload system in Next.js. Protect your private API secret with server-side signatures while transferring files directly to the Cloudinary CDN.\" \/>\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\/nextjs-secure-image-uploads\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Secure Image Uploads in Next.js With Signed Uploads and Cloudinary\" \/>\n<meta property=\"og:description\" content=\"Learn how to build a secure image upload system in Next.js. Protect your private API secret with server-side signatures while transferring files directly to the Cloudinary CDN.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-11T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-16T17:30:09+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"4000\" \/>\n\t<meta property=\"og:image:height\" content=\"2200\" \/>\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\/nextjs-secure-image-uploads#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Secure Image Uploads in Next.js With Signed Uploads and Cloudinary\",\"datePublished\":\"2026-06-11T14:00:00+00:00\",\"dateModified\":\"2026-06-16T17:30:09+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads\"},\"wordCount\":11,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA\",\"keywords\":[\"Next.js\",\"Security\",\"Upload\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2026\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads\",\"url\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads\",\"name\":\"Secure Next.js Image Uploads: Signed Cloudinary Guide\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA\",\"datePublished\":\"2026-06-11T14:00:00+00:00\",\"dateModified\":\"2026-06-16T17:30:09+00:00\",\"description\":\"Learn how to build a secure image upload system in Next.js. Protect your private API secret with server-side signatures while transferring files directly to the Cloudinary CDN.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA\",\"width\":4000,\"height\":2200},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Secure Image Uploads in Next.js With Signed Uploads 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":"Secure Next.js Image Uploads: Signed Cloudinary Guide","description":"Learn how to build a secure image upload system in Next.js. Protect your private API secret with server-side signatures while transferring files directly to the Cloudinary CDN.","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\/nextjs-secure-image-uploads","og_locale":"en_US","og_type":"article","og_title":"Secure Image Uploads in Next.js With Signed Uploads and Cloudinary","og_description":"Learn how to build a secure image upload system in Next.js. Protect your private API secret with server-side signatures while transferring files directly to the Cloudinary CDN.","og_url":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads","og_site_name":"Cloudinary Blog","article_published_time":"2026-06-11T14:00:00+00:00","article_modified_time":"2026-06-16T17:30:09+00:00","og_image":[{"width":4000,"height":2200,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_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\/nextjs-secure-image-uploads#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Secure Image Uploads in Next.js With Signed Uploads and Cloudinary","datePublished":"2026-06-11T14:00:00+00:00","dateModified":"2026-06-16T17:30:09+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads"},"wordCount":11,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA","keywords":["Next.js","Security","Upload"],"inLanguage":"en-US","copyrightYear":"2026","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads","url":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads","name":"Secure Next.js Image Uploads: Signed Cloudinary Guide","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA","datePublished":"2026-06-11T14:00:00+00:00","dateModified":"2026-06-16T17:30:09+00:00","description":"Learn how to build a secure image upload system in Next.js. Protect your private API secret with server-side signatures while transferring files directly to the Cloudinary CDN.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA","width":4000,"height":2200},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Secure Image Uploads in Next.js With Signed Uploads 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"}}]}},"parsely":{"version":"1.1.0","canonical_url":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads","smart_links":{"inbound":0,"outbound":0},"traffic_boost_suggestions_count":0,"meta":{"@context":"https:\/\/schema.org","@type":"NewsArticle","headline":"Secure Image Uploads in Next.js With Signed Uploads and Cloudinary","url":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads","mainEntityOfPage":{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA&w=150&h=150&crop=1","image":{"@type":"ImageObject","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA"},"articleSection":"Uncategorized","author":[{"@type":"Person","name":"melindapham"}],"creator":["melindapham"],"publisher":{"@type":"Organization","name":"Cloudinary Blog","logo":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA"},"keywords":["next.js","security","upload"],"dateCreated":"2026-06-11T14:00:00Z","datePublished":"2026-06-11T14:00:00Z","dateModified":"2026-06-16T17:30:09Z"},"rendered":"<meta name=\"parsely-title\" content=\"Secure Image Uploads in Next.js With Signed Uploads and Cloudinary\" \/>\n<meta name=\"parsely-link\" content=\"https:\/\/cloudinary.com\/blog\/nextjs-secure-image-uploads\" \/>\n<meta name=\"parsely-type\" content=\"post\" \/>\n<meta name=\"parsely-image-url\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA&w=150&amp;h=150&amp;crop=1\" \/>\n<meta name=\"parsely-pub-date\" content=\"2026-06-11T14:00:00Z\" \/>\n<meta name=\"parsely-section\" content=\"Uncategorized\" \/>\n<meta name=\"parsely-tags\" content=\"next.js,security,upload\" \/>\n<meta name=\"parsely-author\" content=\"melindapham\" \/>","tracker_url":"https:\/\/cdn.parsely.com\/keys\/cloudinary.com\/p.js"},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1781121970\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary\/Blog_Secure_Image_Uploads_in_Next.js_with_Signed_Uploads_and_Cloudinary.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/40084","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=40084"}],"version-history":[{"count":2,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/40084\/revisions"}],"predecessor-version":[{"id":40115,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/40084\/revisions\/40115"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/40087"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=40084"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=40084"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=40084"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}