{"id":33806,"date":"2024-05-21T07:00:00","date_gmt":"2024-05-21T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=33806"},"modified":"2025-10-16T11:37:12","modified_gmt":"2025-10-16T18:37:12","slug":"cloudinary-image-uploads-using-nextjs-app-router","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router","title":{"rendered":"Cloudinary Image Uploads Using NextJS App Router"},"content":{"rendered":"\n<p>If you\u2019ve ever added file uploading to a web application, you know it can be a tricky endeavor. There\u2019s a lot of work involved with providing a good user experience for uploading images on both the backend and frontend of a web application. Thankfully, along with all the other great features for optimizing your images, this is something that Cloudinary APIs and client libraries simplify. The Cloudinary service covers the backend part and the <a href=\"https:\/\/next.cloudinary.dev\/\">Next Cloudinary<\/a> package includes widgets to handle the client bits.<\/p>\n\n\n\n<p>Likely you are reading this article because you are currently a Cloudinary user, but if not, you can <a href=\"https:\/\/cloudinary.com\/users\/register_free\">sign up for Cloudinary<\/a> and get started with a free forever account. You\u2019ll discover how Cloudinary makes everything you do to create beautiful web pages easier, and with your free account, you can follow along with this guide and get moving right away with the Cloudinary NextJS image upload process for your project.<\/p>\n\n\n\n<p>In this blog post, we\u2019ll build a simple avatar image uploader component in a web app powered by the<a href=\"https:\/\/nextjs.org\/\"> NextJS App Router<\/a> with<a href=\"https:\/\/nextjs.org\/docs\/app\/building-your-application\/rendering\/server-components\"> React Server Components<\/a>. Cloudinary will do most of the heavy lifting, but we\u2019ll still have some integration work to do in our server-rendered React app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Getting Started<\/strong><\/h2>\n\n\n\n<p>There are a few things we\u2019ll need to create our demo avatar uploader:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A<a href=\"https:\/\/cloudinary.com\/users\/register_free\"> Cloudinary account<\/a>.<\/li>\n\n\n\n<li>NextJS App Router app.<\/li>\n\n\n\n<li><a href=\"https:\/\/next.cloudinary.dev\/\">next-cloudinary<\/a> and <a href=\"https:\/\/cloudinary.com\/documentation\/node_integration\">cloudinary<\/a> packages.<\/li>\n<\/ul>\n\n\n\n<p>Once we have our Cloudinary account, we can generate our NextJS app.<\/p>\n\n\n<pre class=\"wp-block-code\" 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><\/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\n\n<p>You\u2019ll be prompted to choose some options for your new application. We need to provide a name and enable the App Router. I\u2019ve also enabled TypeScript and Tailwind for this demo app.Once our app is generated, we can install next-cloudinary and cloudinary packages.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">cd avatar-app\nnpm i next-cloudinary cloudinary<\/code><\/span><\/pre>\n\n\n<p>To render our avatar images uploaded to Cloudinary NextJS, we\u2019ll need to\u00a0<a href=\"https:\/\/nextjs.org\/docs\/pages\/api-reference\/components\/image#domains\">whitelist the host location<\/a>\u00a0of our image files in our next.config.mjs config file. This is required for any hostnames for images that we want to render with the next Image component.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/** <span class=\"hljs-doctag\">@type <span class=\"hljs-type\">{import('next').NextConfig}<\/span> <\/span>*\/<\/span>\n<span class=\"hljs-keyword\">const<\/span> nextConfig = {\n  <span class=\"hljs-attr\">images<\/span>: {\n    <span class=\"hljs-attr\">remotePatterns<\/span>: &#91;\n      {\n        <span class=\"hljs-attr\">protocol<\/span>: <span class=\"hljs-string\">\"https\"<\/span>,\n        <span class=\"hljs-attr\">hostname<\/span>: <span class=\"hljs-string\">\"res.cloudinary.com\"<\/span>,\n      },\n    ],\n  },\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> nextConfig;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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\n\n<p>Next, we\u2019ll need to create a <code>.env<\/code> file in our project root to add some environment variables. We have to navigate to a few different places in our Cloudinary account to collect all these.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>You can find the cloud name on the <strong>Getting Started<\/strong> page in the dashboard.<\/li>\n\n\n\n<li>We can get an upload preset in the <strong>Settings <\/strong>&gt;<strong> Upload<\/strong> page of the dashboard.<\/li>\n\n\n\n<li>Finally, we can get our API_KEY and API_SECRET in <strong>Settings <\/strong>&gt;<strong> Access Keys<\/strong>.<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># These are fake values<\/span>\nNEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=abc123xyz\nNEXT_PUBLIC_UPLOAD_PRESET=ml_default\nCLOUDINARY_API_KEY=<span class=\"hljs-number\">568392749283619<\/span>\nCLOUDINARY_API_SECRET=<span class=\"hljs-number\">7<\/span>HJKhR1MjKXPqhe9XjdFM1yxZZX<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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\n\n<p>The <code>cloudinary<\/code> package, <code>API_KEY<\/code>, and <code>API_SECRET<\/code> are specifically for creating signing keys to secure our image and happen in our backend.<\/p>\n\n\n\n<p>With all these pieces in place, we\u2019re good to move forward.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Creating a Fake Database<\/strong><\/h2>\n\n\n\n<p>For the purposes of our demo, let\u2019s imagine that we have an auth system and database from which we load the current user. On a NextJS development server, we can just use a global variable as a mock database since it runs on a stateful Node server. In the src directory, I\u2019ve created a fake database for the application to use.<\/p>\n\n\n<pre class=\"wp-block-code\" 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-comment\">\/\/ db.ts<\/span>\n\ndeclare global {\n  <span class=\"hljs-keyword\">var<\/span> DB: { <span class=\"hljs-attr\">currentUser<\/span>: User };\n}\n\n<span class=\"hljs-keyword\">export<\/span> interface User {\n  <span class=\"hljs-attr\">name<\/span>: string;\n  email: string;\n  avatar?: string;\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> DB = (global.DB = {\n  <span class=\"hljs-attr\">currentUser<\/span>: {\n    <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"John Smith\"<\/span>,\n  <span class=\"hljs-attr\">email<\/span>: <span class=\"hljs-string\">\"jsmith@email.com\"<\/span>,\n    <span class=\"hljs-attr\">avatar<\/span>: <span class=\"hljs-literal\">undefined<\/span>,\n  },\n});\n\n<span class=\"hljs-keyword\">const<\/span> delay = <span class=\"hljs-function\">(<span class=\"hljs-params\">ms: number = <span class=\"hljs-number\">80<\/span><\/span>) =&gt;<\/span>\n  <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve<\/span>) =&gt;<\/span> setTimeout(resolve, ms));\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\">getCurrentUser<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">await<\/span> delay();\n  <span class=\"hljs-keyword\">return<\/span> DB.currentUser;\n}\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\">updateUser<\/span>(<span class=\"hljs-params\">user: Partial&lt;User&gt;<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">await<\/span> delay();\n  DB.currentUser = <span class=\"hljs-built_in\">Object<\/span>.assign({}, DB.currentUser, user);\n}\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\n\n<p>We have async functions with an artificial delay for getting the current user and updating the user object.<\/p>\n\n\n\n<p>Our app has only a home page. The home page (<code>page.tsx \/<\/code>) is a React Server Component. To integrate our avatar uploader we\u2019ll create a client component at <code>components\/avatar-uploader.tsx<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Avatar Upload Widget<\/strong><\/h2>\n\n\n\n<p>We\u2019ll work backwards and start with the AvatarUploader component. The first line of our file declares that this component is a client component which means it\u2019s the traditional type of component that we\u2019re used to using in React. We can have state, effects, and access browser APIs. Because most existing packages in the React ecosystem weren\u2019t built for server components, we have to be mindful about whether or not they\u2019re using client-only APIs. If they are, we might have to wrap them in a file that declares use client at the top.<\/p>\n\n\n<pre class=\"wp-block-code\" 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-comment\">\/\/ components\/avatar-uploader.tsx<\/span>\n<span class=\"hljs-string\">\"use client\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { CldUploadWidget } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-cloudinary\"<\/span>;\n\ninterface AvatarUploaderProps {\n  <span class=\"hljs-attr\">onUploadSuccess<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">url: string<\/span>) =&gt;<\/span> <span class=\"hljs-keyword\">void<\/span>;\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">AvatarUploader<\/span>(<span class=\"hljs-params\">{ onUploadSuccess }: AvatarUploaderProps<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldUploadWidget<\/span>\n      <span class=\"hljs-attr\">uploadPreset<\/span>=<span class=\"hljs-string\">{process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET}<\/span>\n      <span class=\"hljs-attr\">signatureEndpoint<\/span>=<span class=\"hljs-string\">\"\/api\/sign-cloudinary-params\"<\/span>\n      <span class=\"hljs-attr\">onSuccess<\/span>=<span class=\"hljs-string\">{(result)<\/span> =&gt;<\/span> {\n        if (typeof result.info === \"object\" &amp;&amp; \"secure_url\" in result.info) {\n          onUploadSuccess(result.info.secure_url);\n        }\n      }}\n      options={{\n        singleUploadAutoClose: true,\n      }}\n    &gt;\n      {({ open }) =&gt; {\n        return (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span>\n            <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> open()}\n            className=\"rounded-md bg-indigo-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600\"\n          &gt;\n            Upload Avatar\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n        );\n      }}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">CldUploadWidget<\/span>&gt;<\/span><\/span>\n  );\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\n\n<p>Our component takes a single prop on <code>UploadSuccess<\/code>. We can use this prop to pass a Server Action function to this client component to handle updating our user with the uploaded avatar url. This component has to be a client component because the CldUploadWidget is a traditional client side component. It depends on client specific code for rendering the popup widget and creating an AJAX request to upload the file.<\/p>\n\n\n\n<p>Cloudinary\u2019s <a href=\"https:\/\/next.cloudinary.dev\/clduploadwidget\/basic-usage\"><code>CldUploadWidget<\/code><\/a> does all the heavy lifting for uploading the image on the client. It takes a render prop that we render a button that triggers the upload widget UI when clicked.<\/p>\n\n\n\n<p>The important pieces are the<a href=\"https:\/\/next.cloudinary.dev\/clduploadwidget\/configuration\"> props and configuration<\/a> that we provide to <code>CldUploadWidget<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><strong>uploadPreset<\/strong><\/code>. This is the value that we took from our Cloudinary settings. This lets us provide some default options for optimizing our image through Cloudinary.<\/li>\n\n\n\n<li><code><strong>signatureEndpoint<\/strong><\/code>. We\u2019ll add some security by creating signed URLs for our images so that they can only be accessed through our application. This prop just takes an endpoint that we\u2019ll handle the signing on the server side.<\/li>\n\n\n\n<li><code><strong>onSuccess<\/strong><\/code>. A callback function when the image upload has been completed. We\u2019ll check the result to make TypeScript happy and pass our signed URL to our components onUploadSuccess prop.<\/li>\n\n\n\n<li><code><strong>options<\/strong><\/code>. Since we\u2019re only uploading a single image, we\u2019ll pass the singleUploadAutoClose: true value so the upload widget closes when the upload is completed.<\/li>\n<\/ul>\n\n\n\n<p>This is a pretty simple setup. The docs are comprehensive if we want to customize our widget or other options a bit more.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Image Signing API Endpoint<\/strong><\/h2>\n\n\n\n<p>In our CldUploadWidget, we passed an API URL path \/api\/sign-cloudinary-params for the signatureEndpoint prop. We need to create this API endpoint to handle the signing.<\/p>\n\n\n\n<p>We\u2019ll create a file at the path <code>api\/sign-cloudinary-params\/route.ts<\/code>. <a href=\"https:\/\/next.cloudinary.dev\/clduploadwidget\/signed-uploads\">The docs provide the code for us<\/a>.<\/p>\n\n\n<pre class=\"wp-block-code\" 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> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n});\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> body = <span class=\"hljs-keyword\">await<\/span> request.json();\n  <span class=\"hljs-keyword\">const<\/span> { paramsToSign } = body;\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({ signature });\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\n\n<h2 class=\"wp-block-heading\"><strong>Creating the Home Page<\/strong><\/h2>\n\n\n\n<p>I mentioned before that the home page is a Server Component. We\u2019re able to<a href=\"https:\/\/nextjs.org\/docs\/app\/building-your-application\/data-fetching\/fetching-caching-and-revalidating\"> fetch our user<\/a> from the database in our component and also define our<a href=\"https:\/\/nextjs.org\/docs\/app\/building-your-application\/data-fetching\/server-actions-and-mutations\"> server action<\/a> that handles the upload success callback to update our users avatar in the database.<\/p>\n\n\n\n<p>Our goal is to have our home page render the user\u2019s newly updated avatar image once the upload completes. In client-side React, we could just use some state to handle this. The upload completes, we set the state, and it re-renders the component showing the new image. This flow is a little bit different in a Server Component.<\/p>\n\n\n\n<p>Here\u2019s the code for our home page. It\u2019s a simple UI that shows a welcome message with a placeholder or avatar image rendered below, and an upload button.<\/p>\n\n\n<pre class=\"wp-block-code\" 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> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/image\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { revalidatePath } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/cache\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getCurrentUser, updateUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/db\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { AvatarUploader } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/avatar-uploader\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> getCurrentUser();\n\n  <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">saveAvatar<\/span>(<span class=\"hljs-params\">url: string<\/span>) <\/span>{\n    <span class=\"hljs-string\">\"use server\"<\/span>;\n    <span class=\"hljs-keyword\">await<\/span> updateUser({ <span class=\"hljs-attr\">avatar<\/span>: url });\n    revalidatePath(<span class=\"hljs-string\">\"\/\"<\/span>);\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"p-24 flex flex-col justify-center items-center\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-4xl font-bold my-12\"<\/span>&gt;<\/span>Welcome back, {user.name}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col items-center space-y-4\"<\/span>&gt;<\/span>\n        {user.avatar ? (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{user.avatar}<\/span>\n            <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{288}<\/span>\n            <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{288}<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"rounded-full\"<\/span>\n            <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"Your avatar\"<\/span>\n          \/&gt;<\/span>\n        ) : (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-gray-300 w-72 h-72 rounded-full\"<\/span> \/&gt;<\/span>\n        )}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex items-center justify-center gap-x-4\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">AvatarUploader<\/span> <span class=\"hljs-attr\">onUploadSuccess<\/span>=<span class=\"hljs-string\">{saveAvatar}<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\n  );\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\n\n<p>Here\u2019s how it looks by default before we upload our image:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img width=\"586\" height=\"512\" data-public-id=\"Web_Assets\/blog\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-1\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-1.png\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_586,h_512,c_scale\/f_auto,q_auto\/v1715720821\/Web_Assets\/blog\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-1\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-1.png?_i=AA\" alt=\"Image showing screenshot of the nextjs cloudinary sample web page with no image uploaded\" class=\"wp-post-33806 wp-image-33807\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1715720821\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1715720821\/Web_Assets\/blog\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-1\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-1.png?_i=AA 586w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1715720821\/Web_Assets\/blog\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-1\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-1.png?_i=AA 300w\" sizes=\"auto, (max-width: 586px) 100vw, 586px\" \/><\/figure><\/div>\n\n\n<p>Looking at the component code, before we actually return our <code>JSX<\/code>, we have an async function that runs on the server and fetches the user from the database, and a server action function that handles updating the user in the database. We\u2019ll conditionally render the avatar if it\u2019s set. Otherwise, we\u2019ll show a gray circle placeholder. We\u2019ll render our <code>AvatarUploader<\/code> client component and pass it our <code>saveAvatar<\/code> server action. When the <code>onSuccess<\/code> callback is triggered in our Cloudinary widget component, our server action will update the user in the database with the signed URL of the uploaded image.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img width=\"589\" height=\"531\" data-public-id=\"Web_Assets\/blog\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-2\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-2.png\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/w_589,h_531,c_scale\/f_auto,q_auto\/v1715720818\/Web_Assets\/blog\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-2\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-2.png?_i=AA\" alt=\"Image showing screenshot of the Cloudinary nextjs upload sample web page with image uploaded\" class=\"wp-post-33806 wp-image-33808\" style=\"width:589px;height:auto\" data-format=\"png\" data-transformations=\"f_auto,q_auto\" data-version=\"1715720818\" data-seo=\"1\" srcset=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1715720818\/Web_Assets\/blog\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-2\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-2.png?_i=AA 589w, https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1715720818\/Web_Assets\/blog\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-2\/blog-Cloudinary-Image-Uploads-Using-NextJS-App-Router-2.png?_i=AA 300w\" sizes=\"auto, (max-width: 589px) 100vw, 589px\" \/><\/figure><\/div>\n\n\n<p>Even though we\u2019re not setting any state for the user or avatar, it\u2019s just being fetched in our server component and automatically renders in the page when the upload is completed. This happens because of the <code>revalidatePath(\"\/\")<\/code> call in our <code>saveAvatar<\/code> action.<\/p>\n\n\n<pre class=\"wp-block-code\" 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-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">saveAvatar<\/span>(<span class=\"hljs-params\">url: string<\/span>) <\/span>{\n    <span class=\"hljs-string\">\"use server\"<\/span>;\n    <span class=\"hljs-keyword\">await<\/span> updateUser({ <span class=\"hljs-attr\">avatar<\/span>: url });\n    revalidatePath(<span class=\"hljs-string\">\"\/\"<\/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\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This tells NextJS that some data on which this page depends has potentially changed. NextJS will stream in the updated UI from the server and update our client without triggering a page refresh.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wrapping Up<\/strong><\/h2>\n\n\n\n<p>We\u2019ve managed to provide a client app-like experience on a server-rendered page thanks to Cloudinary and NextJS! We just have some straightforward React code and a couple of functions that fetch and update some data. We also integrated secure and optimized avatar images into our application without much friction at all.<\/p>\n\n\n\n<p>Still considering Cloudinary? Learn more about <a href=\"https:\/\/cloudinary.com\/products\/image\">Cloudinary features to optimize and secure images<\/a>. And if you are a current Cloudinary user who found this blog post helpful and want to learn more about what you can do in Cloudinary, <a href=\"https:\/\/cloudinary.com\/guides\">check out our full suite of guides<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019ve ever added file uploading to a web application, you know it can be a tricky endeavor. There\u2019s a lot of work involved with providing a good user experience for uploading images on both the backend and frontend of a web application. Thankfully, along with all the other great features for optimizing your images, [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":33809,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[212,373],"class_list":["post-33806","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-next-js","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>How to Upload Image to Cloudinary NextJS Using NextJS App Router<\/title>\n<meta name=\"description\" content=\"Definitive guide to the Cloudinary NextJS image upload process: Build a simple avatar image uploader component in a web app powered by the NextJS App Router with React Server Components.\" \/>\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\/cloudinary-image-uploads-using-nextjs-app-router\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Cloudinary Image Uploads Using NextJS App Router\" \/>\n<meta property=\"og:description\" content=\"Definitive guide to the Cloudinary NextJS image upload process: Build a simple avatar image uploader component in a web app powered by the NextJS App Router with React Server Components.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2024-05-21T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-10-16T18:37:12+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.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\/cloudinary-image-uploads-using-nextjs-app-router#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Cloudinary Image Uploads Using NextJS App Router\",\"datePublished\":\"2024-05-21T14:00:00+00:00\",\"dateModified\":\"2025-10-16T18:37:12+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router\"},\"wordCount\":1385,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA\",\"keywords\":[\"Next.js\",\"Upload\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2024\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router\",\"url\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router\",\"name\":\"How to Upload Image to Cloudinary NextJS Using NextJS App Router\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA\",\"datePublished\":\"2024-05-21T14:00:00+00:00\",\"dateModified\":\"2025-10-16T18:37:12+00:00\",\"description\":\"Definitive guide to the Cloudinary NextJS image upload process: Build a simple avatar image uploader component in a web app powered by the NextJS App Router with React Server Components.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA\",\"width\":2000,\"height\":1100,\"caption\":\"Image showing graphic of the cloudinary nextjs upload\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Cloudinary Image Uploads Using NextJS App Router\"}]},{\"@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":"How to Upload Image to Cloudinary NextJS Using NextJS App Router","description":"Definitive guide to the Cloudinary NextJS image upload process: Build a simple avatar image uploader component in a web app powered by the NextJS App Router with React Server Components.","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\/cloudinary-image-uploads-using-nextjs-app-router","og_locale":"en_US","og_type":"article","og_title":"Cloudinary Image Uploads Using NextJS App Router","og_description":"Definitive guide to the Cloudinary NextJS image upload process: Build a simple avatar image uploader component in a web app powered by the NextJS App Router with React Server Components.","og_url":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router","og_site_name":"Cloudinary Blog","article_published_time":"2024-05-21T14:00:00+00:00","article_modified_time":"2025-10-16T18:37:12+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.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\/cloudinary-image-uploads-using-nextjs-app-router#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Cloudinary Image Uploads Using NextJS App Router","datePublished":"2024-05-21T14:00:00+00:00","dateModified":"2025-10-16T18:37:12+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router"},"wordCount":1385,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA","keywords":["Next.js","Upload"],"inLanguage":"en-US","copyrightYear":"2024","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router","url":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router","name":"How to Upload Image to Cloudinary NextJS Using NextJS App Router","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA","datePublished":"2024-05-21T14:00:00+00:00","dateModified":"2025-10-16T18:37:12+00:00","description":"Definitive guide to the Cloudinary NextJS image upload process: Build a simple avatar image uploader component in a web app powered by the NextJS App Router with React Server Components.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA","width":2000,"height":1100,"caption":"Image showing graphic of the cloudinary nextjs upload"},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Cloudinary Image Uploads Using NextJS App Router"}]},{"@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\/cloudinary-image-uploads-using-nextjs-app-router","smart_links":{"inbound":0,"outbound":0},"traffic_boost_suggestions_count":0,"meta":{"@context":"https:\/\/schema.org","@type":"NewsArticle","headline":"Cloudinary Image Uploads Using NextJS App Router","url":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router","mainEntityOfPage":{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA&w=150&h=150&crop=1","image":{"@type":"ImageObject","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.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","upload"],"dateCreated":"2024-05-21T14:00:00Z","datePublished":"2024-05-21T14:00:00Z","dateModified":"2025-10-16T18:37:12Z"},"rendered":"<meta name=\"parsely-title\" content=\"Cloudinary Image Uploads Using NextJS App Router\" \/>\n<meta name=\"parsely-link\" content=\"https:\/\/cloudinary.com\/blog\/cloudinary-image-uploads-using-nextjs-app-router\" \/>\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\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA&w=150&amp;h=150&amp;crop=1\" \/>\n<meta name=\"parsely-pub-date\" content=\"2024-05-21T14:00:00Z\" \/>\n<meta name=\"parsely-section\" content=\"Uncategorized\" \/>\n<meta name=\"parsely-tags\" content=\"next.js,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\/v1714769205\/image_upload_NextJS_App_Router-blog\/image_upload_NextJS_App_Router-blog.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/33806","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=33806"}],"version-history":[{"count":4,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/33806\/revisions"}],"predecessor-version":[{"id":38796,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/33806\/revisions\/38796"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/33809"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=33806"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=33806"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=33806"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}