Set up Individual Product Pages with Next.js Dynamic Routes

Colby Fayock
InstructorColby Fayock

Share this video with your friends

Send Tweet
Published a year ago
Updated a year ago

We are going to take a query and use it to create a dynamic query out of it with variables. In doing that, we are going to make use of the getStaticPaths function to create a path for every single one of our products.

Then, we'll use getStaticProps to query for each and every one of those dynamically using the variables object, where just like any other page, we will grab that data as a prop to our product page function, where we will fill in our page and have our dynamic route.

Colby Fayock: [0:00] Now that we have our home page being dynamically created, including bringing in the products dynamically, we want to create a page for each individual product. To do that, we're going to couple our getStaticProps call, similar to that home page, with getStaticPaths.

[0:14] Where in getStaticProps we were able to request the data we need for the page, getStaticPaths allow us to dynamically create all the different paths that we want for a particular route.

[0:25] To get started in our starter, I created a page so that when we go to products/product, we can see that we load this very simple template, which we'll use to fill in our product data.

[0:35] If we look at this page inside of the code, we can see that it's a static route, where we have pages, products, and product. That means it's just going to be served at /products/product.

[0:44] What we want to happen is, we want to be able to go to /products/product slug so that whenever we pass in that slug dynamically, Next.js knows that already exists and can load that page. To do that, we're going to use dynamic routes, where we can create these paths dynamically, where we can create these dynamic routes with these brackets so that inside, we have our parameter.

[1:05] Which in our case will be a product slug where we can use that value inside of paths as well as our props to dynamically request the pages we want. To see how this is going to work, we want to first set up our route to be a dynamic route.

[1:20] To do that, we're going to rename our product file so that it's going to be in brackets, first of all. Let's call this product slug so that we know exactly what that parameter is that we're going to grab.

[1:32] Next, we want to tell Next.js every single path that we have and the product slug that corresponds to that path. We're going to scroll all the way down to the bottom of the page.

[1:43] This time we're going to export async function, getStaticPaths, where we're going to return an object, but this time we're going to return our paths. We're going to dynamically create these paths by first requesting our product data.

[1:56] We can do this similarly to how we were doing it on our home page. I'm going to first copy over this import statement of the Apollo Client. I'm going to paste it in at the top of the file.

[2:06] Then at the very bottom of the file, similar to how we were requesting our products before, I'm going to grab both this client instance, as well as the entire data query which later I'll tweak.

[2:16] Where if I go back over to product slug and head all the way down to the bottom, I'm going to paste that in at the top of getStaticPaths. In this query, I only want to grab the products. I don't want to grab the page.

[2:28] I'm going to first get rid of this page field inside of that query. Then I'm going to also update this query to page products so that we know what the query is if we ever need to debug. Also, I want to grab all the products.

[2:42] I'm also going to remove this first designator here so that we get all of them in that return. Before we move on let's console.log out this data just so that we can have an idea of what it looks like when we're trying to create our paths.

[2:53] Like we saw in previous lessons, we have our data.data.products where we have all of our product objects. If we see here, we also have an error. That's because we also are required to define a fallback property.

[3:04] Now, what this is going to do is it's going to allow us to set up a fallback page. Since we're not going to worry about that for now, we want our pages to just 404 if it's not found, so I'm going to say fallback=false.

[3:16] Now, if we additionally remember from before, each of our product objects are going to have a product slug. That's exactly what we want to use to create our paths.

[3:26] I'm going to create a new constant called paths. I'm going to say for data.data.products, I want to map through each and every one of those products, so that inside of there, I'm going to create a new parameters object where I'm going to be able to define that product slug, which is going to be equal to product.slug.

[3:43] Then, I can take those paths, and I'm going to just remove this empty array and pass that right in through. Now, just to visualize what this is going to look like, I'm going to go ahead and console.log this out. We can see we have this paths array where for each and every one, we have a params object where we have that product slug with our product slug.

[4:03] Next, as we're seeing here, we want to add our getStaticProps so that we can take that dynamic value and request the data for that page. Above my getStaticPaths or below, if that's what you prefer, I'm going to export an async function called getStaticProps, where inside, I'm going to ultimately return my props object.

[4:22] Now, when this getStaticProps function runs, I'm going to receive a value where I can destructure that first argument as params. Now, this params object is going to have that value of that product slug that I just created and passed to every one of those paths.

[4:37] We can see what that looks like if I console.log out that value. I'm going to say params.productSlug, and we can see what the value is of params.productSlug. If I go to a path that will exist such as Cosmo mug, we can both see that the page loads, but inside of our terminal, we see that product slug for Cosmo mug.

[4:58] Now, we want to use this product slug to request that singular product data and pass that into the page. Now, to start off, I'm going to use the same instance of client where I'm going to create it at the top of getStaticProps.

[5:12] For what it's worth, you could probably abstract this logic to create and return that instance of the client. For now, we're going to keep it simple and just copy and paste the different instances.

[5:21] Then, I also want to copy that query for the products. I'm going to grab that constant of data, and I'm going to paste that right below the other client instance.

[5:31] This one's going to be a little bit different, because this is going to be a dynamic query. Let's head back over to our playground explorer, where I want to scroll down here and find the product node. I want to query where something is going to be, that particular product.

[5:45] Where particularly, we want to see where the slug will be that product. We can type in a slug here such as, Cosmo mug. We can see that it's filling out the query here. If we click play, we can see that's going to fill out that ID, and we get our product.

