{"id":36633,"date":"2025-01-27T07:00:00","date_gmt":"2025-01-27T15:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=36633"},"modified":"2025-05-12T15:51:54","modified_gmt":"2025-05-12T22:51:54","slug":"virtual-try-on-app-cloudinarys-face-detection-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js","title":{"rendered":"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>In this blog post, we\u2019ll show you how to leverage <a href=\"https:\/\/cloudinary.com\/documentation\/face_detection_based_transformations\">Cloudinary\u2019s face detection algorithm<\/a> using <a href=\"https:\/\/nextjs.org\/\">Next.js<\/a>. We\u2019ll build a simple but fun demo of a virtual try-on app that showcases one of the many things you can do with this feature.<\/p>\n<p>But first, we\u2019ll need to set up the following:<\/p>\n<ul>\n<li>\n<strong>A free Cloudinary account<\/strong>. If you haven\u2019t already, <a href=\"https:\/\/cloudinary.com\/users\/register_free\">sign up for a Cloudinary account<\/a>. It\u2019s where our images will live and be managed.<\/li>\n<li>\n<strong>Next.js App<\/strong>. Make sure you have the <a href=\"https:\/\/nextjs.org\/docs\/app\">Next.js App Router<\/a>. We can use the pages router, but for this demo, let\u2019s stick with the App Router to stay on the cutting edge.<\/li>\n<li>\n<strong>Next-Cloudinary package<\/strong>. The <a href=\"https:\/\/next.cloudinary.dev\/installation\">next-cloudinary package<\/a> is a handy tool that makes integrating Cloudinary into our Next.js app a breeze.<\/li>\n<li>\n<strong>Cloudinary package<\/strong>. This is the <a href=\"http:\/\/node.js\/\">Cloudinary SDK for Node.js<\/a>.<\/li>\n<\/ul>\n<p>We need to display and upload images to Cloudinary using Next.js. Because of this, we highly recommend checking out our previous blog post: <a href=\"https:\/\/cloudinary.com\/blog\/next-js-cloudinary-upload-transform-moderate-images\">Using Next.js and Cloudinary to Upload, Transform, and Moderate Your Images<\/a> to learn how to display and upload images to Cloudinary using Next.js.<\/p>\n<p>Once we have our Cloudinary account ready, it\u2019s time to kick things off by setting up our Next.js app. Let\u2019s start with this command:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npx<\/span> <span class=\"hljs-selector-tag\">create-next-app<\/span><span class=\"hljs-keyword\">@latest<\/span>\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>After running the command you\u2019ll see a few prompts that will set the details of the app for you. Let\u2019s go step by step:<\/p>\n<ul>\n<li>\n<strong>Name your app<\/strong>. Pick a cool name that resonates with your project.<\/li>\n<li>\n<strong>Enable app router<\/strong>. Make sure to turn this feature on.<\/li>\n<\/ul>\n<p>The other steps are optional and entirely up to you. In my case, I also chose to use <a href=\"https:\/\/www.typescriptlang.org\/\">TypeScript<\/a> and <a href=\"https:\/\/tailwindcss.com\/\">Tailwind CSS<\/a>.<\/p>\n<p>Now that\u2019s done, we need to install our Cloudinary package for easy Cloudinary integration in our app:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install next-cloudinary\n<\/code><\/span><\/pre>\n<p>We\u2019re almost there, just one more thing, and we\u2019re ready to start hacking! We need to set up some environment variables that will help us to securely store key information required for our app to communicate with Cloudinary:<\/p>\n<ul>\n<li>\n<strong>NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME<\/strong>. This is your Cloudinary cloud name, a unique identifier for your Cloudinary account.<\/li>\n<li>\n<strong>NEXT_PUBLIC_CLOUDINARY_API_KEY.<\/strong> The private key used for secure connections with the Cloudinary API<\/li>\n<li>\n<strong>CLOUDINARY_API_KEY.<\/strong> The public key used to authenticate your requests to the Cloudinary API<\/li>\n<\/ul>\n<p>The <strong>NEXT_PUBLIC<\/strong> is essential here, as we need to read these environment variables from the client side. The way to tell Next.js that these are client-side usable is by adding this prefix.<\/p>\n<h2>Building Our Demo UI<\/h2>\n<p>For this demo, we\u2019ll build a simple try-on app where users can upload their profile picture and try on some eyeglass frames.<\/p>\n<p>We\u2019ll use two components we created in our previous blog post, <a href=\"https:\/\/cloudinary.com\/blog\/next-js-cloudinary-upload-transform-moderate-images\">Using Next.js and Cloudinary to Upload, Transform, and Moderate Your Images<\/a>: <code>UploadButton<\/code> and <code>CldImage<\/code>. Please check out that blog to see how we built these components in detail.<\/p>\n<p>We\u2019ll use our Cloudinary SDK to retrieve our images. Let\u2019s create a new utility function to retrieve a Cloudinary SDK instance for us. In our app folder, create a new folder called \u201cutils\u201d and inside it, let\u2019s add the file <code>getCloudinary<\/code> and write the following:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">app\n \u21b3utils\n  \u21b3getCloudinary.ts\n  \n<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\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getCloadinary<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ Config our Cloudinary instance<\/span>\n  cloudinary.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.NEXT_PUBLIC_CLOUDINARY_API_KEY,\n    <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n  });\n  <span class=\"hljs-keyword\">return<\/span> cloudinary;\n}\n<\/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<p>Now, we\u2019ll create a simple UI to upload a profile picture. For that, let\u2019s create the following UI:<\/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\">app\n \u21b3page.tsx\n\n<span class=\"hljs-keyword\">import<\/span> UploadButton <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/UploadButton\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> getCloudinary <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/utils\/getCloudinary\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> CldImage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/CldImage\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> CldImageButton <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/CldImageButton\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> PROFILE_IMAGE_PUBLIC_ID = <span class=\"hljs-string\">\"profile-image\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> cloudinary = getCloudinary();\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-comment\">\/\/ Search for a resource with the public ID of \"profile-image\"<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">resources<\/span>: profileImage } = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n    .expression(<span class=\"hljs-string\">`public_id=<span class=\"hljs-subst\">${PROFILE_IMAGE_PUBLIC_ID}<\/span>`<\/span>)\n    .execute();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col gap-10 mt-10 items-center min-h-screen\"<\/span>&gt;<\/span>\n       {\/* If profileImage resource is found, render it *\/}\n        {profileImage.length ? (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg\"<\/span>\n            <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{PROFILE_IMAGE_PUBLIC_ID}<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{profileImage&#91;0].secure_url}<\/span>\n            <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{500}<\/span>\n            <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{500}<\/span>\n          \/&gt;<\/span>\n        ) : null}\n        \n        {\/* Upload an image with publicId \"profile-image\" *\/}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UploadButton<\/span>\n          \/\/ <span class=\"hljs-attr\">The<\/span> <span class=\"hljs-attr\">signatureEndpoint<\/span> <span class=\"hljs-attr\">prop<\/span> <span class=\"hljs-attr\">is<\/span> <span class=\"hljs-attr\">the<\/span> <span class=\"hljs-attr\">path<\/span> <span class=\"hljs-attr\">to<\/span> <span class=\"hljs-attr\">the<\/span> <span class=\"hljs-attr\">route<\/span> <span class=\"hljs-attr\">that<\/span> <span class=\"hljs-attr\">will<\/span> <span class=\"hljs-attr\">sign<\/span> <span class=\"hljs-attr\">the<\/span> <span class=\"hljs-attr\">Cloudinary<\/span> <span class=\"hljs-attr\">params<\/span>\n          <span class=\"hljs-attr\">signatureEndpoint<\/span>=<span class=\"hljs-string\">\"\/api\/sign-cloudinary-params\"<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full\"<\/span>\n          <span class=\"hljs-attr\">options<\/span>=<span class=\"hljs-string\">{{<\/span>\n            <span class=\"hljs-attr\">publicId:<\/span> <span class=\"hljs-attr\">PROFILE_IMAGE_PUBLIC_ID<\/span>,\n            <span class=\"hljs-attr\">multiple:<\/span> <span class=\"hljs-attr\">false<\/span>,\n          }}\n        \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\n  );\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>In the above snippet, we\u2019ll get our Cloudinary instance using the <code>getCloudinary<\/code> function.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> cloudinary = getCloudinary();\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In our <a href=\"https:\/\/nextjs.org\/docs\/app\/building-your-application\/rendering\/server-components\">Server Component<\/a> <code>Home<\/code>, we\u2019ll fetch an image with the public ID \u201cprofile-image\u201d. If we find it, we\u2019ll display it using our <code>CldImage<\/code> component.<\/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-comment\">\/\/ Search for a resource with the public ID of \"profile-image\"<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">resources<\/span>: profileImage } = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n    .expression(<span class=\"hljs-string\">`public_id=<span class=\"hljs-subst\">${PROFILE_IMAGE_PUBLIC_ID}<\/span>`<\/span>)\n    .execute();\n    \n    ...\n    \n    {<span class=\"hljs-comment\">\/* If profileImage resource is found, render it *\/<\/span>}\n    {profileImage.length ? (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg\"<\/span>\n        <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{PROFILE_IMAGE_PUBLIC_ID}<\/span>\n        <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{profileImage&#91;0].secure_url}<\/span>\n        <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{500}<\/span>\n        <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{500}<\/span>\n      \/&gt;<\/span><\/span>\n    ) : <span class=\"hljs-literal\">null<\/span>}\n \n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Finally, we\u2019ll add a button that uses the <code>UploadButton<\/code> component to upload our image with the same public ID \u201cprofile-image\u201d. In this component, we\u2019ll set the multiple option to false to prevent uploading multiple images at once. Every time we use this button to upload our image, the previous image will be replaced, as they both share the same public ID.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">{<span class=\"hljs-comment\">\/* Upload an image with publicId \"profile-image\" *\/<\/span>}\n &lt;UploadButton\n   <span class=\"hljs-comment\">\/\/ The signatureEndpoint prop is the path to the route that will sign the Cloudinary params<\/span>\n   signatureEndpoint=<span class=\"hljs-string\">\"\/api\/sign-cloudinary-params\"<\/span>\n   className=<span class=\"hljs-string\">\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full\"<\/span>\n   options={{\n    publicId: PROFILE_IMAGE_PUBLIC_ID,\n    multiple: <span class=\"hljs-keyword\">false<\/span>,\n   }}\n \/&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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 result, with the aggregated styles using Tailwind, will be like this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1738010440\/blog-Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_with_Next.js-1.png\" alt=\"upload button\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1046\" height=\"316\"\/><\/p>\n<p>Now, if we use our button to upload a profile picture and then refresh the page, we\u2019ll see our image:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1738010440\/blog-Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_with_Next.js-2.png\" alt=\"profile picture uploaded\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1056\" height=\"908\"\/><\/p>\n<p>Refreshing the page every time we upload a new image can be a real hassle. To make things smoother, let\u2019s create a <a href=\"https:\/\/nextjs.org\/docs\/app\/building-your-application\/data-fetching\/server-actions-and-mutations\">Server Action<\/a> that automatically revalidates the page whenever an image is successfully uploaded.<\/p>\n<p>Create a new folder called \u201cactions\u201d inside our \u201capp\u201d folder, and then create a file called <code>getProfileImage.ts<\/code>. Here, we\u2019ll have the following code:<\/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\">app\n \u21b3actions\n  \u21b3getProfileImage\n\n<span class=\"hljs-string\">\"use server\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { revalidatePath } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/cache\"<\/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\">getProfileImage<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ wait 5 seconds for Cloudinary to update<\/span>\n  <span class=\"hljs-keyword\">await<\/span> <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, <span class=\"hljs-number\">5000<\/span>));\n  <span class=\"hljs-comment\">\/\/ revalidate home page<\/span>\n  revalidatePath(<span class=\"hljs-string\">\"\/\"<\/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<p>The timeout we\u2019re using here isn\u2019t ideal because it keeps the connection open. A better approach might be, for example, to have the frontend poll for updates instead. We\u2019ll leave it as is just for this tutorial.<\/p>\n<p>Let\u2019s import and use this server action in our <code>UploadButton<\/code> component:<\/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\">app\n \u21b3components\n  \u21b3UploadButton.tsx\n  \n<span class=\"hljs-string\">\"use client\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { CldUploadButton, type CldUploadButtonProps } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-cloudinary\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getProfileImage } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/actions\/getProfileImage\"<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">UploadButton<\/span>(<span class=\"hljs-params\">props: CldUploadButtonProps<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldUploadButton<\/span>\n      {<span class=\"hljs-attr\">...props<\/span>}\n      <span class=\"hljs-attr\">onSuccess<\/span>=<span class=\"hljs-string\">{async<\/span> () =&gt;<\/span> {\n        \/\/ when image finish uploading call server action\n        await getProfileImage();\n      }}\n    \/&gt;<\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> UploadButton;\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>If we upload a new image using our app, the page will update the image without the need to refresh the page.<\/p>\n<p>Now, we\u2019ll need a way to choose the eyeglass frames we want to try on our profile picture. To do this, let\u2019s first upload a few eyeglass frames and store them in a glasses folder inside Cloudinary:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1738010442\/blog-Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_with_Next.js-3.png\" alt=\"glasses folder\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1999\" height=\"532\"\/><\/p>\n<p>In our code, we can now fetch these resources and list them on our <code>Home<\/code> page:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">app\n \u21b3page.tsx\n\n<span class=\"hljs-keyword\">import<\/span> UploadButton <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/UploadButton\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> getCloudinary <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/utils\/getCloudinary\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> CldImage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/CldImage\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> CldImageButton <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/CldImageButton\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> PROFILE_IMAGE_PUBLIC_ID = <span class=\"hljs-string\">\"profile-image\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> cloudinary = getCloudinary();\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>) \n\n  <span class=\"hljs-title\">const<\/span> <\/span>{ resources: profileImage } = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n    .expression(<span class=\"hljs-string\">`public_id=<span class=\"hljs-subst\">${PROFILE_IMAGE_PUBLIC_ID}<\/span>`<\/span>)\n    .execute();\n    \n  <span class=\"hljs-comment\">\/\/ Search for our resources inside our \"glasses\" folder<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">resources<\/span>: glassResources } = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n    .expression(<span class=\"hljs-string\">\"folder=glasses\"<\/span>)\n    .execute();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col gap-10 mt-10 items-center min-h-screen\"<\/span>&gt;<\/span>\n        {profileImage.length ? (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg\"<\/span>\n            <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{PROFILE_IMAGE_PUBLIC_ID}<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{profileImage&#91;0].secure_url}<\/span>\n            <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{500}<\/span>\n            <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{500}<\/span>\n          \/&gt;<\/span>\n        ) : null}\n        \n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UploadButton<\/span>\n          <span class=\"hljs-attr\">signatureEndpoint<\/span>=<span class=\"hljs-string\">\"\/api\/sign-cloudinary-params\"<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full\"<\/span>\n          <span class=\"hljs-attr\">options<\/span>=<span class=\"hljs-string\">{{<\/span>\n            <span class=\"hljs-attr\">publicId:<\/span> <span class=\"hljs-attr\">PROFILE_IMAGE_PUBLIC_ID<\/span>,\n            <span class=\"hljs-attr\">multiple:<\/span> <span class=\"hljs-attr\">false<\/span>,\n          }}\n        \/&gt;<\/span>\n        \n        {\/* Listing our glass frames *\/}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid grid-cols-3 gap-4\"<\/span>&gt;<\/span>\n          {glassResources.map((image: ResourceImage) =&gt; (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{image.public_id}<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n                <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg hover:shadow-2xl\"<\/span>\n                <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{image.public_id}<\/span>\n                <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{image.secure_url}<\/span>\n                <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{100}<\/span>\n                <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{100}<\/span>\n               \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          ))}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\n  );\n}\n\ntype ResourceImage = {\n  <span class=\"hljs-attr\">public_id<\/span>: string;\n  secure_url: string;\n  width: number;\n  height: number;\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019ll fetch the resources that reside in the <code>glasses<\/code> folder.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">  <span class=\"hljs-comment\">\/\/ Search for our resources inside our \"glasses\" folder<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">resources<\/span>: glassResources } = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n    .expression(<span class=\"hljs-string\">\"folder=glasses\"<\/span>)\n    .execute();\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then, we\u2019ll list them using our <code>CldImage<\/code> component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">       {\/* Listing our glass frames *\/}\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid grid-cols-3 gap-4\"<\/span>&gt;<\/span>\n          {glassResources.map((image: ResourceImage) =&gt; (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{image.public_id}<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n                <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg hover:shadow-2xl\"<\/span>\n                <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{image.public_id}<\/span>\n                <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{image.secure_url}<\/span>\n                <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{100}<\/span>\n                <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{100}<\/span>\n               \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          ))}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1738010440\/blog-Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_with_Next.js-4.png\" alt=\"uploaded profile picture with glasses options below\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1054\" height=\"913\"\/><\/p>\n<p>In our demo, the end-user should be able to click these eyeglass frames and try them on. So, instead of just rendering them as images, let\u2019s create a button where the user can interact later. Create a new component inside our <code>components<\/code> folder called <code>CldImageButton<\/code>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">app\n \u21b3components\n  \u21b3CldImageButton.tsx\n\n<span class=\"hljs-string\">\"use client\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> CldImage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/CldImage\"<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">CldImageButton<\/span>(<span class=\"hljs-params\">{ image }: { image: ResourceImage }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg hover:shadow-2xl\"<\/span>\n        <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{image.public_id}<\/span>\n        <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{image.secure_url}<\/span>\n        <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{100}<\/span>\n        <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{100}<\/span>\n      \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> CldImageButton;\n\n<span class=\"hljs-keyword\">export<\/span> type ResourceImage = {\n  <span class=\"hljs-attr\">public_id<\/span>: string;\n  secure_url: string;\n  width: number;\n  height: number;\n};\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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>And replace it in our <code>Home<\/code> server component:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">app\n \u21b3page.tsx\n ...\n <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid grid-cols-3 gap-4\"<\/span>&gt;<\/span>\n    {glassResources.map((image: CldImage) =&gt; (\n       <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{image.public_id}<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImageButton<\/span> <span class=\"hljs-attr\">image<\/span>=<span class=\"hljs-string\">{image}<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n      ))}\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n  ...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>Leveraging the Face-Detection Algorithm From Cloudinary<\/h2>\n<p>To try on our eyeglass frames, we\u2019ll need to position them correctly on our profile picture. We\u2019ll use Cloudinary\u2019s face-detection algorithm to find the face in our image and place the frames accurately over it.<\/p>\n<p>Cloudinary offers face-detection algorithms that automatically apply <a href=\"https:\/\/cloudinary.com\/documentation\/image_transformations\">transformations<\/a> to images based on the faces it detects.<\/p>\n<p>First, let\u2019s get the public ID of the frame the user selects. When they click on a frame, we will add a <a href=\"https:\/\/nextjs.org\/docs\/app\/api-reference\/file-conventions\/page#searchparams-optional\">search param<\/a> to the URL using <a href=\"https:\/\/nextjs.org\/docs\/pages\/api-reference\/functions\/use-router\">useRouter<\/a> from Next.js.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-string\">\"use client\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> CldImage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/CldImage\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/navigation\"<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">CldImageButton<\/span>(<span class=\"hljs-params\">{ image }: { image: ResourceImage }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n      \/\/ <span class=\"hljs-attr\">add<\/span> <span class=\"hljs-attr\">selectedImagePublicId<\/span> <span class=\"hljs-attr\">search<\/span> <span class=\"hljs-attr\">param<\/span> <span class=\"hljs-attr\">with<\/span> <span class=\"hljs-attr\">the<\/span> <span class=\"hljs-attr\">public<\/span> <span class=\"hljs-attr\">id<\/span> <span class=\"hljs-attr\">of<\/span> <span class=\"hljs-attr\">the<\/span> <span class=\"hljs-attr\">image<\/span>\n      <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> router.push(`\/?selectedImagePublicId=${image.public_id}`)}\n    &gt;\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg hover:shadow-2xl\"<\/span>\n        <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{image.public_id}<\/span>\n        <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{image.secure_url}<\/span>\n        <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{100}<\/span>\n        <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{100}<\/span>\n      \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> CldImageButton;\n\n<span class=\"hljs-keyword\">export<\/span> type ResourceImage = {\n  <span class=\"hljs-attr\">public_id<\/span>: string;\n  secure_url: string;\n  width: number;\n  height: number;\n};\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then, on our <code>Home<\/code> page, we\u2019ll get this parameter:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" 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> UploadButton <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/UploadButton\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> getCloudinary <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/utils\/getCloudinary\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> CldImage <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/CldImage\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> CldImageButton, { type ResourceImage } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/app\/components\/CldImageButton\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> PROFILE_IMAGE_PUBLIC_ID = <span class=\"hljs-string\">\"profile-image\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> cloudinary = getCloudinary();\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\">{\n  searchParams,\n}: {\n  searchParams: Promise&lt;{ &#91;key: string]: string | string&#91;] | undefined }&gt;;\n}<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ get our search params<\/span>\n  <span class=\"hljs-keyword\">const<\/span> searchParamsObj = <span class=\"hljs-keyword\">await<\/span> searchParams;\n\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">resources<\/span>: glassResources } = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n    .expression(<span class=\"hljs-string\">\"folder=glasses\"<\/span>)\n    .execute();\n\n  <span class=\"hljs-comment\">\/\/ Search for a resource with the public ID of \"profile-image\"<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { <span class=\"hljs-attr\">resources<\/span>: profileImage } = <span class=\"hljs-keyword\">await<\/span> cloudinary.search\n    .expression(<span class=\"hljs-string\">`public_id=<span class=\"hljs-subst\">${PROFILE_IMAGE_PUBLIC_ID}<\/span>`<\/span>)\n    .execute();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col gap-10 mt-10 items-center min-h-screen\"<\/span>&gt;<\/span>\n        {profileImage.length ? (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImage<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg\"<\/span>\n            <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{PROFILE_IMAGE_PUBLIC_ID}<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{profileImage&#91;0].secure_url}<\/span>\n            <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{500}<\/span>\n            <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{500}<\/span>\n          \/&gt;<\/span>\n        ) : null}\n\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">UploadButton<\/span>\n          <span class=\"hljs-attr\">signatureEndpoint<\/span>=<span class=\"hljs-string\">\"\/api\/sign-cloudinary-params\"<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full\"<\/span>\n          <span class=\"hljs-attr\">options<\/span>=<span class=\"hljs-string\">{{<\/span>\n            <span class=\"hljs-attr\">publicId:<\/span> <span class=\"hljs-attr\">PROFILE_IMAGE_PUBLIC_ID<\/span>,\n            <span class=\"hljs-attr\">multiple:<\/span> <span class=\"hljs-attr\">false<\/span>,\n          }}\n        \/&gt;<\/span>\n\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid grid-cols-3 gap-4\"<\/span>&gt;<\/span>\n          {glassResources.map((image: ResourceImage) =&gt; (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{image.public_id}<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CldImageButton<\/span> <span class=\"hljs-attr\">image<\/span>=<span class=\"hljs-string\">{image}<\/span> \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          ))}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><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>To make use of this algorithm and to automatically <a href=\"https:\/\/cloudinary.com\/documentation\/layers#layer_placement_gravity\">position an overlay<\/a> over the detected face(s) in an image, we need to set the <code>gravity<\/code> value to either \u201cface\u201d or \u201cfaces\u201d (if we want to add the overlay to each of the detected faces in the image).<\/p>\n<p>To add the overlay in our <code>CldImage<\/code> component, we are going to use the <code>overlays<\/code> property:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"> ...\n  &lt;CldImage\n    className=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg\"<\/span>\n    alt={PROFILE_IMAGE_PUBLIC_ID}\n    src={profileImage&#91;<span class=\"hljs-number\">0<\/span>].secure_url}\n    width={<span class=\"hljs-number\">500<\/span>}\n    height={<span class=\"hljs-number\">500<\/span>}\n    <span class=\"hljs-comment\">\/\/ check if an image has been selected to try-on<\/span>\n    {...(searchParamsObj.selectedImagePublicId\n      ? {\n         <span class=\"hljs-attr\">overlays<\/span>: &#91;\n          {\n            <span class=\"hljs-comment\">\/\/ Our search param with the public ID of the image<\/span>\n            <span class=\"hljs-attr\">publicId<\/span>: searchParamsObj.selectedImagePublicId <span class=\"hljs-keyword\">as<\/span> string,\n            <span class=\"hljs-attr\">position<\/span>: {\n             <span class=\"hljs-comment\">\/\/ our gravity value<\/span>\n             <span class=\"hljs-attr\">gravity<\/span>: <span class=\"hljs-string\">\"face\"<\/span>,\n            },\n           },\n          ],\n        }\n        : <span class=\"hljs-literal\">null<\/span>)}\n    \/&gt;\n  ...\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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>We can set our <code>gravity<\/code> value by using the <code>position<\/code> prop inside <code>overlays<\/code>. We\u2019ll also specify the image we want to use as the overlay with the <code>publicId<\/code> prop by providing the ID of our image.<\/p>\n<p>If we select a frame, we\u2019ll see something similar to the following:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1738010440\/blog-Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_with_Next.js-5.png\" alt=\"small glasses frame selected and placed onto face\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1052\" height=\"923\"\/><\/p>\n<p>However, the frames aren\u2019t fitting quite right yet. We\u2019ll need to resize them to match the size of the detected face. To achieve this, we\u2019ll use the <code>region_relative<\/code> <a href=\"https:\/\/cloudinary.com\/documentation\/transformation_reference#fl_flag\">flag<\/a> and specify a <code>width<\/code> that scales based on this flag.<\/p>\n<p>The <code>region_relative<\/code> flag tells Cloudinary to interpret percentage-based width and height values for an image layer (like an overlay) as percentages relative to the size of the detected region \u2014 in our case, the face. So, if we set the width to 1.0 (100%), the overlay will scale to 100% of the face\u2019s size. This ensures that the eyeglass frames adjust proportionally to fit the face in the image.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"> ...\n  &lt;CldImage\n    className=<span class=\"hljs-string\">\"max-w-sm overflow-hidden shadow-lg\"<\/span>\n    alt={PROFILE_IMAGE_PUBLIC_ID}\n    src={profileImage&#91;<span class=\"hljs-number\">0<\/span>].secure_url}\n    width={<span class=\"hljs-number\">500<\/span>}\n    height={<span class=\"hljs-number\">500<\/span>}\n    <span class=\"hljs-comment\">\/\/ check if an image has been selected to try-on<\/span>\n    {...(searchParamsObj.selectedImagePublicId\n      ? {\n         <span class=\"hljs-attr\">overlays<\/span>: &#91;\n          {\n            <span class=\"hljs-comment\">\/\/ Our search param with the public ID of the image<\/span>\n            <span class=\"hljs-attr\">publicId<\/span>: searchParamsObj.selectedImagePublicId <span class=\"hljs-keyword\">as<\/span> string,\n            <span class=\"hljs-attr\">flags<\/span>: &#91;<span class=\"hljs-string\">\"region_relative\"<\/span>],\n            <span class=\"hljs-attr\">position<\/span>: {\n             <span class=\"hljs-comment\">\/\/ our gravity value<\/span>\n             <span class=\"hljs-attr\">gravity<\/span>: <span class=\"hljs-string\">\"face\"<\/span>,\n            },\n            <span class=\"hljs-attr\">effects<\/span>: &#91;\n             {\n               <span class=\"hljs-attr\">width<\/span>: <span class=\"hljs-string\">\"1.0\"<\/span>,\n              },\n            ],\n           },\n          ],\n        }\n        : <span class=\"hljs-literal\">null<\/span>)}\n    \/&gt;\n  ...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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>If we check our app one more time, we\u2019ll notice that the frames are now fitting right based on the face size:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1738010440\/blog-Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_with_Next.js-6.png\" alt=\"picture with the correctly sized glasses placed\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"989\" height=\"947\"\/><\/p>\n<h2><strong>Conclusion<\/strong><\/h2>\n<p>In this blog post, we built a Next.js app that works smoothly with Cloudinary to create a fun, virtual try-on experience. We used Cloudinary\u2019s face detection to figure out where to place eyeglass frames on profile pictures, and then applied overlays and region-relative transformations to make them fit nicely. We also took advantage of server actions so the page updates automatically when a new image is uploaded, and we set up a user-friendly interface with next-cloudinary to keep everything running smoothly.<\/p>\n<p>If you haven\u2019t already, <a href=\"https:\/\/cloudinary.com\/users\/register_free\">sign up for a free Cloudinary account<\/a> and try creating your live stream today.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":36635,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[113,212,263],"class_list":["post-36633","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-face-detection","tag-next-js","tag-sdk"],"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>Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js<\/title>\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\/virtual-try-on-app-cloudinarys-face-detection-next-js\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-01-27T15:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-05-12T22:51:54+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next-js-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\/virtual-try-on-app-cloudinarys-face-detection-next-js#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js\",\"datePublished\":\"2025-01-27T15:00:00+00:00\",\"dateModified\":\"2025-05-12T22:51:54+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js\"},\"wordCount\":13,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA\",\"keywords\":[\"Face Detection\",\"Next.js\",\"SDK\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js\",\"url\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js\",\"name\":\"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA\",\"datePublished\":\"2025-01-27T15:00:00+00:00\",\"dateModified\":\"2025-05-12T22:51:54+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js\"}]},{\"@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":"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js","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\/virtual-try-on-app-cloudinarys-face-detection-next-js","og_locale":"en_US","og_type":"article","og_title":"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js","og_url":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js","og_site_name":"Cloudinary Blog","article_published_time":"2025-01-27T15:00:00+00:00","article_modified_time":"2025-05-12T22:51:54+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next-js-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\/virtual-try-on-app-cloudinarys-face-detection-next-js#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js","datePublished":"2025-01-27T15:00:00+00:00","dateModified":"2025-05-12T22:51:54+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js"},"wordCount":13,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA","keywords":["Face Detection","Next.js","SDK"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js","url":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js","name":"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA","datePublished":"2025-01-27T15:00:00+00:00","dateModified":"2025-05-12T22:51:54+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/virtual-try-on-app-cloudinarys-face-detection-next-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building a Virtual Try-On App With Cloudinary\u2019s Face Detection and Next.js"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1737158625\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js\/Building_a_Virtual_Try-On_App_with_Cloudinary_s_Face_Detection_and_Next.js.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36633","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=36633"}],"version-history":[{"count":3,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36633\/revisions"}],"predecessor-version":[{"id":37605,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36633\/revisions\/37605"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/36635"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=36633"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=36633"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=36633"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}