{"id":28474,"date":"2022-03-24T21:14:00","date_gmt":"2022-03-24T21:14:00","guid":{"rendered":"http:\/\/building-a-clothing-store-app"},"modified":"2022-03-24T21:14:00","modified_gmt":"2022-03-24T21:14:00","slug":"building-a-clothing-store-app","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/","title":{"rendered":"Building a Clothing Store App"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>When you\u2019re building an online shopping platform, you want to make sure that everything loads quickly for your potential customers. One huge factor that slows down many e-commerce sites is loading images. When you have hundreds of product images loading on a page at the same time, it can create a frustrating user experience as people wait to see how things look.<\/p>\n<p>In this article, we\u2019re going to build a simple clothing store to demonstrate how you can make fast-loading pages. We\u2019ll be working with Next.js for the front-end and back-end of the app and we\u2019ll be using Cloudinary as the image host.<\/p>\n<h2>Setting up the Next app<\/h2>\n<p>Let\u2019s start by generating a Next.js app. Open your terminal and run this command. It\u2019ll generate all of the files you need for a Typescript Next app.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ yarn create next-app --typescript\n<\/code><\/span><\/pre>\n<p>At some point, you\u2019ll be prompted to enter the name of the project. The one we\u2019ll be working with is called <code>tienda-de-ropa<\/code>, but feel free to name it anything you like. After you set the name, the downloads will start and the project files will be created.<\/p>\n<p>Most of our work will be in the <code>pages<\/code> folder because this is where we\u2019ll add the pages that users will interact with on the online store.<\/p>\n<p>Let\u2019s install a few packages before we get too far ahead of ourselves.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ yarn add typescript ts-node @types\/node @prisma\/client prisma styled-components\n<\/code><\/span><\/pre>\n<h2>Setting up Prisma and a Postgres database<\/h2>\n<p>Before we get to that though, we\u2019re going to be pulling items from a Postgres database and using Prisma to do so. If you don\u2019t have Postgres installed locally, go ahead and <a href=\"https:\/\/www.postgresql.org\/download\/\">download it here<\/a> and get set up. You should create a new database called <code>tienda-de-ropa<\/code> so that we can connect to it immediately.<\/p>\n<p>Now we need to get Prisma set up in our app. We\u2019ll start by adding a new file to the root of the project called <code>prisma.ts<\/code>. This will handle the Prisma client we need to interact with and keep our app from having issues due to database connection limits. Inside of this file, add the following code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { PrismaClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@prisma\/client\"<\/span>;\n\n<span class=\"hljs-keyword\">let<\/span> prisma: PrismaClient\n\n<span class=\"hljs-keyword\">if<\/span> (process.env.NODE_ENV === <span class=\"hljs-string\">'production'<\/span>) {\n  prisma = <span class=\"hljs-keyword\">new<\/span> PrismaClient()\n} <span class=\"hljs-keyword\">else<\/span> {\n  <span class=\"hljs-keyword\">if<\/span> (!global.prisma) {\n    global.prisma = <span class=\"hljs-keyword\">new<\/span> PrismaClient()\n  }\n  prisma = global.prisma\n}\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> prisma\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>That\u2019s not the only new thing we\u2019ll be adding to this project to get Prisma working. In the root directory, add a new folder called <code>prisma<\/code>. Inside the new sub-folder, add a file named <code>schema.prisma<\/code>. This will hold the database schema and the instructions that tell Prisma how to connect to Postgres. Open this file and add the following code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">generator client {\n    provider = <span class=\"hljs-string\">\"prisma-client-js\"<\/span>\n}\n\ndatasource db {\n    provider = <span class=\"hljs-string\">\"postgresql\"<\/span>\n    url      = <span class=\"hljs-string\">\"postgres:\/\/usernamer:password@localhost:5432\/tienda-de-ropa\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This code defines how Prisma connects to Postgres by defining the database provider type and the connection string. Make sure to update the <code>username<\/code> and <code>password<\/code> to your own values. Since we have the connection established, it\u2019s time to create the model for our products. Below the code we just wrote, add this model definition:<\/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\">model Product {\n    id          <span class=\"hljs-built_in\">String<\/span> @id @<span class=\"hljs-keyword\">default<\/span>(uuid())\n    name        <span class=\"hljs-built_in\">String<\/span>\n    category    <span class=\"hljs-built_in\">String<\/span>\n    price       Float\n    image       <span class=\"hljs-built_in\">String<\/span>\n    description <span class=\"hljs-built_in\">String<\/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<p>The model has a few fields that are commonly seen in online stores, but feel free to add any other fields you like! We\u2019re going to take advantage of NextJS doing server-side rendering and not worry about building a back-end. You can extend this app and add some back-end functionality with <a href=\"https:\/\/nextjs.org\/docs\/api-routes\/introduction\">NextJS API routes<\/a>, but that\u2019ll be out of the scope of this post.<\/p>\n<p>To have some product data to work with, we\u2019ll seed the database with a few different products.<\/p>\n<h3>Seeding the database<\/h3>\n<p>Create a new file in the <code>prisma<\/code> directory called <code>seed.ts<\/code>. This is the standard file name you\u2019ll see when working with Prisma seed data. In this file, we\u2019ll add the following code and then discuss what\u2019s happening:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { PrismaClient, Prisma } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@prisma\/client'<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> prisma = <span class=\"hljs-keyword\">new<\/span> PrismaClient()\n\n<span class=\"hljs-keyword\">const<\/span> products: Prisma.ProductCreateInput&#91;] = &#91;\n  {\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Blue Shirt'<\/span>,\n      <span class=\"hljs-attr\">category<\/span>: <span class=\"hljs-string\">'Tops'<\/span>,\n      <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">29.99<\/span>,\n      <span class=\"hljs-attr\">image<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/image\/upload\/v1606580778\/3dogs.jpg'<\/span>,\n      <span class=\"hljs-attr\">description<\/span>: <span class=\"hljs-string\">'Blue shirt with dog print'<\/span>\n  },\n  {\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Taupe Pants'<\/span>,\n      <span class=\"hljs-attr\">category<\/span>: <span class=\"hljs-string\">'Bottoms'<\/span>,\n      <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">59.99<\/span>,\n      <span class=\"hljs-attr\">image<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/image\/upload\/v1606580780\/beach-boat.jpg'<\/span>,\n      <span class=\"hljs-attr\">description<\/span>: <span class=\"hljs-string\">'Ankle length pants'<\/span>\n  },\n  {\n      <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'Black Patent Leather Oxfords'<\/span>,\n      <span class=\"hljs-attr\">category<\/span>: <span class=\"hljs-string\">'Shoes'<\/span>,\n      <span class=\"hljs-attr\">price<\/span>: <span class=\"hljs-number\">99.99<\/span>,\n      <span class=\"hljs-attr\">image<\/span>: <span class=\"hljs-string\">'https:\/\/res.cloudinary.com\/milecia\/image\/upload\/v1606580772\/dessert.jpg'<\/span>,\n      <span class=\"hljs-attr\">description<\/span>: <span class=\"hljs-string\">'Ankle high dress shoes'<\/span>\n  }\n]\n\n<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">main<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Start seeding ...`<\/span>)\n  <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> p <span class=\"hljs-keyword\">of<\/span> products) {\n    <span class=\"hljs-keyword\">const<\/span> product = <span class=\"hljs-keyword\">await<\/span> prisma.product.create({\n      <span class=\"hljs-attr\">data<\/span>: p,\n    })\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Created product with id: <span class=\"hljs-subst\">${product.id}<\/span>`<\/span>)\n  }\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Seeding finished.`<\/span>)\n}\n\nmain()\n  .catch(<span class=\"hljs-function\">(<span class=\"hljs-params\">e<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.error(e)\n    process.exit(<span class=\"hljs-number\">1<\/span>)\n  })\n  .finally(<span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-keyword\">await<\/span> prisma.$disconnect()\n  })\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We start off by importing a few objects from the Prisma client package and setting up an instance of the client. Then we create a typed array for the products we want to add to the database. If you have images in Cloudinary already, you should definitely replace these example images with your own.<\/p>\n<p>Then we have a <code>main<\/code> function that will add all of these products to the <code>Product<\/code> table we defined in our schema file. Finally, we\u2019ll call the <code>main<\/code> function to execute the seeding and then disconnect from the database if there are no errors. That\u2019s all the code we need for seeding this data.<\/p>\n<p>There\u2019s one more tiny thing we need to do before we can run a migration to put the schema and seed data in the <code>tienda-de-ropa<\/code> database. Since this is a Typescript project, that means we need to be more explicit about how the app should handle seeding the data in the migration.<\/p>\n<p>We need to add a small script to the <code>package.json<\/code> so everything runs smoothly. Open this file and add the following code below your <code>devDependencies<\/code>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-string\">\"prisma\"<\/span>: {\n    <span class=\"hljs-string\">\"seed\"<\/span>: <span class=\"hljs-string\">\"ts-node --compiler-options {\\\"module\\\":\\\"CommonJS\\\"} prisma\/seed.ts\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will help us avoid any errors due to Typescript interfacing with regular JavaScript packages. Now we can run the migration. Open your terminal and run the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">$ npx prisma migrate dev\n<\/code><\/span><\/pre>\n<p>You will be prompted for a name for this migration and then it will create the database schema in Postgres and add your seed data to it. Now that we have the data side of the app set up, we can jump over to the front-end and get a few pages up.<\/p>\n<h2>Making the product listing page<\/h2>\n<p>Like most online stores, we\u2019re going to have a few pages. There will be a page that displays all of the products, individual product pages, and a cart that shows customers all of the items they\u2019ve selected. Keep in mind that this is just a demo so there\u2019s <em>a lot<\/em> of functionality, styles, and other considerations that could be added.<\/p>\n<p>Let\u2019s start by making a page that displays all of the products. Adding a new route in NextJS means adding a new file to the <code>pages<\/code> directory. So we\u2019re going to add a file called <code>products<\/code>. Open this new file and add the following code.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/image'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/link'<\/span>\n<span class=\"hljs-keyword\">import<\/span> styled <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'styled-components'<\/span>\n<span class=\"hljs-keyword\">import<\/span> prisma <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/prisma'<\/span>\n\ninterface Product {\n    <span class=\"hljs-attr\">id<\/span>: string\n    <span class=\"hljs-attr\">name<\/span>: string\n    <span class=\"hljs-attr\">category<\/span>: string\n    <span class=\"hljs-attr\">price<\/span>: number\n    <span class=\"hljs-attr\">image<\/span>: string\n    <span class=\"hljs-attr\">description<\/span>: string\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Products<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Products<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Products\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We\u2019ve added all of the imports we need for this specific file and we\u2019ve defined the type for the product data we\u2019ll receive from the database. We\u2019ve also added the <code>Products<\/code> component that will get rendered on the page. If you run the app now with <code>yarn dev<\/code>, you\u2019ll just see the \u201cProducts\u201d text on the page.<\/p>\n<p>Next let\u2019s add a few styled components so that the app has a bit of organization. Right below the product type definition, add this code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> HoverCard = styled.div`\n    margin: <span class=\"hljs-number\">12<\/span>px;\n    padding: <span class=\"hljs-number\">12<\/span>px;\n    width: <span class=\"hljs-number\">30<\/span>%;\n\n    &amp;:hover {\n        border: <span class=\"hljs-number\">1<\/span>px solid <span class=\"hljs-comment\">#c4c4c4;<\/span>\n        cursor: pointer;\n    }\n`\n\n<span class=\"hljs-keyword\">const<\/span> ToCart = styled.div`\n    border: <span class=\"hljs-number\">1<\/span>px solid <span class=\"hljs-comment\">#c4c4c4;<\/span>\n    background-color: <span class=\"hljs-comment\">#abacab;<\/span>\n    width: <span class=\"hljs-number\">150<\/span>px;\n`\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">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 <code>HoverCard<\/code> element will be used to display some overview info about each product and if you click on one of them, you\u2019ll be redirected to the individual product page that has all of the details about that product. The <code>ToCart<\/code> element will wrap the link to the current user cart so that it looks more button-like instead of like a link. We do this because NextJS has a specific element to handle links.<\/p>\n<h3>Getting data using the getStaticProps function<\/h3>\n<p>NextJS does a great job at server-side rendering, so that means we don\u2019t have to worry about sensitive info leaking even though we are going to fetch data directly from the database. Any code defined in the <code>getStaticProps<\/code> function is executed at build time on the server-side.<\/p>\n<p>To get all of the products from the database, add the following code just above the export statement line:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getStaticProps<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> products = <span class=\"hljs-keyword\">await<\/span> prisma.product.findMany()\n    \n    <span class=\"hljs-keyword\">return<\/span> {\n        <span class=\"hljs-attr\">props<\/span>: {\n            products,\n        },\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This code lets us connect to the database directly through our Prisma client and we get all of the product records available. Then we return this product data as a prop to the <code>Products<\/code> component. So let\u2019s start filling in the code for that component now that we have the data.<\/p>\n<h3>Wrapping up the products page<\/h3>\n<p>With our data readily available, let\u2019s use it to display the shop\u2019s products to potential customers. Update your <code>Products<\/code> component to match the following code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Products<\/span>(<span class=\"hljs-params\">{ products }<\/span>) <\/span>{\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\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">margin:<\/span> '<span class=\"hljs-attr\">24px<\/span>' }}&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Everything in the store<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">display:<\/span> '<span class=\"hljs-attr\">flex<\/span>' }}&gt;<\/span>\n                {\n                    products.map((product: Product) =&gt; (\n                        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span>\n                            <span class=\"hljs-attr\">passHref<\/span>\n                            <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{product.name}<\/span>\n                            <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{<\/span>`\/<span class=\"hljs-attr\">product<\/span>\/${<span class=\"hljs-attr\">encodeURIComponent<\/span>(<span class=\"hljs-attr\">product.id<\/span>)}`}\n                        &gt;<\/span>\n                            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">HoverCard<\/span>&gt;<\/span>\n                                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{product.image}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{product.description}<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{250}<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{250}<\/span> \/&gt;<\/span>\n                                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>{product.name}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n                                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{product.category}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n                            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">HoverCard<\/span>&gt;<\/span>\n                        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n                    ))\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\">Link<\/span> <span class=\"hljs-attr\">passHref<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{<\/span>`\/<span class=\"hljs-attr\">cart<\/span>`}&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ToCart<\/span>&gt;<\/span>Go to Cart<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ToCart<\/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><\/span>\n    )\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Thankfully, this component is relatively simple because it only has one set of data to work with and the elements are already defined for us. There\u2019s a styled <code>div<\/code> that wraps everything and we have a small heading for the page. Then we get to the most important part of this component, where we actually display the data we pulled from the database.<\/p>\n<p>In the <code>div<\/code> with the flex style, we take the <code>products<\/code> prop that was passed in from the <code>getStaticProps<\/code> function and map it to the different elements. First, we have a link that routes to the individual product pages based on the product id.<\/p>\n<p>Within that <code>Link<\/code>, we display the product image, name, and category so that customers can quickly find what they need. The last element we have is a link to the cart so that customers can see what they\u2019ve decided to purchase. Take a moment to run the app and navigate to the <code>\/products<\/code> route. You should see something similar to this:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/mediadevs\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1646844504\/e-603fc55d218a650069f5228b\/hulxehenhviogxy8s0dd.png\" alt=\"products page with all of the products seeded in the database\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"950\"\/><\/p>\n<p>Now the customers can see all of the products, we need to work on the individual product pages.<\/p>\n<h2>Adding the product page<\/h2>\n<p>NextJS uses the folder structure in the <code>pages<\/code> directory to determine how to handle nested routes. To make the individual product pages, we\u2019ll use the product id as the differentiator. Let\u2019s add a couple of new folders and a file to the <code>pages<\/code> directory.<\/p>\n<p>Start by adding a folder named <code>product<\/code>. This will create a new route like <code>\/product<\/code>. Inside of this folder, add another folder named <code>[id]<\/code>. This is how NextJS does dynamic routing. So this gives us routes like, <code>\/product\/[id]<\/code> where <code>id<\/code> is the product id getting passed from the products page.<\/p>\n<p>Inside the <code>[id]<\/code> folder, create a new file called <code>index.tsx<\/code>. This is where we\u2019ll make the component for a single product. Go ahead and open this file and add the following code:<\/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> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/image'<\/span>\n<span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/link'<\/span>\n<span class=\"hljs-keyword\">import<\/span> styled <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'styled-components'<\/span>\n<span class=\"hljs-keyword\">import<\/span> prisma <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'..\/..\/..\/prisma'<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> Back = styled.div<span class=\"hljs-string\">`\n    border: 1px solid #c4c4c4;\n    background-color: #abacab;\n    width: 150px;\n`<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Product<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> ()\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Product\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>We\u2019re bringing in the imports like normal and making another styled component to wrap the link back to the products page and we have the outline of the <code>Product<\/code> component we\u2019ll display on the page.<\/p>\n<p>Now we need to fetch the data for a particular product based on the id parameter that\u2019s passed in the URL.<\/p>\n<h3>Retrieving data with getServerSideProps<\/h3>\n<p>Since this data will need to be retrieved on each request because there will be different products being displayed, we\u2019ll use the <code>getServerSideProps<\/code> function instead of the <code>getStaticProps<\/code> function.<\/p>\n<p>All of the data fetching will still happen server-side, it will just happen each time this route is requested. So just above the export statement, add the following code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" 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\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getServerSideProps<\/span>(<span class=\"hljs-params\">context<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> id = context.params.id\n\n    <span class=\"hljs-keyword\">const<\/span> product = <span class=\"hljs-keyword\">await<\/span> prisma.product.findUnique({\n        <span class=\"hljs-attr\">where<\/span>: { id }\n    })\n\n    <span class=\"hljs-keyword\">return<\/span> {\n        <span class=\"hljs-attr\">props<\/span>: {\n            product,\n        },\n    }\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\">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>Because this page uses dynamic routing, we are using the <code>context<\/code> parameter to get the <code>params<\/code> from the route parameters. So since our page is in the <code>[id]<\/code> folder, we\u2019ll be able to get the product id from this <code>context<\/code> object. Then we make a call to the database using the Prisma client to get the specific product based on its id and we pass the product data to the component as a prop.<\/p>\n<h3>Filling in the product component<\/h3>\n<p>This component will render the image, name, price, and category for the specified product and there will be a button to add the item to a customer\u2019s cart. There are a number of ways to manage cart items, but if you take a look in the developer tools on your favorite store\u2019s website, you\u2019ll likely see an array is stored in the local storage. We\u2019ll take that approach here.<\/p>\n<p>Let\u2019s start by adding a function inside of the <code>Product<\/code> component that handles products being added to the cart. Update your existing <code>Product<\/code> component with the following 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-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Product<\/span>(<span class=\"hljs-params\">{ product }<\/span>) <\/span>{\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">addToCart<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n        <span class=\"hljs-keyword\">let<\/span> arr = &#91;]\n\n        <span class=\"hljs-keyword\">const<\/span> existingItems = <span class=\"hljs-built_in\">window<\/span>.localStorage.getItem(<span class=\"hljs-string\">'lineItems'<\/span>)\n\n        <span class=\"hljs-keyword\">if<\/span> (existingItems != <span class=\"hljs-literal\">null<\/span>) {\n            arr = <span class=\"hljs-built_in\">JSON<\/span>.parse(existingItems)\n        }\n\n        <span class=\"hljs-keyword\">const<\/span> itemForCart = {\n            <span class=\"hljs-attr\">name<\/span>: product.name,\n            <span class=\"hljs-attr\">price<\/span>: product.price,\n            <span class=\"hljs-attr\">imageUrl<\/span>: product.image\n        }\n\n        arr.push(itemForCart)\n\n        <span class=\"hljs-built_in\">window<\/span>.localStorage.setItem(<span class=\"hljs-string\">'lineItems'<\/span>, <span class=\"hljs-built_in\">JSON<\/span>.stringify(arr))\n    }\n\n    <span class=\"hljs-keyword\">return<\/span> ()\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>You can see that we are using that <code>product<\/code> prop returned from the <code>getServerSideProps<\/code> call. Then we create the <code>addToCart<\/code> function that checks the local storage for any existing cart items and it will push this current product to the cart when a customer clicks the button we\u2019ll add shortly. This is how we can preserve the cart items as customers navigate around the site.<\/p>\n<p>Now we\u2019re ready to add the customer-facing functionality. Update the existing return statement with the following code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">display:<\/span> '<span class=\"hljs-attr\">flex<\/span>', <span class=\"hljs-attr\">justifyContent:<\/span> '<span class=\"hljs-attr\">space-evenly<\/span>' }}&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{product.image}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{product.description}<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{250}<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{250}<\/span> \/&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">width:<\/span> '<span class=\"hljs-attr\">30<\/span>%' }}&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>{product.name}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{product.category}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>$ {product.price}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/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\">{addToCart}<\/span>&gt;<\/span>Add to cart<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        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">passHref<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{<\/span>`\/<span class=\"hljs-attr\">products<\/span>`}&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Back<\/span>&gt;<\/span>Back to Products<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Back<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">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 display all of the product info we discussed earlier along with a button to add the product to the cart and a link back to the products page. If you aren\u2019t running the app, start it back up with <code>yarn dev<\/code>, click on one of the products from the page that has all of the products, and you should see something similar to this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/mediadevs\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1646844543\/e-603fc55d218a650069f5228b\/mwuitvtgnow8lhxu2lrr.png\" alt=\"an individual product page\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"808\"\/><\/p>\n<p>You\u2019ll also see a URL similar to this: <code>http:\/\/localhost:3000\/product\/62b6e542-33dd-4e88-98a0-c781c970e317<\/code> It has the product id we\u2019re using in that data request.<\/p>\n<p>Go ahead and add the current product to the cart because we\u2019re about to make the page for it and having some data there will help.<\/p>\n<h2>Making the cart page<\/h2>\n<p>This is the last page for this app so we\u2019re almost there! Remember that we have the button to go to the cart on the products page. You could definitely add this button to the individual product pages if you like.<\/p>\n<p>For now, let\u2019s work on this cart page. Add a new file to the <code>pages<\/code> directory called <code>cart.tsx<\/code> and add the following:<\/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> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/image'<\/span>\n<span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'next\/link'<\/span>\n<span class=\"hljs-keyword\">import<\/span> styled <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'styled-components'<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> Back = styled.div<span class=\"hljs-string\">`\n    border: 1px solid #c4c4c4;\n    background-color: #abacab;\n    width: 150px;\n`<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> CartItem = styled.div<span class=\"hljs-string\">`\n    border: 1px solid #c4c4c4;\n    display: flex;\n    height: 100px;\n    justify-content: space-between;\n    margin: 12px;\n    padding: 24px;\n`<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> Container = styled.div<span class=\"hljs-string\">`\n    margin: 24px;\n    padding: 12px;\n`<\/span>\n\ninterface CartItem {\n    <span class=\"hljs-attr\">name<\/span>: string\n    <span class=\"hljs-attr\">price<\/span>: number\n    <span class=\"hljs-attr\">imageUrl<\/span>: string\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Cart<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-keyword\">return<\/span> ()\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Cart\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>This brings in all the imports we need, some styled components to bring a bit of organization to the list of items in the cart, and the type definition for the data we\u2019ll be handling. Since the cart items are stored in the browser\u2019s local storage, we don\u2019t need to request anything from the database on this page.<\/p>\n<p>All that\u2019s left is getting the items from local storage and using our styled components to display them to the customers. So update the component with this code to get those items and handle the render state accordingly:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Cart<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-keyword\">let<\/span> cartItems\n\n    <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">typeof<\/span> <span class=\"hljs-built_in\">window<\/span> !== <span class=\"hljs-string\">\"undefined\"<\/span>) {\n        cartItems = <span class=\"hljs-built_in\">window<\/span>.localStorage.getItem(<span class=\"hljs-string\">'lineItems'<\/span>)\n    }\n\n    <span class=\"hljs-keyword\">if<\/span> (!cartItems) {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>No items in the cart<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n    }\n\n    <span class=\"hljs-keyword\">const<\/span> parsedCartItems = <span class=\"hljs-built_in\">JSON<\/span>.parse(cartItems)\n\n    <span class=\"hljs-keyword\">return<\/span>()\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This code will check the local storage for any cart items and if there aren\u2019t any, we\u2019ll render a message to the customer that the cart is empty. Otherwise, we\u2019ll parse the cart items so we can use the data. Now let\u2019s update that empty return statement to render all of those cart items:<\/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\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Container<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h2<\/span>&gt;<\/span>Tu Carrito<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h2<\/span>&gt;<\/span>\n            {\n                parsedCartItems.map((item: CartItem) =&gt; (\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">CartItem<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{item.name}<\/span>&gt;<\/span>\n                        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Name: {item.name}<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>Price: {item.price}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{item.imageUrl}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{item.name}<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{100}<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{100}<\/span> \/&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">CartItem<\/span>&gt;<\/span>\n                ))\n            }\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">passHref<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{<\/span>`\/<span class=\"hljs-attr\">products<\/span>`}&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Back<\/span>&gt;<\/span>Back to Products<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Back<\/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\">Container<\/span>&gt;<\/span><\/span>\n)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We have a page header so that the customer knows they\u2019re on the cart page. Then we take all of the cart items and map the data to our <code>CartItem<\/code> styled component. This will create a styled list view of all the items. After we show all of the items, then we have a button to go back to the products page. This is a good place to add a button to some check-out functionality and that will likely require some third-party service.<\/p>\n<p>At this point, run the app again and add a few items to the cart from their respective product pages. Then navigate to the <code>\/cart<\/code> route and you\u2019ll see something like this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/mediadevs\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1646847149\/e-603fc55d218a650069f5228b\/naplf4mwvguzwaem4vwv.png\" alt=\"all of the items in the cart\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"1129\"\/><\/p>\n<p>Now the app has common pages that customers will go to in any online store! This can be cleaned up and turned into a full-fledged online store app that renders images quickly.<\/p>\n<h2>Finished code<\/h2>\n<p>If you want to check out the full repo, you can find the complete code in <a href=\"https:\/\/github.com\/flippedcoder\/media-projects\/tree\/main\/tienda-de-ropa\">here<\/a>.<\/p>\n<p>Or you can check out the non-database code in this <a href=\"https:\/\/codesandbox.io\/s\/sleepy-banach-y92t8y\">Code Sandbox<\/a>.<\/p>\n<p>&lt;CodeSandBox\ntitle=\u201csleepy-banach-y92t8y\u201d\nid=\u201csleepy-banach-y92t8y\u201d\n\/&gt;<\/p>\n<h2>Conclusion<\/h2>\n<p>Making a fast-loading online store is essential to getting customers to stay on your site. Waiting on images or pages to load will deter people from sticking around and it can cause your existing customers to look for other options. Using server-side rendering can help improve your customer experience and make your app more secure, so make sure it\u2019s a consideration before you build!<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28475,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,212,371],"class_list":["post-28474","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-next-js","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building a Clothing Store App<\/title>\n<meta name=\"description\" content=\"There are a lot of considerations that go into building an online store. Things like security and user experience are at the top of that list. In this tutorial, we&#039;ll take a look at doing server-side rendering with NextJS to help address both of these concerns.\" \/>\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\/building-a-clothing-store-app\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Clothing Store App\" \/>\n<meta property=\"og:description\" content=\"There are a lot of considerations that go into building an online store. Things like security and user experience are at the top of that list. In this tutorial, we&#039;ll take a look at doing server-side rendering with NextJS to help address both of these concerns.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-24T21:14:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"5472\" \/>\n\t<meta property=\"og:image:height\" content=\"3648\" \/>\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\/building-a-clothing-store-app\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Building a Clothing Store App\",\"datePublished\":\"2022-03-24T21:14:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/\"},\"wordCount\":5,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"Next.js\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/\",\"name\":\"Building a Clothing Store App\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA\",\"datePublished\":\"2022-03-24T21:14:00+00:00\",\"description\":\"There are a lot of considerations that go into building an online store. Things like security and user experience are at the top of that list. In this tutorial, we'll take a look at doing server-side rendering with NextJS to help address both of these concerns.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA\",\"width\":5472,\"height\":3648},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Clothing Store App\"}]},{\"@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":"Building a Clothing Store App","description":"There are a lot of considerations that go into building an online store. Things like security and user experience are at the top of that list. In this tutorial, we'll take a look at doing server-side rendering with NextJS to help address both of these concerns.","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\/building-a-clothing-store-app\/","og_locale":"en_US","og_type":"article","og_title":"Building a Clothing Store App","og_description":"There are a lot of considerations that go into building an online store. Things like security and user experience are at the top of that list. In this tutorial, we'll take a look at doing server-side rendering with NextJS to help address both of these concerns.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-24T21:14:00+00:00","og_image":[{"width":5472,"height":3648,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.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\/building-a-clothing-store-app\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/"},"author":{"name":"","@id":""},"headline":"Building a Clothing Store App","datePublished":"2022-03-24T21:14:00+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/"},"wordCount":5,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA","keywords":["Guest Post","Image","Next.js","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/","name":"Building a Clothing Store App","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA","datePublished":"2022-03-24T21:14:00+00:00","description":"There are a lot of considerations that go into building an online store. Things like security and user experience are at the top of that list. In this tutorial, we'll take a look at doing server-side rendering with NextJS to help address both of these concerns.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA","width":5472,"height":3648},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/building-a-clothing-store-app\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building a Clothing Store App"}]},{"@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\/v1681924344\/Web_Assets\/blog\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3\/99bdccae11d2c2b2104a1a187ff0ca8ac8211201-5472x3648-1_284753fda3.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28474","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=28474"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28474\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28475"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28474"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28474"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28474"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}