Dynamic slugs to pages

Posted
Return to overview

The logic that we used for the homepage will come into play. In orde to use more dynamic routing, we can use a special notation for the Vue file in the `pages` folder.

Duplicate the `index.vue` file in the `pages` folder and name the new file `[slug].vue`. The brackets allow Nuxt to interpret that part as a URL parameter, which will be available within the file!

We can access it via the included Vue router instance, using the `$route` property. We want the parameter with the name that matches the part within the brackets of the filename (`slug`), so we can call the value by: `$route.params.slug`.

To use it, we simply replace the static value that's targeting the homepage to a dynamic route:

<PageBody :slug="$route.params.slug" />

Note the : that's added to the `slug`, to signal that the value is an expression. The entire file should look like this:

<script lang="ts"> definePageMeta({ layout: "default", }); </script> <template> <PageBody :slug="$route.params.slug" /> </template>

To verify that everything works, run the following command in your terminal in the root of your project:

npm run dev

This spins up the Nuxt dev server. You should first see the homepage contents as JSON format. If you created another page in Contentful already, you should be able to access those contents by adding the slug to the URL. For instance:

The homepage runs on http://localhost:3000 and an About page would run on http://localhost:3000/about (provided that there's a Page in Contentful with that exact slug). If not, quickly create one and test it out!

Let's add a bit more to make sure we don't have to guess all of the pages. Let's add a dynamic menu for now.

We are going to do a couple of things. First, we're going to add a new composable that will let us grab every page from the CMS. Add the following lines the the existing `contentful.ts` composable:

interface PageNav { title: string; slug: string; };

The above defines the interface for the type PageNave (I find that a clean way of working). And add the following code as well (which return a Promise of an Array with the type PageNav. Also notice the 'pageNav' as the first parameter of the `useAsyncData` composable. That is the key on which Nuxt stores the response in cache, so it needs to be unique across the app (the same goes for the existing usePages composable obviously ]):

export const usePagesNav = async (): Promise<PageNav[]> => { const { data } = await useAsyncData('pageNav', async (nuxtApp) => { const { $contentfulClient } = nuxtApp return $contentfulClient.getEntries({ content_type: 'page', 'fields.slug[ne]': 'home' // filter out home slug }) }) const pagesNav = data.value.items .map((pageFields: any) => { const { title, slug } = pageFields.fields return { title, slug } }) return pagesNav }

This will let us get all of the pages for the moment and return the title and slug field from the response. If you want to add some filtering on what pages to show, you can either add a Boolean type in Contentful and select only those, or do some manual filtering (in this case I don't need the `home` slug).

Now let's add a component in the `components` folder called `NavBar.vue` and add the following contents:

<script lang="ts" setup> const NavItems = await usePagesNav(); </script> <template> <nav> <a v-for="item in NavItems" :key="item.slug" :href="`/${item.slug}`" :title="item.title" >{{ item.title }} </a> </nav> </template>

Note that the docs point to the usage of `<nuxt-link />` instead of the `<a />`. At the moment though, I've found that it doesn't refetch the data from the client, but only on refresh, so it's not as brainless a drop in as I expected. I experienced that the `<a />` element works flawlessly with no significant downside.

Lastly, we'll update the `default.vue` in the `layouts` folder to include the new NavBar, like so:

<template> <NavBar /> <NuxtPage /> </template>

Now you may have noticed that we're not manually importing everything. That is because Nuxt is automagically resolving the imports! That is one of the things that I like about the framework: it makes the Developer Experience such a pleasant one by having cleaner code and less overhead!

You should have a similar application as this branch on the code example, where you can access Pages from Contentful from your application. It's already coming along a bit, don't you think?

Return to overview