{"id":28229,"date":"2022-03-23T22:24:48","date_gmt":"2022-03-23T22:24:48","guid":{"rendered":"http:\/\/How-to-build-a-Product-Hunt-Clone-with-Next.js-Part-1-of-2"},"modified":"2022-03-23T22:24:48","modified_gmt":"2022-03-23T22:24:48","slug":"how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/","title":{"rendered":"How to build a Product Hunt Clone &#8211; Part 1 of 2"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>In this media jam series, we will build a clone of <a href=\"https:\/\/www.producthunt.com\/\">Product Hunt<\/a>, a website to share and discover new products with <a href=\"https:\/\/nextjs.org\/\">Next.js<\/a>. In addition, we will use <a href=\"https:\/\/auth0.com\/\">Auth0<\/a> for authentication, <a href=\"https:\/\/airtable.com\/\">Airtable<\/a> for storing product data, and <a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a> for storing product images.<\/p>\n<p>In part 1, we will configure the initial Next.js app with Auth0, Cloudinary, and Airtable. Then, we will fetch the data from Airtable and display them on the homepage. We will also discuss how to add new products to our app.<\/p>\n<p>If you want to jump right into the code, check out <a href=\"https:\/\/github.com\/lelouchB\/product-hunt-clone-part-1\">the GitHub Repo here<\/a>.<\/p>\n<p>You can refer to the second part here:  <a href=\"https:\/\/mediajams.dev\/post\/How-to-build-a-Product-Hunt-Clone-with-Next.js-Part-2-of-2\">How to build a Product Hunt Clone with Next.js &#8211; Part 2 of 2<\/a><\/p>\n<h2>CodeSandbox<\/h2>\n<p>Since the preview in the CodeSandbox is embedded inside another page,  you will see the products but you will not be able to log in via the preview. Instead, to explore and play navigate to deployed or live version, <a href=\"https:\/\/qc1kx.sse.codesandbox.io\/\">https:\/\/qc1kx.sse.codesandbox.io\/<\/a>.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/product-hunt-clone-part-1-qc1kx?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=%2Fpages%2Findex.js&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"product-hunt-clone-part-1\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>How to Setup and Install Next.js<\/h2>\n<p>We will use <a href=\"https:\/\/nextjs.org\/docs\/api-reference\/create-next-app\">Create Next App<\/a> to initialize a Next.js project quickly. In your project\u2019s root directory, run the following commands in the terminal.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">npx create-next-app product-hunt-<span class=\"hljs-keyword\">clone<\/span>\ncd product-hunt-<span class=\"hljs-keyword\">clone<\/span>\nnpm run dev\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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 last command, <code>npm run dev<\/code>, will start the development server on your system\u2019s port 3000.<\/p>\n<p>You can close the server by hitting the <strong>CTRL+C<\/strong> in the terminal.<\/p>\n<p>In this tutorial, you will use Tailwind CSS to style your Product Hunt Clone app. Run the following command in the terminal to install Tailwind CSS.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-selector-tag\">-D<\/span> <span class=\"hljs-selector-tag\">tailwindcss<\/span><span class=\"hljs-keyword\">@latest<\/span> postcss@latest autoprefixer@latest @tailwindcss\/forms\nnpx tailwindcss init -p\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The last command will create a <code>tailwind.config.js<\/code> and <code>postcss.config.js<\/code> file in your project\u2019s root directory.<\/p>\n<p>Update the <code>tailwind.config.js<\/code> file like this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-built_in\">module<\/span>.exports = {\n  <span class=\"hljs-attr\">purge<\/span>: {\n    <span class=\"hljs-attr\">content<\/span>: &#91;<span class=\"hljs-string\">\".\/pages\/**\/*.js\"<\/span>, <span class=\"hljs-string\">\".\/components\/**\/*.js\"<\/span>],\n    <span class=\"hljs-attr\">options<\/span>: {},\n  },\n  <span class=\"hljs-attr\">darkMode<\/span>: <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-comment\">\/\/ or 'media' or 'class'<\/span>\n  <span class=\"hljs-attr\">theme<\/span>: {},\n  <span class=\"hljs-attr\">plugins<\/span>: &#91;<span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">\"@tailwindcss\/forms\"<\/span>)],\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<ul>\n<li>\n<code>@tailwindcss\/forms<\/code> &#8211; A <a href=\"https:\/\/github.com\/tailwindlabs\/tailwindcss-forms\">@tailwindcss plugin<\/a> that provides a basic reset for form styles. You will use this to create the form for adding\/updating new products.<\/li>\n<\/ul>\n<p>Update <code>styles\/global.css<\/code> file like this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-keyword\">@tailwind<\/span> base;\n<span class=\"hljs-keyword\">@tailwind<\/span> components;\n<span class=\"hljs-keyword\">@tailwind<\/span> utilities;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><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>You can refer to <a href=\"https:\/\/tailwindcss.com\/docs\/guides\/nextjs#install-tailwind-via-npm\">https:\/\/tailwindcss.com\/docs\/guides\/nextjs#install-tailwind-via-npm<\/a> for detailed instructions on setting up a Next.js app with Tailwind CSS.<\/p>\n<p>In this tutorial, you will use Auth0 for all your authentication and authorization needs. In addition, you will use the <code>@auth0\/nextjs-auth0<\/code> SDK for quick and easy installation and configuration.<\/p>\n<p>Create an account on <a href=\"https:\/\/auth0.com\/signup\">Auth0.com<\/a> if you haven\u2019t already.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985278\/e-603fc45fe6c0b4006873802f\/fejzurvyavwzb1afnmf2.png\" alt=\"Auth0 SignUp\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1519\" height=\"940\"\/><\/p>\n<p>Run the following command in the project\u2019s terminal to install <code>@auth0\/nextjs-auth0<\/code> SDK.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">install<\/span> <span class=\"hljs-keyword\">@auth0<\/span>\/nextjs-auth0\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><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>On your <a href=\"https:\/\/manage.auth0.com\/\">Auth0 dashboard<\/a>, create a <strong>Regular Web Application<\/strong> and name it <strong>Product Hunt Clone<\/strong>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985328\/e-603fc45fe6c0b4006873802f\/ysfazzryr0f6tlnlzrrw.png\" alt=\"Auth0 New Application\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1000\" height=\"779\"\/><\/p>\n<p>Under <strong>Settings\u2013&gt;Application URIs<\/strong>,  configure the following URLs for your application:<\/p>\n<ul>\n<li>\n<strong>Allowed Callback URLs<\/strong>: <a href=\"http:\/\/localhost:3000\/api\/auth\/callback\">http:\/\/localhost:3000\/api\/auth\/callback<\/a>\n<\/li>\n<li>\n<strong>Allowed Logout URLs<\/strong>: <a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000\/<\/a>\n<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985371\/e-603fc45fe6c0b4006873802f\/ugz9jkugsiixdzfqtu5x.png\" alt=\"Configuring Auth0 Application\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1102\" height=\"698\"\/><\/p>\n<p>Remember to click on the <strong>SAVE CHANGES<\/strong> button afterward.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985427\/e-603fc45fe6c0b4006873802f\/lovdtdcban6h6uyeyvv7.png\" alt=\"SAVE CHANGES\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1275\" height=\"123\"\/><\/p>\n<p>In your project, create a new file named <code>.env.local<\/code> by running the following command.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">touch<\/span> <span class=\"hljs-selector-class\">.env<\/span><span class=\"hljs-selector-class\">.local<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">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>Copy and paste the <strong>Client ID<\/strong>, <strong>Client Secret<\/strong>, and <strong>Domain<\/strong> of your application from your Auth0 dashboard inside the <code>.env.local<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\"><code># A long secret value used to encrypt the session cookie\nAUTH0_SECRET='F(5^&amp;wMh&amp;nDvbr!!&amp;jxJ01TYjh4reUcs'\n\n# The base url of your application\nAUTH0_BASE_URL='http:\/\/localhost:3000'\n\n# The url of your Auth0 tenant domain\nAUTH0_ISSUER_BASE_URL='https:\/\/YOUR_AUTH0_DOMAIN.auth0.com'\n\n# Your Auth0 application's Client ID\nAUTH0_CLIENT_ID='YOUR_AUTH0_CLIENT_ID'\n\n# Your Auth0 application's Client Secret\nAUTH0_CLIENT_SECRET='YOUR_AUTH0_CLIENT_SECRET'\n<\/code><\/pre>\n<p>Create a <a href=\"https:\/\/nextjs.org\/docs\/api-routes\/dynamic-api-routes\">Dynamic API Route handler<\/a> at <code>\/pages\/api\/auth\/[...auth0].js<\/code> by running the following command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">cd pages\/api\nmkdir auth\ntouch auth\/&#91;...auth0].js\n<\/code><\/span><\/pre>\n<p>Add following code to the <code>[...auth0].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> { handleAuth } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@auth0\/nextjs-auth0'<\/span>;\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> handleAuth();\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>This will create the following urls: <code>\/api\/auth\/login<\/code>, <code>\/api\/auth\/callback<\/code>, <code>\/api\/auth\/logout<\/code> and <code>\/api\/auth\/me<\/code>.<\/p>\n<p>Wrap your <code>pages\/_app.js<\/code> component in the <code>UserProvider<\/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\"><span class=\"hljs-comment\">\/\/ pages\/_app.js<\/span>\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">\"..\/styles\/globals.css\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { UserProvider } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/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\">UserProvider<\/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\">UserProvider<\/span>&gt;<\/span><\/span>\n  );\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>You will use <a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a>, a media management platform for web and mobile developers, to store the media related to products like images or thumbnails.<\/p>\n<p>Create a free account on <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">Cloudinary<\/a> if you haven\u2019t already.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985491\/e-603fc45fe6c0b4006873802f\/jk3fbfugihlmghqfvw8h.png\" alt=\"Cloudinary Registration\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1524\" height=\"1336\"\/><\/p>\n<p>After creating the account, head over to your dashboard and create a <strong>Product Hunt Clone<\/strong> folder; to store the product\u2019s images, take note of your \u00a0Cloudinary\u2019s <strong>Cloud name<\/strong>, <strong>API Key<\/strong>, and <strong>API Secret<\/strong>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985549\/e-603fc45fe6c0b4006873802f\/tcrgwwrizzs7xgwdkvxu.png\" alt=\"Cloudinary API Keys\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1330\" height=\"385\"\/><\/p>\n<p>Paste this <strong>Cloud name<\/strong>, <strong>API Key<\/strong> and <strong>API Secret<\/strong> in your <code>.env.local<\/code> file along with other credentials.<\/p>\n<pre class=\"js-syntax-highlighted\"><code>NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME = 'YOUR_CLOUDINARY_CLOUD_NAME'\nCLOUDINARY_API_KEY = 'YOUR_CLOUDINARY_API_KEY'\nCLOUDINARY_API_SECRET = 'YOUR_CLOUDINARY_API_SECRET'\n<\/code><\/pre>\n<p>Next.js has built-in support for loading environment variables from <code>.env.local<\/code> into <code>process.env<\/code>.<\/p>\n<p>By default, all environment variables loaded through <code>.env.local<\/code> are only available in the Node.js environment. This means that they won\u2019t be exposed to the browser. However, using the <code>NEXT_PUBLIC_<\/code> prefix exposes the environment variable to the browser. You can read more about it <a href=\"https:\/\/nextjs.org\/docs\/basic-features\/environment-variables\">here<\/a>.<\/p>\n<p>Run the following command in the terminal to install the <a href=\"https:\/\/cloudinary.com\/documentation\/react_integration#installation_and_setup\">Cloudinary React SDK<\/a> and Cloudinary Node.js SDK.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install cloudinary-react cloudinary\n<\/code><\/span><\/pre>\n<p>The next step is to configure your product\u2019s database, i.e., <a href=\"https:\/\/airtable.com\/\">Airtable<\/a>. Then, finally, create an Airtable account, if you haven\u2019t already.<\/p>\n<p>After creating an account, create a <code>base<\/code> named <strong>Product Hunt Clone<\/strong>, and inside this base, create a table named <code>products<\/code> with the following schema.<\/p>\n<ul>\n<li>\n<code>Name<\/code> &#8211; Name of the product &#8211; Single line text<\/li>\n<li>\n<code>Description<\/code> &#8211; Short description of the product &#8211; Long text<\/li>\n<li>\n<code>Link<\/code> &#8211; Link to the product website &#8211; URL<\/li>\n<li>\n<code>Sub<\/code> &#8211; Unique Identifier to identify the creator of product &#8211; Single line text<\/li>\n<li>\n<code>PublicId<\/code> &#8211; Public Id of the Image stored on Cloudinary &#8211; Single line text<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985595\/e-603fc45fe6c0b4006873802f\/zn8ihozjdmlncv2iihve.png\" alt=\"Airtable - Product Hunt Clone base\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"859\"\/><\/p>\n<p>After creating the schema, make sure to add at least one entry to the table. You can copy the sample data from <a href=\"https:\/\/airtable.com\/shrO6QCi02vl2cL6H\">here<\/a>.  In the next part, you will update the schema to add upvotes of the product.<\/p>\n<p>Head over to your <a href=\"https:\/\/airtable.com\/account\">Airtable account settings<\/a> and copy your API Key.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985638\/e-603fc45fe6c0b4006873802f\/rp8yfcv1psromkczgou4.png\" alt=\"Airtable API Key\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1108\" height=\"344\"\/><\/p>\n<p>Head over to <a href=\"https:\/\/airtable.com\/api\">https:\/\/airtable.com\/api<\/a> and choose the <strong>Product Hunt Clone<\/strong> base.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985674\/e-603fc45fe6c0b4006873802f\/ddiwxysivthxan59mtvp.png\" alt=\"Airtable Base Docs\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"860\"\/><\/p>\n<p>On your base\u2019s API documentation page, copy the base ID of the <strong>Product Hunt Clone<\/strong> base.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985716\/e-603fc45fe6c0b4006873802f\/t9udabtbwjqocrulz7q1.png\" alt=\"Airtbale Base ID\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"860\"\/>\nPaste both Airtable API Key and Airtable Base ID in your <code>.env.local<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\"><code>AIRTABLE_API_KEY = ''\nAIRTABLE_BASE_ID = ''\n<\/code><\/pre>\n<p>You can refer to the <code>.env.local<\/code> file of this project <a href=\"https:\/\/github.com\/lelouchB\/product-hunt-clone-part-1\/blob\/main\/.env.example\">here<\/a>.<\/p>\n<p>Run the following command in the terminal to install the <a href=\"https:\/\/www.npmjs.com\/package\/airtable\">Airtable JS official API client<\/a>, <code>airtable<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install airtable\n<\/code><\/span><\/pre>\n<p>There are a few other dependencies that you will need in this project. Run the following command to install them.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install react-dropzone \n<\/code><\/span><\/pre>\n<ul>\n<li>\n<code>react-dropzone<\/code> &#8211; React hook to create a HTML5-compliant drag-drop zone for files. You can read more about this package <a href=\"https:\/\/react-dropzone.js.org\/\">here<\/a>.<\/li>\n<\/ul>\n<h2>How To Create the Navbar Component<\/h2>\n<p>In this section, you will create the Navbar component, which will display the application name and a Login\/SignUp button.<\/p>\n<p>Run the following command in the project\u2019s root directory to create the <code>Navbar.js<\/code> file inside the <code>components<\/code> directory.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">mkdir<\/span> <span class=\"hljs-selector-tag\">components<\/span>\n<span class=\"hljs-selector-tag\">cd<\/span> <span class=\"hljs-selector-tag\">components<\/span>\n<span class=\"hljs-selector-tag\">touch<\/span> <span class=\"hljs-selector-tag\">Navbar<\/span><span class=\"hljs-selector-class\">.js<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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>Add the following code to the <code>Navbar.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/link\"<\/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\">Navbar<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { user} = useUser();\n \n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative bg-white\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-7xl mx-auto px-2 sm:px-3\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col md:flex-row justify-between items-center border-b-2 border-gray-100 py-6  md:space-x-10 \"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex  justify-start lg:w-0 lg:flex-1\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n                  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-10 w-auto sm:h-12\"<\/span>\n                  <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/singhashutoshk\/image\/upload\/v1622888633\/product-hunt-clone\/b2beawbhpjcy7kwnipo4.svg\"<\/span>\n                  <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"\"<\/span>\n                \/&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"leading-normal text-3xl md:text-4xl font-serif tracking-tight font-bold ml-1 text-gray-800\"<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block text-purple-600 xl:inline\"<\/span>&gt;<\/span>\n                    Product Hunt Clone\n                  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          {user ? (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/products\/insert\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"inline-flex font-medium text-gray-500 bg-transparent  text-indigo-500 font-semibold px-3 py-2 my-1 border border-indigo-500 rounded\"<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span>\n                  <span class=\"hljs-attr\">xmlns<\/span>=<span class=\"hljs-string\">\"http:\/\/www.w3.org\/2000\/svg\"<\/span>\n                  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-6 w-6 hover:bg-white\"<\/span>\n                  <span class=\"hljs-attr\">fill<\/span>=<span class=\"hljs-string\">\"none\"<\/span>\n                  <span class=\"hljs-attr\">viewBox<\/span>=<span class=\"hljs-string\">\"0 0 24 24\"<\/span>\n                  <span class=\"hljs-attr\">stroke<\/span>=<span class=\"hljs-string\">\"#6366F1\"<\/span>\n                &gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">path<\/span>\n                    <span class=\"hljs-attr\">strokeLinecap<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeLinejoin<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeWidth<\/span>=<span class=\"hljs-string\">{2}<\/span>\n                    <span class=\"hljs-attr\">d<\/span>=<span class=\"hljs-string\">\"M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"<\/span>\n                  \/&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n                Add Product\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n          ) : null}\n\n          {user ? (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\" flex items-center justify-end md:flex-1 lg:w-0\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/user\/profile\"<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"whitespace-nowrap text-base font-medium text-gray-500 hover:text-gray-900\"<\/span>&gt;<\/span>\n                  My Profile\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/api\/auth\/logout\"<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"ml-8 whitespace-nowrap inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-red-400 hover:bg-red-600\"<\/span>&gt;<\/span>\n                  Log Out\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          ) : (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/api\/auth\/login\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"ml-8 whitespace-nowrap inline-flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-purple-600 hover:bg-purple-700\"<\/span>&gt;<\/span>\n                Login\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n          )}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\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>You start by importing <code>useUser()<\/code> hook from <code>@auth0\/nextjs-auth0<\/code>. The <code>useUser()<\/code> hook will give you the <code>UserProfile<\/code> object from the server-side session by requesting it from the <code>HandleProfile<\/code> API Route handler.<\/p>\n<p>The <code>user<\/code> object from <code>userUser()<\/code> hook contains the current user\u2019s information and defaults to <code>undefined<\/code> if there is no user. Here is how it looks like.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json shcb-wrap-lines\">{\n  <span class=\"hljs-attr\">\"nickname\"<\/span>: <span class=\"hljs-string\">\"lelouchB\"<\/span>,\n  <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"ASHUTOSH KUMAR SINGH\"<\/span>,\n  <span class=\"hljs-attr\">\"picture\"<\/span>: <span class=\"hljs-string\">\"https:\/\/avatars.githubusercontent.com\/u\/45850882?v=4\"<\/span>,\n  <span class=\"hljs-attr\">\"updated_at\"<\/span>: <span class=\"hljs-string\">\"2021-06-06T05:52:11.117Z\"<\/span>,\n  <span class=\"hljs-attr\">\"email\"<\/span>: <span class=\"hljs-string\">\"ashutoshksingh@outlook.com\"<\/span>,\n  <span class=\"hljs-attr\">\"sub\"<\/span>: <span class=\"hljs-string\">\"github|45960883\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The <code>sub<\/code> in the <code>user<\/code> object is the unique identifier to identify the creator of the product.<\/p>\n<p>In the <code>Navbar.js<\/code> file, you use this <code>user<\/code> object with the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/Conditional_Operator\">ternary operator<\/a> to either show the <code>Login<\/code> button or the <code>Logout<\/code> button. The <code>Login<\/code> and <code>Logout<\/code> buttons are links with <code>href<\/code> equal to <code>\/api\/auth\/login<\/code> and <code>\/api\/auth\/logout<\/code> routes.<\/p>\n<p>Along with the <code>Logout<\/code> button, you show a link, <code>My Profile<\/code>, which takes the user to <code>\/user\/profile<\/code> page. This page shows the user their name and their products. Also, in the Navbar, you create a link to the <code>\/products\/insert<\/code> page. This page contains a form to add new products to the database.<\/p>\n<p>You will create the <code>\/user\/profile<\/code> page in part 2 and <code>\/products\/insert<\/code> page later in this jam.<\/p>\n<p>The next step is to import and use the <code>Navbar<\/code> component to <code>index.js<\/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\"><span class=\"hljs-comment\">\/\/ index.js<\/span>\n<span class=\"hljs-keyword\">import<\/span> Head <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/head\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useEffect, useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Navbar <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/Navbar\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/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\">const<\/span> { user, error, isLoading } = useUser();\n\n  <span class=\"hljs-keyword\">if<\/span> (isLoading) {\n    <span class=\"hljs-keyword\">return<\/span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-auto my-64 text-gray-800 text-center text-3xl\"<\/span>&gt;<\/span>\n        Loading\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n    );\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"contianer px-3\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">title<\/span>&gt;<\/span>Product Hunt Clone<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">title<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"icon\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/favicon.ico\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/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-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>Start the development server by running the <code>npm run dev<\/code> command. Next, navigate to <a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000\/<\/a> in your browser.<\/p>\n<p>Here is how your app will look.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985781\/e-603fc45fe6c0b4006873802f\/ybwugmiz2mb1xsqfbs87.png\" alt=\"Navbar - Without Login\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"167\"\/><\/p>\n<p>Click on the <code>Login<\/code> button and either log in with Google OAuth or register a new user on the Auth0 Login Page.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985842\/e-603fc45fe6c0b4006873802f\/uy52lknxvz6jx3lkipn1.png\" alt=\"Auth0 Login Page\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"860\"\/><\/p>\n<p>Here is how the Navbar will look after you have logged in.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985898\/e-603fc45fe6c0b4006873802f\/pctj1xnt9bfowflwqjao.png\" alt=\"Navbar - Logged In\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"140\"\/><\/p>\n<p>The next step is to create a container or a Card component that will be used to display the products with their name, image, and description.<\/p>\n<h2>How To Create the Product Component<\/h2>\n<p>In this section, you will create the Product component inside the <code>components<\/code> directory.<\/p>\n<p>Run the following command to create the <code>Product<\/code> component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">cd<\/span> <span class=\"hljs-selector-tag\">components<\/span>\n<span class=\"hljs-selector-tag\">touch<\/span> <span class=\"hljs-selector-tag\">Product<\/span><span class=\"hljs-selector-class\">.js<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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>Add the following command to the <code>Product.js<\/code> file.<\/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-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Image, Transformation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary-react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/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\">Product<\/span>(<span class=\"hljs-params\">{\n  name,\n  id,\n  publicId,\n  description,\n  link,\n  check,\n}<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n  <span class=\"hljs-keyword\">const<\/span> { user, error, isLoading } = useUser();\n\n\t<span class=\"hljs-keyword\">const<\/span> deleteThisProduct = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n\t    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/deleteProduct\"<\/span>, {\n\t      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"DELETE\"<\/span>,\n\t      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ id, publicId }),\n\t      <span class=\"hljs-attr\">headers<\/span>: {\n\t        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n\t      },\n\t    });\n\t    router.reload();\n\t  };\n\n\t  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-md mx-auto my-4 bg-white rounded-xl shadow-xl overflow-hidden md:max-w-2xl\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"md:flex\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"md:flex-shrink-0\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-48 w-full object-cover md:w-48\"<\/span>\n            <span class=\"hljs-attr\">publicId<\/span>=<span class=\"hljs-string\">{publicId}<\/span>\n            <span class=\"hljs-attr\">cloudName<\/span>=<span class=\"hljs-string\">{process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}<\/span>\n            <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{name}<\/span>\n            <span class=\"hljs-attr\">format<\/span>=<span class=\"hljs-string\">\"webp\"<\/span>\n            <span class=\"hljs-attr\">secure<\/span>\n          &gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Transformation<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">\"800\"<\/span>  <span class=\"hljs-attr\">gravity<\/span>=<span class=\"hljs-string\">\"auto\"<\/span> <span class=\"hljs-attr\">crop<\/span>=<span class=\"hljs-string\">\"fill\"<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Image<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"p-8 w-full\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex justify-between  items-start\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span>\n              <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{link}<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block mt-1 text-xl font-semibold leading-tight font-medium  text-indigo-700 hover:underline\"<\/span>\n            &gt;<\/span>\n              {name}\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-2 text-gray-600 w-10\/12\"<\/span>&gt;<\/span>{description} <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n          {check &amp;&amp; (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex justify-end\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n                <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-1 h-6 w-6\"<\/span>\n                <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span>\n                  router.push({\n                    pathname: \"\/update\/&#91;id]\",\n                    query: {\n                      id: id,\n                    },\n                  })\n                }\n              &gt;\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span>\n                  <span class=\"hljs-attr\">xmlns<\/span>=<span class=\"hljs-string\">\"http:\/\/www.w3.org\/2000\/svg\"<\/span>\n                  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-6 w-6\"<\/span>\n                  <span class=\"hljs-attr\">fill<\/span>=<span class=\"hljs-string\">\"none\"<\/span>\n                  <span class=\"hljs-attr\">viewBox<\/span>=<span class=\"hljs-string\">\"0 0 24 24\"<\/span>\n                  <span class=\"hljs-attr\">stroke<\/span>=<span class=\"hljs-string\">\"gray\"<\/span>\n                &gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">path<\/span>\n                    <span class=\"hljs-attr\">strokeLinecap<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeLinejoin<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeWidth<\/span>=<span class=\"hljs-string\">{2}<\/span>\n                    <span class=\"hljs-attr\">d<\/span>=<span class=\"hljs-string\">\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"<\/span>\n                  \/&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/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\">{deleteThisProduct}<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"px-1\"<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span>\n                  <span class=\"hljs-attr\">xmlns<\/span>=<span class=\"hljs-string\">\"http:\/\/www.w3.org\/2000\/svg\"<\/span>\n                  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-6 w-6\"<\/span>\n                  <span class=\"hljs-attr\">fill<\/span>=<span class=\"hljs-string\">\"none\"<\/span>\n                  <span class=\"hljs-attr\">viewBox<\/span>=<span class=\"hljs-string\">\"0 0 24 24\"<\/span>\n                  <span class=\"hljs-attr\">stroke<\/span>=<span class=\"hljs-string\">\"gray\"<\/span>\n                &gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">path<\/span>\n                    <span class=\"hljs-attr\">strokeLinecap<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeLinejoin<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeWidth<\/span>=<span class=\"hljs-string\">{2}<\/span>\n                    <span class=\"hljs-attr\">d<\/span>=<span class=\"hljs-string\">\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"<\/span>\n                  \/&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          )}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\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>Most of the above code is Tailwind CSS styling. You use the props passed to this component and display them on the page. Here are the props that are passed to this component.<\/p>\n<ul>\n<li>\n<code>name<\/code> &#8211; Name of the Product<\/li>\n<li>\n<code>id<\/code> &#8211; Id of the product<\/li>\n<li>\n<code>publicId<\/code> &#8211; <code>public_id<\/code> of the image, used with the Image component from <code>cloudinary-react<\/code> to display the product\u2019s image.<\/li>\n<li>\n<code>description<\/code> &#8211; Short description of the Product.<\/li>\n<li>\n<code>link<\/code> &#8211; URL to the product.<\/li>\n<li>\n<code>check<\/code> &#8211; A boolean. You check whether the current user is the product\u2019s creator or not and pass either <code>true<\/code> or <code>false<\/code>. It compares the <code>sub<\/code> in the product with the <code>sub<\/code> of the current user.<\/li>\n<\/ul>\n<p>The above code uses the <code>check<\/code> boolean to show the <code>edit<\/code> and <code>delete<\/code> buttons to the creator of the product. The <code>edit<\/code> button takes the user to <code>\/update\/[id]<\/code> page. The <code>id<\/code> of the product is passed in the query of the route with the <code>router<\/code> object. You can read more about the <code>router<\/code> object <a href=\"https:\/\/nextjs.org\/docs\/api-reference\/next\/router\">here<\/a>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-1 h-6 w-6\"<\/span>\n  <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span>\n    router.push({\n      pathname: \"\/update\/&#91;id]\",\n      query: {\n        id: id,\n      },\n    })\n  }\n&gt;\n\t  \/\/ SVG Code\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><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>On the <code>edit<\/code> or <code>\/update\/[id]<\/code> page, you will use the product\u2019s <code>id<\/code> to update it in the Airtable database.<\/p>\n<p>When the <code>delete<\/code> button is clicked, the <code>deleteThisProduct<\/code> is triggered, sending a <code>DELETE<\/code> request to <code>\/api\/deleteProduct<\/code> route.<\/p>\n<p>In the request body of this <code>DELETE<\/code> request, you send both the <code>id<\/code> and <code>publicId<\/code> of the image. When a product is deleted, its data should be deleted from both Airtable and Cloudinary.<\/p>\n<p>You will create this API route in the last section.<\/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\"><span class=\"hljs-keyword\">const<\/span> deleteThisProduct = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n\t    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/deleteProduct\"<\/span>, {\n\t      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"DELETE\"<\/span>,\n\t      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ id, publicId }),\n\t      <span class=\"hljs-attr\">headers<\/span>: {\n\t        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n\t      },\n\t    });\n\t    router.reload();\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>The <code>router.reload()<\/code> functions reloads the current URL and is equivalent to <code>window.location.reload()<\/code>.<\/p>\n<p>Here is how the <code>Product<\/code> component looks like.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622985994\/e-603fc45fe6c0b4006873802f\/nfh9brhyoma4km7el771.png\" alt=\"Product Card - Desktop\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"866\" height=\"265\"\/><\/p>\n<h2>How To Fetch and Display the Products<\/h2>\n<p>In this section, you will fetch the products from Airtable and display them on the app using the <code>Product<\/code> component.<\/p>\n<p>Create a new file named <code>api.js<\/code> inside the <code>lib<\/code> directory by running the following command.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">mkdir<\/span> <span class=\"hljs-selector-tag\">lib<\/span>\n<span class=\"hljs-selector-tag\">cd<\/span> <span class=\"hljs-selector-tag\">lib<\/span>\n<span class=\"hljs-selector-tag\">touch<\/span> <span class=\"hljs-selector-tag\">api<\/span><span class=\"hljs-selector-class\">.js<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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>You will create all the functions to fetch, update or delete records from Airtable in this file.<\/p>\n<p>Add the following code to <code>api.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-18\" 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> Airtable = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">\"airtable\"<\/span>);\n\n<span class=\"hljs-keyword\">const<\/span> base = <span class=\"hljs-keyword\">new<\/span> Airtable({ <span class=\"hljs-attr\">apiKey<\/span>: process.env.AIRTABLE_API_KEY }).base(\n  process.env.AIRTABLE_BASE_ID\n);\n\n<span class=\"hljs-keyword\">const<\/span> table = base(<span class=\"hljs-string\">\"products\"<\/span>);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getAllProducts = <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> table\n    .select({\n      <span class=\"hljs-attr\">view<\/span>: <span class=\"hljs-string\">\"Grid view\"<\/span>,\n    })\n    .firstPage();\n  <span class=\"hljs-keyword\">return<\/span> data.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">record<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">id<\/span>: record.id, ...record.fields };\n  });\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You start by creating an instance of <code>airtable<\/code> and then access the <code>Product Hunt Clone<\/code> base bypassing the base ID and API key stored in the <code>.env.local<\/code> file to this instance.<\/p>\n<p>You then access the table <code>products<\/code> bypassing <code>&quot;products&quot;<\/code> to the <code>Product Hunt Clone<\/code> base.<\/p>\n<p>You create and export an asynchronous function named <code>getAllProducts<\/code> which fetches all the products using the <code>.select({view:&quot;Grid view&quot;})<\/code> and <code>.firstPage()<\/code> methods to be displayed on the landing page. You can read more about these methods on your base\u2019s documentation page from where you copied the base ID.<\/p>\n<p>Here is how the data array looks like.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">&#91;\n\tRecord {\n\t\t<span class=\"hljs-attr\">_table<\/span>: Table {\n\t\t\t<span class=\"hljs-attr\">_base<\/span>: &#91;Base],\n\t\t\t<span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-literal\">null<\/span>,\n\t\t\t<span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'products'<\/span>,\n\t\t\t<span class=\"hljs-comment\">\/\/ Functions<\/span>\n\t\t},\n\t\t<span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-string\">'rec6Jv8UmrQk1ZoMc'<\/span>,\n\t\t<span class=\"hljs-attr\">_rawJson<\/span>: {\n\t\t\t<span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-string\">'rec6Jv8UmrQk1ZoMc'<\/span>,\n\t\t\t<span class=\"hljs-attr\">fields<\/span>: &#91;<span class=\"hljs-built_in\">Object<\/span>],\n\t\t\t<span class=\"hljs-attr\">createdTime<\/span>: <span class=\"hljs-string\">'2021-06-04T07:37:01.000Z'<\/span>\n\t\t},\n\t\t<span class=\"hljs-attr\">fields<\/span>: {\n\t\t\t<span class=\"hljs-attr\">Sub<\/span>: <span class=\"hljs-string\">'google-oauth2|1053578586999545372338041'<\/span>,\n\t\t\t<span class=\"hljs-attr\">Description<\/span>: <span class=\"hljs-string\">'Final Space API is a RESTful API based on the animated television show Final Space. '<\/span>,\n\t\t\t<span class=\"hljs-attr\">Name<\/span>: <span class=\"hljs-string\">'Final Space API'<\/span>,\n\t\t\t<span class=\"hljs-attr\">PublicId<\/span>: <span class=\"hljs-string\">'product-hunt-clone\/qfadnlboijexxvuqan08'<\/span>,\n\t\t\t<span class=\"hljs-attr\">Link<\/span>: <span class=\"hljs-string\">'https:\/\/finalspaceapi.com\/'<\/span>\n\t\t},\n\t\t<span class=\"hljs-comment\">\/\/ Functions<\/span>\n\t}\n]\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You map over the <code>data<\/code> array to destructure each record in the array to return only the id and the fields.<\/p>\n<p>Here is how the <code>array<\/code> looks like after destructuring. Less messy and easy to use.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">&#91;{\n\t<span class=\"hljs-attr\">id<\/span>: <span class=\"hljs-string\">'rec6Jv8UmrQk1ZoMc'<\/span>,\n\t<span class=\"hljs-attr\">Sub<\/span>: <span class=\"hljs-string\">'google-oauth2|1053578586999545372338041'<\/span>,\n\t<span class=\"hljs-attr\">Description<\/span>: <span class=\"hljs-string\">'Final Space API is a RESTful API based on the animated television show Final Space. '<\/span>,\n\t<span class=\"hljs-attr\">Name<\/span>: <span class=\"hljs-string\">'Final Space API'<\/span>,\n\t<span class=\"hljs-attr\">PublicId<\/span>: <span class=\"hljs-string\">'product-hunt-clone\/qfadnlboijexxvuqan08'<\/span>,\n\t<span class=\"hljs-attr\">Link<\/span>: <span class=\"hljs-string\">'https:\/\/finalspaceapi.com\/'<\/span>\n}]\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><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 next step is to import and use the <code>getAllProducts()<\/code> function in <code>index.js<\/code> file.<\/p>\n<p>Update <code>index.js<\/code> file like this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-21\" 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> Head <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/head\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Product <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/Product\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Navbar <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/Navbar\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getAllProducts } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/lib\/api\"<\/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\">{ products }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { user, error, isLoading } = useUser();\n\n  <span class=\"hljs-keyword\">if<\/span> (isLoading) {\n    <span class=\"hljs-keyword\">return<\/span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-auto my-64 text-gray-800 text-center text-3xl\"<\/span>&gt;<\/span>\n        Loading\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n    );\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"contianer px-3\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">title<\/span>&gt;<\/span>Product Hunt Clone<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">title<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"icon\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/favicon.ico\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-4\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n      {products.length&gt;0 &amp;&amp;\n        products.map((product) =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Product<\/span>\n            <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{product.id}<\/span>\n            <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">{product.id}<\/span>\n            <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">{product.Name}<\/span>\n            <span class=\"hljs-attr\">link<\/span>=<span class=\"hljs-string\">{product.Link}<\/span>\n            <span class=\"hljs-attr\">publicId<\/span>=<span class=\"hljs-string\">{product.PublicId}<\/span>\n            <span class=\"hljs-attr\">description<\/span>=<span class=\"hljs-string\">{product.Description}<\/span>\n            <span class=\"hljs-attr\">check<\/span>=<span class=\"hljs-string\">{user<\/span> &amp;&amp; <span class=\"hljs-attr\">product.Sub<\/span> === <span class=\"hljs-string\">user.sub<\/span> ? <span class=\"hljs-attr\">true<\/span> <span class=\"hljs-attr\">:<\/span> <span class=\"hljs-attr\">false<\/span>}\n          \/&gt;<\/span>\n        ))}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getServerSideProps<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> products = <span class=\"hljs-keyword\">await<\/span> getAllProducts();\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">props<\/span>: { products },\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><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 code, you create and export  async function <code>getServerSideProps()<\/code>. This function uses the <code>getAllProducts()<\/code> function to fetch the products from Airtable and pass it as props to the <code>Home<\/code> component. You can read more about <code>getServerSideProps()<\/code> function <a href=\"https:\/\/nextjs.org\/docs\/basic-features\/data-fetching#getserversideprops-server-side-rendering\">here<\/a>.<\/p>\n<p>You map over the <code>products<\/code> array and pass its data to the <code>Product<\/code> component. Notice the <code>check<\/code> prop of the <code>Product<\/code> component. You check whether the <code>sub<\/code> of product is equal to the <code>sub<\/code> of the current user.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">check={user &amp;&amp; product.Sub === user.sub ? <span class=\"hljs-literal\">true<\/span> : <span class=\"hljs-literal\">false<\/span>}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><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>Navigate to <a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000\/<\/a> in your browser. Here is how your app will look.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622986321\/e-603fc45fe6c0b4006873802f\/fv5igybbmaru1v2ekana.png\" alt=\"Landing Page\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"860\"\/><\/p>\n<h2>How To Add New Products<\/h2>\n<p>In this section, you will create the form for adding new products. Run the following command to create the <code>product\/insert<\/code> page.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">cd<\/span> <span class=\"hljs-selector-tag\">pages<\/span>\n<span class=\"hljs-selector-tag\">mkdir<\/span> <span class=\"hljs-selector-tag\">products<\/span>\n<span class=\"hljs-selector-tag\">cd<\/span> <span class=\"hljs-selector-tag\">products<\/span>\n<span class=\"hljs-selector-tag\">touch<\/span> <span class=\"hljs-selector-tag\">insert<\/span><span class=\"hljs-selector-class\">.js<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><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<blockquote>\n<p>Before proceeding further, copy the code for the <code>insert.js<\/code> file from <a href=\"https:\/\/github.com\/lelouchB\/product-hunt-clone-part-1\/blob\/main\/pages\/products\/insert.js\">GitHub<\/a> and paste it to the <code>insert.js<\/code> file.<\/p>\n<p>Raw Link: <a href=\"https:\/\/raw.githubusercontent.com\/lelouchB\/product-hunt-clone-part-1\/main\/pages\/products\/insert.js\">https:\/\/raw.githubusercontent.com\/lelouchB\/product-hunt-clone-part-1\/main\/pages\/products\/insert.js<\/a><\/p>\n<\/blockquote>\n<p>You might notice that in the <code>insert.js<\/code> file, you are exporting the <code>AddNewProduct<\/code> component with the <code>withPageAuthRequired()<\/code> function. This function protects the page it is used with and redirects any anonymous user to the login page.<\/p>\n<p>You will need to be logged in to access the <code>\/products\/insert<\/code> page in simple words, which makes sense to discourage spam inserts in the database. You can read more about this method <a href=\"https:\/\/auth0.github.io\/nextjs-auth0\/modules\/frontend_with_page_auth_required.html\">here<\/a>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> withPageAuthRequired(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">AddNewProduct<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\t\t<span class=\"hljs-comment\">\/\/ Code<\/span>\n\t}\n)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><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 <code>insert.js<\/code> file, you are ultimately making a POST request to the <code>\/api\/createProduct<\/code> API route with the product\u2019s name, link, description, and the data URL of the image.<\/p>\n<p>The next step is to accept data from the user. You can split this task into two parts. The first is to store the textual data, i.e., <code>name<\/code>, <code>link<\/code>, <code>description<\/code>, and the second, to create a Drag N Drop area to accept the image and then create a data URL of that image. You will also show the preview of the image to the user.<\/p>\n<ul>\n<li>You initialize three states, namely <code>name<\/code>, <code>link<\/code>, and <code>description<\/code>. These states stores the name, link, and description of the product using the <code>value<\/code> property and <code>onChange()<\/code> event handler.<\/li>\n<\/ul>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n  <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span>\n  <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"product_name\"<\/span>\n  <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"product_name\"<\/span>\n  <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{name}<\/span>\n  <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setName(e.target.value)}\n  autoComplete=\"given-name\"\n  className=\"mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md\"\n  required\n\/&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><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<ul>\n<li>Creating Drag N Drop is rather tricky compared to storing the textual data. You will use the <code>react-dropzone<\/code> library installed in the first section for the drag n drop component.<\/li>\n<\/ul>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-26\" 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> { getRootProps, getInputProps } = useDropzone({\n  <span class=\"hljs-attr\">accept<\/span>: <span class=\"hljs-string\">\"image\/*\"<\/span>,\n  <span class=\"hljs-attr\">multiple<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  <span class=\"hljs-attr\">onDrop<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">acceptedFiles<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> file = acceptedFiles&#91;<span class=\"hljs-number\">0<\/span>];\n    <span class=\"hljs-keyword\">const<\/span> reader = <span class=\"hljs-keyword\">new<\/span> FileReader();\n    reader.readAsDataURL(file);\n    reader.onloadend = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      setUploadImage(reader.result);\n    };\n    reader.onerror = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Something has happend.\"<\/span>);\n    };\n\n    setFiles(\n      acceptedFiles.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">file<\/span>) =&gt;<\/span>\n        <span class=\"hljs-built_in\">Object<\/span>.assign(file, {\n          <span class=\"hljs-attr\">preview<\/span>: URL.createObjectURL(file),\n        })\n      )\n    );\n  },\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><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 code, you use the <code>useDropzone<\/code> hook to configure your dropzone. You set <code>accept<\/code> property to image only and the <code>multiple<\/code> property to <code>false<\/code>, which means you can only upload one image.<\/p>\n<p>Inside the <code>onDrop<\/code> property, you use the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/FileReader\">FileReader API<\/a>\u2019s <code>.readAsDataURL()<\/code> method to convert the image to data URL. This data URL is stored inside  the <code>uploadImage<\/code> state.<\/p>\n<p>Here is how the data URL of an image looks like. This data URL has been trimmed. You can see the original data URL <a href=\"https:\/\/gist.githubusercontent.com\/lelouchB\/15e1ec0790db1fd129015e354a2c3c24\/raw\/216995efdc4c2a074928ef0d90722791c9457f55\/dataURLOfImage.txt\">here<\/a>.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAB2oAAAOMCAYAAACM7zM................\n<\/code><\/span><\/pre>\n<p>You use the <code>.createObjectURL()<\/code> method to convert the image to Object URL stored inside the <code>file<\/code> state and is later used to preview the image.<\/p>\n<p>Here is how the Object URL of an image looks like.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">blob:http:<span class=\"hljs-comment\">\/\/localhost:3000\/6d511357-c6fa-43ce-9374-0f5ddb7c5445<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This <code>file<\/code> state is used to preview the selected image to the user.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">aside<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\" flex flex-row flex-wrap justify-center mt-2\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex min-w-0  overflow-hidden\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{file}<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block w-auto h-32 w-32\"<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">aside<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><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>When the <code>Add New Product<\/code> button is clicked, the <code>handleSubmit<\/code> function is triggered which sends the data in a POST request  to <code>\/api\/createProduct<\/code> route.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-29\" 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> createProduct = <span class=\"hljs-keyword\">async<\/span> ({ name, description, link, uploadImage }) =&gt; {\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/createProduct\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ name, description, link, uploadImage }),\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n      },\n    });\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    <span class=\"hljs-built_in\">console<\/span>.error(err);\n  }\n};\n\n<span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n  <span class=\"hljs-keyword\">await<\/span> e.preventDefault();\n  <span class=\"hljs-keyword\">await<\/span> setName(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">await<\/span> setDescription(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">await<\/span> setLink(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">await<\/span> setUploadImage(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">await<\/span> setFile(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">await<\/span> setLoading(<span class=\"hljs-literal\">true<\/span>);\n  <span class=\"hljs-keyword\">await<\/span> createProduct({ name, description, link, uploadImage });\n  <span class=\"hljs-keyword\">await<\/span> router.push(<span class=\"hljs-string\">\"\/\"<\/span>);\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><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>While the data is being added to Airtable, the <code>loading<\/code> state is set to <code>true<\/code>, which makes the form disappear and a message<\/p>\n<p><code>Uploading!\u00a0New\u00a0Product\u00a0is\u00a0being added.<\/code> is shown to the user. After a successful POST request, the user is redirected to the landing page.<\/p>\n<p>The next step is to create the <code>\/api\/createProduct<\/code> route or <code>createProduct.js<\/code> file inside the <code>pages\/api<\/code> directory. Run the following command in the terminal to create the file.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch pages\/api\/createProduct.js\n<\/code><\/span><\/pre>\n<p>Add the following code to the <code>createProduct.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-30\" 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> { withApiAuthRequired, getSession } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> cloudinary = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'cloudinary'<\/span>).v2;\n<span class=\"hljs-keyword\">var<\/span> Airtable = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'airtable'<\/span>);\n\ncloudinary.config({\n    <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n    <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n    <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n});\n\n<span class=\"hljs-keyword\">const<\/span> base = <span class=\"hljs-keyword\">new<\/span> Airtable({ <span class=\"hljs-attr\">apiKey<\/span>: process.env.AIRTABLE_API_KEY }).base(\n  process.env.AIRTABLE_BASE_ID\n);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> config = {\n  <span class=\"hljs-attr\">api<\/span>: {\n    <span class=\"hljs-attr\">bodyParser<\/span>: {\n      <span class=\"hljs-attr\">sizeLimit<\/span>: <span class=\"hljs-string\">\"3mb\"<\/span>,\n    },\n  },\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> withApiAuthRequired(<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> session = getSession(req, res);\n  <span class=\"hljs-keyword\">const<\/span> sub = <span class=\"hljs-keyword\">await<\/span> session.user.sub;\n\n  <span class=\"hljs-keyword\">if<\/span> (req.method !== <span class=\"hljs-string\">\"POST\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">405<\/span>);\n  }\n  <span class=\"hljs-keyword\">const<\/span> { name, description, link, uploadImage } = req.body;\n\n  <span class=\"hljs-keyword\">const<\/span> image = <span class=\"hljs-keyword\">await<\/span> cloudinary.uploader.upload(\n    uploadImage,\n    { <span class=\"hljs-attr\">folder<\/span>: <span class=\"hljs-string\">\"product-hunt-clone\"<\/span> },\n    (error, result) =&gt; {\n      <span class=\"hljs-keyword\">if<\/span> (error) {\n        <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"An error has occured while uploading the image\"<\/span>);\n      }\n      <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Image was uploaded successfully\"<\/span>);\n    }\n  );\n<span class=\"hljs-keyword\">const<\/span> public_id = image.public_id;\n\n    base(<span class=\"hljs-string\">'products'<\/span>).create(&#91;\n      {\n        <span class=\"hljs-string\">\"fields\"<\/span>: {\n          <span class=\"hljs-string\">\"Name\"<\/span>: name,\n          <span class=\"hljs-string\">\"Description\"<\/span>: description,\n          <span class=\"hljs-string\">\"Link\"<\/span>: link,\n          <span class=\"hljs-string\">\"Sub\"<\/span>: sub,\n          <span class=\"hljs-string\">\"PublicId\"<\/span>: public_id\n        }\n      }\n    ], <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">err, records<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">if<\/span> (err) {\n        <span class=\"hljs-built_in\">console<\/span>.error(err);\n      <span class=\"hljs-keyword\">return<\/span>  res.status(<span class=\"hljs-number\">500<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Something went wrong.\"<\/span> });\n      }\n    });\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">200<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Product Added\"<\/span> });\n  \n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You might notice that similar to the <code>\/product\/insert<\/code> route, this route is also protected, and anonymous users cannot access it. The only difference is that this route is protected with the <code>withApiAuthRequired()<\/code> function and not the <code>withPageAuthRequired()<\/code> function. You can read more about the <code>withApiAuthRequired()<\/code> function <a href=\"https:\/\/auth0.github.io\/nextjs-auth0\/modules\/helpers_with_api_auth_required.html#withapiauthrequired\">here<\/a>.<\/p>\n<p>You will also notice that in this API route you export a <code>config<\/code> object from this API route. This limits the request body size to 3mb.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> config = {\n  <span class=\"hljs-attr\">api<\/span>: {\n    <span class=\"hljs-attr\">bodyParser<\/span>: {\n      <span class=\"hljs-attr\">sizeLimit<\/span>: <span class=\"hljs-string\">\"3mb\"<\/span>,\n    },\n  },\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><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 store the <code>public_id<\/code> of the image in Airtable, you first need to upload the image to Cloudinary and then use the <code>public_id<\/code> sent from Cloudinary to create a new record in Airtable.<\/p>\n<p>You create an instance of <code>cloudinary<\/code> and pass the Cloud Name, API Key, and API Secret, stored in the <code>.env.local<\/code> file, to the instance. You then use the <code>uploader.upload()<\/code> function to upload the data URL of the image inside the <code>product-hunt-clone<\/code> folder in your Cloudinary account. You store the <code>public_id<\/code> of the uploaded image in the <code>public_id<\/code> variable.<\/p>\n<p>You create an instance of <code>airtable<\/code> similar to the one created in the <code>lib\/api.js<\/code> file and create a new record using <code>create()<\/code> method.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">base(<span class=\"hljs-string\">\"products\"<\/span>).create(&#91;\n  {\n    <span class=\"hljs-attr\">fields<\/span>: {\n      <span class=\"hljs-attr\">Name<\/span>: name,\n      <span class=\"hljs-attr\">Description<\/span>: description,\n      <span class=\"hljs-attr\">Link<\/span>: link,\n      <span class=\"hljs-attr\">Sub<\/span>: sub,\n      <span class=\"hljs-attr\">PublicId<\/span>: public_id,\n    },\n  },\n])\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><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>One thing to notice is how you are getting the user\u2019s <code>sub<\/code> since it was not sent in the request body along with other data. The <code>sub<\/code> is accessed using the <code>getSession()<\/code> function from <code>@auth0\/nextjs-auth0<\/code> SDK.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-33\" 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> session = getSession(req, res);\n<span class=\"hljs-keyword\">const<\/span> sub = <span class=\"hljs-keyword\">await<\/span> session.user.sub;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><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>Head over to <a href=\"http:\/\/localhost:3000\/products\/insert\">http:\/\/localhost:3000\/products\/insert<\/a> in the browser or click on <code>Add Product<\/code> button in the Navbar.<\/p>\n<p>Here is how this page will look.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622986204\/e-603fc45fe6c0b4006873802f\/m6ay5ffxfhk7kposmqjb.png\" alt=\"Add New Product Page\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1519\" height=\"728\"\/><\/p>\n<p>Add some dummy data and click on <code>Add New Product<\/code> button.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622986236\/e-603fc45fe6c0b4006873802f\/ideckwqzkaquiw0rex1v.gif\" alt=\"Add New Product GIF\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1280\" height=\"720\"\/><\/p>\n<p>Once the new product has been added in the Airtable, you will be redirected to the landing page.<\/p>\n<h2>How To Delete a Product<\/h2>\n<p>You have already created the <code>delete<\/code> button. which triggers <code>deleteThisProduct()<\/code> function, in the <code>Product<\/code> component. The <code>deleteThisProduct<\/code> function makes a DELETE request to the <code>\/api\/deleteProduct<\/code> API route.<\/p>\n<p>Run the following command to create the <code>deleteProduct.js<\/code> file in the <code>pages\/api<\/code> directory.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch pages\/api\/deleteProduct.js\n<\/code><\/span><\/pre>\n<p>Add the following code to <code>deleteProduct.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-34\" 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> { withApiAuthRequired } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> cloudinary = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">\"cloudinary\"<\/span>).v2;\n<span class=\"hljs-keyword\">var<\/span> Airtable = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">\"airtable\"<\/span>);\n\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n});\n\n<span class=\"hljs-keyword\">const<\/span> base = <span class=\"hljs-keyword\">new<\/span> Airtable({ <span class=\"hljs-attr\">apiKey<\/span>: process.env.AIRTABLE_API_KEY }).base(\n  process.env.AIRTABLE_BASE_ID\n);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> withApiAuthRequired(<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (req.method !== <span class=\"hljs-string\">\"DELETE\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">405<\/span>);\n  }\n  <span class=\"hljs-keyword\">const<\/span> { id, publicId } = req.body;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> deletedProduct = <span class=\"hljs-keyword\">await<\/span> base(<span class=\"hljs-string\">\"products\"<\/span>).destroy(id);\n    <span class=\"hljs-keyword\">const<\/span> deleteImageFromCloudinary = cloudinary.uploader.destroy(\n      publicId,\n      <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">error, result<\/span>) <\/span>{\n        <span class=\"hljs-keyword\">if<\/span> (error) {\n          <span class=\"hljs-built_in\">console<\/span>.log(error);\n        }\n      }\n    );\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">200<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Product Deleted\"<\/span> });\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    <span class=\"hljs-built_in\">console<\/span>.error(err);\n    res.status(<span class=\"hljs-number\">500<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Something went wrong.\"<\/span> });\n  }\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><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 cove, you use the <code>destroy(id)<\/code> method to delete the record from Airtable.<\/p>\n<p>You use the <code>uploader.destroy()<\/code> method to destroy or delete the image from Cloudinary. After successful deletion, the <code>Product Deleted<\/code> message is sent as a response to the frontend.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1622986277\/e-603fc45fe6c0b4006873802f\/m1a1pgdnoh55bwzqlirq.gif\" alt=\"Delete Product GIF\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1280\" height=\"720\"\/><\/p>\n<h2>Conclusion<\/h2>\n<p>In this media jam, we configure our initial Next.js app and created <code>Navbar<\/code> and <code>Product<\/code> components. We also fetched and displayed the products on the home page. Finally, we made a form to add new products to the database.<\/p>\n<p>** In the next part, we will**<\/p>\n<ul>\n<li>Create the <code>My Profile<\/code> page.<\/li>\n<li>Create the <code>\/update\/[id]<\/code> page to update the product.<\/li>\n<li>Create the <code>upvote<\/code> button in the <code>Product<\/code> component to allow logged-in users to upvote a product.<\/li>\n<\/ul>\n<p>You can refer to the second part here:  <a href=\"https:\/\/mediajams.dev\/post\/How-to-build-a-Product-Hunt-Clone-with-Next.js-Part-2-of-2\">How to build a Product Hunt Clone with Next.js &#8211; Part 2 of 2<\/a><\/p>\n<p>Here are a few resources that you might find helpful.<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/auth0.github.io\/nextjs-auth0\/index.html\">@auth0\/nextjs-auth0 SDK Docs<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/image_upload_api_reference\">Cloudinary Docs<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/support.airtable.com\/hc\/en-us\/articles\/203313985-Public-REST-API\">Airtable API Docs<\/a>\n<\/li>\n<\/ul>\n<p>Happy coding!<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28230,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,175,212,371],"class_list":["post-28229","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-jamstack","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>How to build a Product Hunt Clone - Part 1 of 2<\/title>\n<meta name=\"description\" content=\"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.\" \/>\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\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to build a Product Hunt Clone - Part 1 of 2\" \/>\n<meta property=\"og:description\" content=\"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:24:48+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"3813\" \/>\n\t<meta property=\"og:image:height\" content=\"2541\" \/>\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\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"How to build a Product Hunt Clone &#8211; Part 1 of 2\",\"datePublished\":\"2022-03-23T22:24:48+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/\"},\"wordCount\":10,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"JAMStack\",\"Next.js\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/\",\"name\":\"How to build a Product Hunt Clone - Part 1 of 2\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:24:48+00:00\",\"description\":\"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA\",\"width\":3813,\"height\":2541},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to build a Product Hunt Clone &#8211; Part 1 of 2\"}]},{\"@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":"How to build a Product Hunt Clone - Part 1 of 2","description":"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.","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\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/","og_locale":"en_US","og_type":"article","og_title":"How to build a Product Hunt Clone - Part 1 of 2","og_description":"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:24:48+00:00","og_image":[{"width":3813,"height":2541,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.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\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/"},"author":{"name":"","@id":""},"headline":"How to build a Product Hunt Clone &#8211; Part 1 of 2","datePublished":"2022-03-23T22:24:48+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/"},"wordCount":10,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA","keywords":["Guest Post","JAMStack","Next.js","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/","name":"How to build a Product Hunt Clone - Part 1 of 2","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA","datePublished":"2022-03-23T22:24:48+00:00","description":"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA","width":3813,"height":2541},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-1-of-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to build a Product Hunt Clone &#8211; Part 1 of 2"}]},{"@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\/v1681925071\/Web_Assets\/blog\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98\/4cf2a3aeb1cd50757938f263be9292478d0600bb-3813x2541-1_2823042b98.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28229","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=28229"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28229\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28230"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28229"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28229"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}