[6:00] The difference here is, we want to configure this so that we can pass in this slug as a variable. The nice thing is, if I head over back to this slug, I can see this little dollar sign here. If I click that, it's going to automatically turn that into a variable.

[6:13] Now, because we've set this up as a variable, we're going to be able to pass this in like a function. Where we can say, we want this slug to equal a particular value for this particular query. Since we are passing it in, we don't want this to default to Cosmo mug.

[6:27] I'm going to go ahead and remove that to start. We can test what it would be like to pass in a dynamic value by clicking this query variables tab. If I start creating a JSON object, where I'm going to start by specifying my property.

[6:39] If I start typing, I'm going to automatically get some autofill options such as slug, which is what I want, where I can specify that I want this to be Cosmo mug. Then if I hit play, we can see that we're going to still get that same data ID.

[6:53] We're building this query. This is exactly what we want to use. I'm going to go ahead and fill out the rest of the queries such as image and name and price. Because we're going to implement this on the product page itself, we also want the description.

[7:07] If we open up this description node, we'll also notice that we have a few formats that we can grab it as. We want to grab it as HTML. That way we can render it rich inside of our document by rendering it right into React.

[7:19] I'm going to go ahead and copy this entire query. I'm going to paste it right above my existing query that I copied over. I can get rid of that page products, because we don't want to grab all of the products. We want to grab that single product.

[7:31] Now, I can also rename my query to page product since we're only grabbing that one. Next, we want to define what's going to make this particular slug dynamic. To do that, we want to create a variable's property on this client.query request.

[7:46] We're going to say, we want our slug to equal params which we're grabbing from that argument inside a getStaticProps. We want to say params.productSlug. Now, like we usually do, we can test this out and see what's inside of our data object.

[8:03] We can see our beautiful product data for our mug inside the terminal. I'm going to create my constant of product and set that equal to data.data.products and pass that right in through a prop. Where additionally, at the top of the page like I did on the home page, I'm going to create that prop of product.

[8:21] Then I can start updating the data inside of my page such as the product.name. I have my product.price. We can see that we also have our product description. Again, we're going to render that as HTML. Before we start to add that, if you remember, we never created our descriptions.

[8:41] If I go back over to my content editor, I can see that I can enter in my description for this particular product. I'm going to go ahead and paste this in. We can see that I have, "Wake up with a fresh brew of Cosmo with this exclusive Cosmo mug."

[8:54] I'm going to go ahead and save and publish that. Where now, that's going to be available at product.description.html, because if you remember, we needed to select how we want it to render. To use that, I'm going to collapse this div and get rid of that paragraph tag into a self-closing tag, where on that tag, I'm going to add dangerously set inner HTML.

[9:15] I'm going to have a dynamic prop with an object that I pass in. I'm going to use __html, where I'm going to set product.description.html. When we render the page, we can see we have my dynamic name, my description, and the only thing left is that image.

[9:33] To make this easy, I'm going to head back over to my home page. I'm going to grab the same image that we use there because it's going to be the same thing. I'm going to head back over to the product slug and replace that image text, and we can see our beautiful image render.

[9:46] The trick here is, we want to make sure all that data is available for each product. Such as if we go to Cosmo pillow, we can see that it's going to try to render a page, but we don't have a description, so it's going to fail when trying to get that HTML.

[9:59] Technically to fix that, we can say for our product description, we only want to try to access the HTML using this optional chain if we have a description, which would simply not render that section.

[10:10] We want to make sure we have a description for every single one of our products. I'm going to paste in this description for my pillow as well, so you can never get enough pillows for your fort, so get comfy with a new Cosmo pillow. I'm going to save and publish it like before.

[10:23] We can see when we reload the page, that we get that beautiful description. These dynamic values also work for metadata. If we see inside the tab, it still says, product name. We can do the exact same thing inside of the Next.js head component, where instead of product name, I'm going to say product.name.

[10:39] Instead of the description, we can also set that dynamically where maybe I can say, instead of generated by create next tab, I want to find this product.name at Space Jelly gear. We can see when the page reloads, our tab has that new dynamic value.

[10:57] As one last step, we want to make sure that when we link to our products that we can click to them. Back on our home page, we can see that we have this link tag, which is the Next.js client-side routing, but we want to make sure that we're linking to the product.

[11:10] We're going to grab this product slug value, and instead of this hashtag for the href, we're going to create a dynamic prop. Where we're going to say, we want it to go to /products/.productslugvalue. As simple as that, once I click one of my products, we can see that it goes to the product page.

[11:27] At this point, we can't do anything more with this page such as adding it to the cart, but we'll see in later lessons how we can add a shopping cart and how we can add each and every one of these products to that cart.

[11:37] In review, we saw how we were able to take this query and create a dynamic query out of it with variables, where we could use the getStaticPaths function to create a path for every single one of our products.

[11:50] Then use getStaticProps to query for each and every one of those dynamically using the variables object, where just like any other page, we were able to grab that data as a prop to our product page function, where we were able to fill in our page and have our dynamic route.

Jason
Jason
~ 8 months ago

My slug doen't show up in the where clause only the id. I've checked and it is setup the same way as you did and is set as unique. Weird because there isn't anything in the docs to help.

Jason
Jason
~ 8 months ago

I was able to make it work with the id field and still love this course.