{"id":28278,"date":"2022-06-10T08:38:42","date_gmt":"2022-06-10T08:38:42","guid":{"rendered":"http:\/\/create-product-reviews-with-images-in-nextjs"},"modified":"2025-02-22T15:37:36","modified_gmt":"2025-02-22T23:37:36","slug":"create-product-reviews-with-images-in-nextjs","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/","title":{"rendered":"Create Product Reviews with Images in Next.js"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Product reviews are an integral aspect of an e-commerce store\u2019s branding and marketing. They help build trust and loyalty among customers and help drive sales.<\/p>\n<p>In this article, we\u2019ll learn how to create a product review application with Next.js and Cloudinary\u2019s Upload widget.<\/p>\n<h2>Sandbox<\/h2>\n<p>The completed project is on <a href=\"https:\/\/codesandbox.io\/s\/product-review-app-8wzemu\">CodeSandbox<\/a>. Fork it and run the code.<\/p>\n<Codesandbox id=\"product-review-app-8wzemu\" title=\"product-review-app\" \/>\n<p>The source code is also available on <a href=\"https:\/\/github.com\/nefejames\/hackmamba-product-review\">GitHub<\/a>.<\/p>\n<h2>Prerequisites<\/h2>\n<p>To follow along with this article, we need to have:<\/p>\n<ul>\n<li>Knowledge of React and Next.js.<\/li>\n<li>A <a href=\"https:\/\/cloudinary.com\/console\">Cloudinary account<\/a>.<\/li>\n<li>Understanding of Chakra UI, as the demo\u2019s user interface is built with it.<\/li>\n<\/ul>\n<h2>Getting started<\/h2>\n<p>Create a Next.js project by running the command below in our terminal.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">    npx create-next-app product-review-app\n<\/code><\/span><\/pre>\n<p>Next, navigate into the project directory.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">    cd product-review-app\n<\/code><\/span><\/pre>\n<p>Then, run the command below to start the application.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">     npm run dev\n<\/code><\/span><\/pre>\n<p>We will use <a href=\"https:\/\/react-hook-form.com\/\">react-hook-form<\/a> later in the article, so install that also.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">    npm i react-hook-form\n<\/code><\/span><\/pre>\n<h2>What we will create<\/h2>\n<p>We will start by creating the user interface below:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_636B29EE8DB87F096FA2AA0A61175C7265690E76AA42204690BC264D4C8ABE01_1652774749047_Studio_Project.jpeg\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"848\" height=\"744\"\/><\/p>\n<p>Clicking the \u2018Review Product\u2019 button will cause the modal containing the <code>Form<\/code> component to pop up:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_636B29EE8DB87F096FA2AA0A61175C7265690E76AA42204690BC264D4C8ABE01_1652775394520_rsz_screenshot_923.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"829\" height=\"480\"\/><\/p>\n<p>To effectively manage changes in the application, we need to set up state management using the Context API. The application will have two contexts: <code>ImageUploadContext<\/code> and <code>ReviewsContext<\/code>.<\/p>\n<p>The <code>ImageUploadContext<\/code> will hold the image the user uploads, whereas the <code>ReviewsContext<\/code> will contain the user\u2019s comment and the uploaded image.<\/p>\n<h2>Creating the image upload context<\/h2>\n<p>As stated earlier, this context will hold the url of the uploaded image.<\/p>\n<p><code>ImageUploadContext.js<\/code><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" 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> { createContext, useContext, useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n    \n    <span class=\"hljs-keyword\">const<\/span> ImageUploadContext = createContext(<span class=\"hljs-string\">\"\"<\/span>);\n    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> useImageUploadContext = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> useContext(ImageUploadContext);\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\">ImageUploadContextProvider<\/span>(<span class=\"hljs-params\">{ children }<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> &#91;uploadedImgUrl, setUploadedImgUrl] = useState(<span class=\"hljs-literal\">null<\/span>);\n      \n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ImageUploadContext.Provider<\/span> <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">uploadedImgUrl<\/span>, <span class=\"hljs-attr\">setUploadedImgUrl<\/span> }}&gt;<\/span>\n          {children}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ImageUploadContext.Provider<\/span>&gt;<\/span><\/span>\n      );\n    }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here, we do the following:<\/p>\n<ul>\n<li>Import the required dependencies from React.<\/li>\n<li>Create the <code>ImageUploadContext<\/code> that will hold the image URL.<\/li>\n<li>Set up the <code>ImageUploadContextProvider<\/code> that will wrap the root of the application.<\/li>\n<li>Set up the <code>uploadedImgUrl<\/code> state and pass it as the provider\u2019s value along with <code>setUploadedImgUrl<\/code>.<\/li>\n<li>Create and export a custom hook, <code>useImageUploadContext<\/code> from which we will access the data in the <code>ImageUploadContext<\/code>.<\/li>\n<\/ul>\n<h2>Creating the review context<\/h2>\n<p>Though similar in set up to the <code>ImageUploadContext<\/code>, <code>ReviewsContext<\/code> will hold the user\u2019s comment and the uploaded image.<\/p>\n<p><code>ReviewsContext.js<\/code><\/p>\n<pre><code>import { createContext, useContext, useState } from &quot;react&quot;;\n\nconst ReviewsContext = createContext(null);\nexport const useReviewsContext = () =&gt; useContext(ReviewsContext);\n\nexport default function ReviewsContextProvider({ children }) {\n  const [reviews, setReviews] = useState([\n    { reviewText: &quot;first review&quot;, reviewImage: &quot;\/product.webp&quot; },\n  ]);\n  \n  return (\n    &lt;ReviewsContext.Provider value={{ reviews, setReviews }}&gt;\n      {children}\n    &lt;\/ReviewsContext.Provider&gt;\n  );\n}\n<\/code><\/pre>\n<p>Here, we do the following:<\/p>\n<ul>\n<li>Import <code>createContext<\/code>, <code>useContext<\/code>, and <code>useState<\/code> from React.<\/li>\n<li>Create the <code>ReviewsContext<\/code> that will hold the comment and image url.<\/li>\n<li>Set up the <code>ReviewsContextProvider<\/code> that will wrap the root of the application.<\/li>\n<li>Set up the <code>reviews<\/code> state and pass it as the provider\u2019s value along with <code>setReviews<\/code>. The <code>reviews<\/code> state will hold an array of objects, and the objects will have two properties: <code>reviewText<\/code> and <code>reviewImage<\/code>. <code>reviewText<\/code> is the user\u2019s comment, and <code>reviewImage<\/code> is the uploaded image.<\/li>\n<li>Create a custom hook, <code>useReviewsContext<\/code>, from which we will access the data in the <code>ReviewsContext<\/code>.<\/li>\n<\/ul>\n<h2>Creating the <code>ProductCard<\/code> component<\/h2>\n<p>The <code>ProductCard<\/code> component will depict how an e-commerce product card looks in real-world applications.<\/p>\n<p><code>ProductCard.js<\/code><\/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\">    <span class=\"hljs-keyword\">import<\/span> { Box, Heading } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@chakra-ui\/react\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/image\"<\/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\">ProductCard<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Box<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Box<\/span> <span class=\"hljs-attr\">position<\/span>=<span class=\"hljs-string\">\"relative\"<\/span> <span class=\"hljs-attr\">w<\/span>=<span class=\"hljs-string\">\"full\"<\/span> <span class=\"hljs-attr\">h<\/span>=<span class=\"hljs-string\">\"70%\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"\/product.webp\"<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"an img\"<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Box<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Box<\/span> <span class=\"hljs-attr\">display<\/span>=<span class=\"hljs-string\">\"flex\"<\/span> <span class=\"hljs-attr\">alignItems<\/span>=<span class=\"hljs-string\">\"center\"<\/span> <span class=\"hljs-attr\">h<\/span>=<span class=\"hljs-string\">\"90px\"<\/span> <span class=\"hljs-attr\">pl<\/span>=<span class=\"hljs-string\">{6}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Heading<\/span>&gt;<\/span>My Product<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Heading<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Box<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Box<\/span>&gt;<\/span><\/span>\n      );\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>Here, we import <code>Box<\/code> and <code>Heading<\/code> from Chakra UI and <code>Image<\/code> from Next.js and use them to create the card.<\/p>\n<h2>Creating the <code>Review<\/code> component<\/h2>\n<p>The <code>Review<\/code> component will hold the user\u2019s review comment and image.<\/p>\n<p><code>Review.js<\/code><\/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\">\n    <span class=\"hljs-keyword\">import<\/span> { Box, Text, Image } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@chakra-ui\/react\"<\/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\">Review<\/span>(<span class=\"hljs-params\">{ review }<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Box<\/span> <span class=\"hljs-attr\">p<\/span>=<span class=\"hljs-string\">{2}<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Text<\/span> <span class=\"hljs-attr\">fontSize<\/span>=<span class=\"hljs-string\">\"2xl\"<\/span> <span class=\"hljs-attr\">mr<\/span>=<span class=\"hljs-string\">{6}<\/span>&gt;<\/span>\n            {review.reviewText}\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Text<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{review.reviewImage}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{review.reviewImage}<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Box<\/span>&gt;<\/span><\/span>\n      );\n    }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Let\u2019s break down the code above:<\/p>\n<ul>\n<li>We import <code>Box<\/code>, Heading, and <code>Image<\/code> from Chakra UI, and use them to create the comment\u2019s interface.<\/li>\n<li>The component receives a <code>review<\/code> prop, which is an object containing the <code>reviewText<\/code> and <code>reviewImage<\/code> we set up earlier in the <code>ReviewsContext<\/code>.<\/li>\n<li>We import <code>Box<\/code>, <code>Heading<\/code>, and <code>Image<\/code> from Chakra UI and use them to create the comment\u2019s interface.<\/li>\n<li>The component receives a review prop, an object containing the <code>reviewText<\/code> and <code>reviewImage<\/code> we set up earlier in the <code>ReviewsContext<\/code>.<\/li>\n<\/ul>\n<h2>Creating the <code>ReviewsContainer<\/code> component<\/h2>\n<p>The <code>ReviewsContainer<\/code> will contain the different reviews for a product.<\/p>\n<p><code>ReviewsContainer.js<\/code><\/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\">import<\/span> { Box, VStack } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@chakra-ui\/react\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> Review <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@components\/Review\"<\/span>;\n    \n    <span class=\"hljs-keyword\">import<\/span> { useReviewsContext } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"context\/ReviewsContext\"<\/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\">ReviewsContainer<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> { reviews } = useReviewsContext();\n      \n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Box<\/span> <span class=\"hljs-attr\">mt<\/span>=<span class=\"hljs-string\">{10}<\/span> <span class=\"hljs-attr\">rounded<\/span>=<span class=\"hljs-string\">\"md\"<\/span> <span class=\"hljs-attr\">border<\/span>=<span class=\"hljs-string\">\"1px\"<\/span> <span class=\"hljs-attr\">borderColor<\/span>=<span class=\"hljs-string\">\"gray.200\"<\/span> <span class=\"hljs-attr\">p<\/span>=<span class=\"hljs-string\">{3}<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">VStack<\/span> <span class=\"hljs-attr\">spacing<\/span>=<span class=\"hljs-string\">{5}<\/span> <span class=\"hljs-attr\">align<\/span>=<span class=\"hljs-string\">\"flex-start\"<\/span>&gt;<\/span>\n            {reviews.map((review) =&gt; (\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Review<\/span> <span class=\"hljs-attr\">review<\/span>=<span class=\"hljs-string\">{review}<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{review.reviewText}<\/span> \/&gt;<\/span>\n            ))}\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">VStack<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Box<\/span>&gt;<\/span><\/span>\n      );\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<p>Let\u2019s break down the code above:<\/p>\n<ul>\n<li>We import the <code>useReviewsContext<\/code> hook from <code>ReviewsContext<\/code> and access the <code>reviews<\/code>.<\/li>\n<li>\n<code>reviews<\/code> is an array of objects, so we map through it and pass each <code>review<\/code> to the <code>Review<\/code> component we set up earlier.<\/li>\n<\/ul>\n<h2>Creating the <code>FormModal<\/code> component<\/h2>\n<p>The <code>FormModal<\/code> contains the <code>Form<\/code> component.<\/p>\n<p><code>FormModal.js<\/code><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-keyword\">import<\/span> {\n      Modal,\n      ModalOverlay,\n      ModalContent,\n      ModalHeader,\n      ModalBody,\n      ModalCloseButton,\n      useDisclosure,\n      Button,\n      Box,\n    } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@chakra-ui\/react\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> Form <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/Form\"<\/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\">FormModal<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> { isOpen, onOpen, onClose } = useDisclosure();\n      \n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Box<\/span> <span class=\"hljs-attr\">mt<\/span>=<span class=\"hljs-string\">{4}<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{onOpen}<\/span>&gt;<\/span>Review Product<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Modal<\/span> <span class=\"hljs-attr\">isOpen<\/span>=<span class=\"hljs-string\">{isOpen}<\/span> <span class=\"hljs-attr\">onClose<\/span>=<span class=\"hljs-string\">{onClose}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ModalOverlay<\/span> \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ModalContent<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ModalHeader<\/span>&gt;<\/span>Review Product<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ModalHeader<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ModalCloseButton<\/span> \/&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ModalBody<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Form<\/span> <span class=\"hljs-attr\">closeModal<\/span>=<span class=\"hljs-string\">{onClose}<\/span> \/&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ModalBody<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ModalContent<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Modal<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Box<\/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<p>Here, we set up the modal and set the <code>Form<\/code> component as the modal\u2019s content. We also pass the <code>onClose<\/code> method to <code>Form.<\/code> We will need <code>onClose<\/code> to close the modal after submitting the form.<\/p>\n<h2>Creating the <code>Form<\/code> component<\/h2>\n<p>The <code>Form<\/code> component will hold the input field where the user can enter their review, a button that will trigger Cloudinary\u2019s Upload Widget, and another button to submit the form.<\/p>\n<p><code>Form.js<\/code><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-keyword\">import<\/span> { useForm } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-hook-form\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> { FormControl, Input, Stack, Text, Button } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@chakra-ui\/react\"<\/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\">Form<\/span>(<span class=\"hljs-params\">{ closeModal }<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> { handleSubmit, register } = useForm();\n      \n      <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">onSubmit<\/span>(<span class=\"hljs-params\">value<\/span>) <\/span>{\n        <span class=\"hljs-built_in\">console<\/span>.log(value);\n        <span class=\"hljs-comment\">\/\/do something with form data then close the modal<\/span>\n        closeModal();\n      }\n      \n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Stack<\/span> <span class=\"hljs-attr\">spacing<\/span>=<span class=\"hljs-string\">{4}<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Text<\/span>&gt;<\/span>Please leave a review<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Text<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit(onSubmit)}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">FormControl<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span> {<span class=\"hljs-attr\">...register<\/span>(\"<span class=\"hljs-attr\">reviewText<\/span>\")} \/&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Button<\/span>&gt;<\/span>upload image<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">FormControl<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Button<\/span> <span class=\"hljs-attr\">colorScheme<\/span>=<span class=\"hljs-string\">\"blue\"<\/span> <span class=\"hljs-attr\">mt<\/span>=<span class=\"hljs-string\">{3}<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>&gt;<\/span>\n              Submit\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Stack<\/span>&gt;<\/span><\/span>\n      );\n    }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here, we do the following:<\/p>\n<ul>\n<li>Initialize <code>react-hook-form<\/code> and import its <code>useForm<\/code> hook. We will use react-hook-form to track the input field\u2019s value and handle the form submission.<\/li>\n<li>Import <code>handleSubmit<\/code> and <code>register<\/code> from <code>useForm<\/code>.<\/li>\n<li>Register the input field with react-hook-form.<\/li>\n<li>Create an <code>onSubmit<\/code> function where we define how to process the form data; after submitting the form, we call the <code>closeModal<\/code> method.<\/li>\n<\/ul>\n<h2>Bringing it all together<\/h2>\n<p>Having created the required components, let\u2019s bring them into the <code>index.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-keyword\">import<\/span> { Heading } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@chakra-ui\/react\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> ReviewsContainer <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@components\/ReviewsContainer\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> ProductCard <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@components\/ProductCard\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> FormModal <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@components\/FormModal\"<\/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\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Heading<\/span> <span class=\"hljs-attr\">as<\/span>=<span class=\"hljs-string\">\"h1\"<\/span> <span class=\"hljs-attr\">mb<\/span>=<span class=\"hljs-string\">{12}<\/span>&gt;<\/span>\n            A Cool Ecommerce Product Review App\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Heading<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ProductCard<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">FormModal<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ReviewsContainer<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/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<h2>Integrating Cloudinary\u2019s Upload widget<\/h2>\n<p>We\u2019ve created the app\u2019s interface, so now let\u2019s integrate <a href=\"https:\/\/cloudinary.com\/documentation\/upload_widget\">Cloudinary\u2019s Upload widget<\/a>.<\/p>\n<p>Next.js provides a <a href=\"https:\/\/nextjs.org\/docs\/basic-features\/script\">Script component<\/a> that we can use to load third-party scripts in our application. We need the <code>Script<\/code> component to load the upload widget\u2019s script.<\/p>\n<p><code>_app.js<\/code><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-keyword\">import<\/span> { ChakraProvider } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@chakra-ui\/react\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> LayoutWrapper <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@layout\/index\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> Script <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/script\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> ReviewsContextProvider <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@context\/ReviewsContext\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> ImageUploadContextProvider <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@context\/ImageUploadContext\"<\/span>;\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">MyApp<\/span>(<span class=\"hljs-params\">{ Component, pageProps }<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ReviewsContextProvider<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ImageUploadContextProvider<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ChakraProvider<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Script<\/span>\n                <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/upload-widget.cloudinary.com\/global\/all.js\"<\/span>\n                <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\/javascript\"<\/span>\n                <span class=\"hljs-attr\">strategy<\/span>=<span class=\"hljs-string\">\"beforeInteractive\"<\/span>\n              \/&gt;<\/span><span class=\"handlebars\"><span class=\"xml\">\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">LayoutWrapper<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Component<\/span> {<span class=\"hljs-attr\">...pageProps<\/span>} \/&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">LayoutWrapper<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ChakraProvider<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ImageUploadContextProvider<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ReviewsContextProvider<\/span>&gt;<\/span><\/span><\/span><\/span>\n      );\n    }\n    <span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> MyApp;\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>Here, we:<\/p>\n<ul>\n<li>Import <code>Script<\/code> into the <code>_app.js<\/code> file and load the widget\u2019s script.<\/li>\n<li>Wrap our application with the <code>ReviewsContextProvider<\/code> and <code>ImageUploadContextProvider<\/code>.<\/li>\n<\/ul>\n<h2>Initializing the widget<\/h2>\n<p>Having integrated the widget, let\u2019s initialize it back in the <code>Form<\/code> component.<\/p>\n<p><code>Form.js<\/code><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">    <span class=\"hljs-keyword\">import<\/span> { FormControl, Input, Stack, Text, Button } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@chakra-ui\/react\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> { useForm } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react-hook-form\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> { useReviewsContext } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"context\/ReviewsContext\"<\/span>;\n    <span class=\"hljs-keyword\">import<\/span> { useImageUploadContext } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@context\/ImageUploadContext\"<\/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\">Form<\/span>(<span class=\"hljs-params\">{ closeModal }<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">const<\/span> { reviews, setReviews } = useReviewsContext();\n      <span class=\"hljs-keyword\">const<\/span> { uploadedImgUrl, setUploadedImgUrl } = useImageUploadContext();\n      \n      <span class=\"hljs-comment\">\/\/widget initializer<\/span>\n      <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">showWidget<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n        <span class=\"hljs-built_in\">window<\/span>.cloudinary\n          .createUploadWidget(\n            {\n              <span class=\"hljs-attr\">cloudName<\/span>: <span class=\"hljs-string\">\"OUR-ACCOUNT-CLOUD-NAME\"<\/span>,\n              <span class=\"hljs-attr\">uploadPreset<\/span>: <span class=\"hljs-string\">\"ml_default\"<\/span>,\n            },\n            (error, result) =&gt; {\n              <span class=\"hljs-keyword\">if<\/span> (!error &amp;&amp; result &amp;&amp; result.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n                setUploadedImgUrl(result.info.thumbnail_url);\n              }\n              <span class=\"hljs-keyword\">if<\/span> (error) {\n                <span class=\"hljs-built_in\">console<\/span>.log(error);\n              }\n            }\n          )\n          .open();\n      }\n      <span class=\"hljs-keyword\">const<\/span> { handleSubmit, register } = useForm();\n      \n      <span class=\"hljs-comment\">\/\/form submission handler<\/span>\n      <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">onSubmit<\/span>(<span class=\"hljs-params\">value<\/span>) <\/span>{\n        setReviews(&#91;\n          ...reviews,\n          { <span class=\"hljs-attr\">reviewText<\/span>: value.reviewText, <span class=\"hljs-attr\">reviewImage<\/span>: uploadedImgUrl },\n        ]);\n        closeModal();\n      }\n      \n      <span class=\"hljs-keyword\">return<\/span> (\n        <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Stack<\/span> <span class=\"hljs-attr\">spacing<\/span>=<span class=\"hljs-string\">{4}<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Text<\/span>&gt;<\/span>Please leave a review<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Text<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span> <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit(onSubmit)}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">FormControl<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span> {<span class=\"hljs-attr\">...register<\/span>(\"<span class=\"hljs-attr\">reviewText<\/span>\")} \/&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{showWidget}<\/span>&gt;<\/span>upload image<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">FormControl<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Button<\/span> <span class=\"hljs-attr\">colorScheme<\/span>=<span class=\"hljs-string\">\"blue\"<\/span> <span class=\"hljs-attr\">mt<\/span>=<span class=\"hljs-string\">{3}<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>&gt;<\/span>\n              Submit\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Button<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Stack<\/span>&gt;<\/span><\/span>\n      );\n    }\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Let\u2019s break down the code above:<\/p>\n<ul>\n<li>We import <code>ReviewsContext<\/code> and <code>ImageUploadContext<\/code>. We access <code>reviews<\/code> and <code>setReviews<\/code> from <code>ReviewsContext<\/code>, and <code>uploadedImgUrl<\/code> and <code>setUploadedImgUrl<\/code> from <code>ImageUploadContext<\/code>.<\/li>\n<li>We create a <code>showWidget<\/code> function that initializes the widget. We pass <code>showWidget<\/code> to the image button\u2019s <code>onClick<\/code> handler.<\/li>\n<li>We update the <code>onSubmit<\/code> function. Instead of logging the form data to the console, we pass that data to the <code>reviews<\/code> state. Upon form submission, we add a new object to the state. We get the <code>reviewText<\/code> from the input field and the <code>reviewImage<\/code> from the <code>uploadedImgUrl<\/code> state. After that, we close the modal.<\/li>\n<\/ul>\n<p>With this, we have successfully created a product review application.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_636B29EE8DB87F096FA2AA0A61175C7265690E76AA42204690BC264D4C8ABE01_1653262514270_ezgif.com-gif-maker.gif\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"600\" height=\"338\"\/><\/p>\n<h2>Conclusion<\/h2>\n<p>This article taught us to create a product review application with Next.js and Cloudinary\u2019s Upload widget.<\/p>\n<h2>Resources<\/h2>\n<ul>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/product_gallery\">Cloudinary<\/a><a href=\"https:\/\/cloudinary.com\/documentation\/upload_widget\">\u2019s Upload Widget Documentation<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/nextjs.org\/docs\/basic-features\/script\">Next Script Documentation<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28279,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,212,371],"class_list":["post-28278","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-next-js","tag-under-review"],"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>Create Product Reviews with Images in Next.js<\/title>\n<meta name=\"description\" content=\"In this article, we&#039;ll learn how to create a product review application with Next.js and Cloudinary&#039;s Upload widget.\" \/>\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\/guest_post\/create-product-reviews-with-images-in-nextjs\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Create Product Reviews with Images in Next.js\" \/>\n<meta property=\"og:description\" content=\"In this article, we&#039;ll learn how to create a product review application with Next.js and Cloudinary&#039;s Upload widget.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-06-10T08:38:42+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-02-22T23:37:36+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23-jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"960\" \/>\n\t<meta property=\"og:image:height\" content=\"540\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\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\/guest_post\/create-product-reviews-with-images-in-nextjs\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Create Product Reviews with Images in Next.js\",\"datePublished\":\"2022-06-10T08:38:42+00:00\",\"dateModified\":\"2025-02-22T23:37:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/\"},\"wordCount\":8,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"Next.js\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/\",\"name\":\"Create Product Reviews with Images in Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA\",\"datePublished\":\"2022-06-10T08:38:42+00:00\",\"dateModified\":\"2025-02-22T23:37:36+00:00\",\"description\":\"In this article, we'll learn how to create a product review application with Next.js and Cloudinary's Upload widget.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA\",\"width\":960,\"height\":540},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Create Product Reviews with Images in 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\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Create Product Reviews with Images in Next.js","description":"In this article, we'll learn how to create a product review application with Next.js and Cloudinary's Upload widget.","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\/guest_post\/create-product-reviews-with-images-in-nextjs\/","og_locale":"en_US","og_type":"article","og_title":"Create Product Reviews with Images in Next.js","og_description":"In this article, we'll learn how to create a product review application with Next.js and Cloudinary's Upload widget.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-06-10T08:38:42+00:00","article_modified_time":"2025-02-22T23:37:36+00:00","og_image":[{"width":960,"height":540,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23-jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/"},"author":{"name":"","@id":""},"headline":"Create Product Reviews with Images in Next.js","datePublished":"2022-06-10T08:38:42+00:00","dateModified":"2025-02-22T23:37:36+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/"},"wordCount":8,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA","keywords":["Guest Post","Image","Next.js","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/","name":"Create Product Reviews with Images in Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA","datePublished":"2022-06-10T08:38:42+00:00","dateModified":"2025-02-22T23:37:36+00:00","description":"In this article, we'll learn how to create a product review application with Next.js and Cloudinary's Upload widget.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA","width":960,"height":540},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/create-product-reviews-with-images-in-nextjs\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Create Product Reviews with Images in 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":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924954\/Web_Assets\/blog\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23\/be3b68c2697514357b45f5ca12201010bc8fcf6d-960x540-1_28279c1c23.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28278","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\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28278"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28278\/revisions"}],"predecessor-version":[{"id":36966,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28278\/revisions\/36966"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28279"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28278"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28278"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28278"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}