How to create a blog with Nuxt.js and Prismic

Hello everyone! Today I will be showing you how I created this blog with Nuxt.js and Prismic ๐Ÿ˜€ I'm going to explain each and every step and try to be as concise as possible!

First, I started off by creating a Nuxt application:
npx create-nuxt-app <project-name>

Go through the whole entire process and choose whatever frameworks you prefer.

After you're done, you can run the project with:
cd <project-name>
npm run dev

In your Nuxt.js application, there should be a directory called pages. This is where you can create new static pages. Whenever you create a new page, Nuxt.js automatically generates routes for them. We will need two new pages for our blog: one for showing a list of blog posts, and one for viewing a single blog post.

First, create a new folder called blog, and within it create two new files called _uid.vue and index.vue.

You should have something like this:

Nuxt.js Blog folder

The index.vue page will display the list of blog posts when the user navigates to '/blog'.
The _uid.vue page displays a single blog post when the user navigates to '/blog/${blog_uid}', where blog_uid is the unique identifier for that specific blog. That blog_uid would be used to query Prismic's API to find the data for that one specific blog!

But before we proceed to the code, create an account on Prismic first at https://prismic.io/signup. After doing so, you should see a page for creating a new repository:

Prismic Repository

After going through all the steps for creating a repository, go to the left side menu and click 'Custom Types.' You should see a screen like this:

Create new custom type

Enter "blog-post" and click Create new custom type. We are choosing a repeatable type because we will have multiple instances of blog posts on our website.

blog post custom type on prismic

You will see a new page appear where you can drag and drop different cards from the under Build mode to the left side. Here, choose the following properties for a single blog post:

  • UID
  • Title
  • Rich Text
  • Date

**The API ID is important because that is what would appear in the API responses**

For your code to work, make sure you set your API ID's corresponding to the fields you chose above:

  • uid
  • title
  • blog_content
  • date

You should end up having something like this:

prismic custom types

Next, let's create our first blog post! Go to the menu on the left side and click Content and you should see a page like this:

create content prismic

Click the green pencil button or the Create New button on the top right. You will see a form for creating a new blog post based on the custom type we created! Create a new blog post and make sure to save and publish it from the top right.

Next, grab the entry point for your API. Navigate to Settings from the left-side menu, then go to API & Security under Configuration. Here, you will see your API Endpoint. Copy this because we will be using it later on our pages.

Next, we need to set up Prismic on our Nuxt.js application. Stay put, we're almost there! The following steps are remaining:
  1. Install prismic-nuxt module and prismic-javascript module and upgrading Nuxt
  2. Configure our nuxt.config.js file so we can connect to Prismic
  3. Using HTML serializer for link wrapping (Helps navigate to other Prismic documents)
  4. Using Link Resolver so links will return an appropriate path to our Prismic documents (used within HTML serializer)
  5. Create a Blog Widget component
  6. Add code to our index.vue and _uid.vue pages
  7. Return Dynamic routes in nuxt.config.js so when creating a build these pages will be generated
  8. Deploy to Netlify
1. Install prismic-nuxt and prismic-javascript modules and upgrade Nuxt:
npm i @nuxtjs/prismic prismic-javascript
npm upgrade nuxt
2. Configure our nuxt.config.js file so we can connect to Prismic:
import Prismic from 'prismic-javascript';

export default {
  mode: 'universal',
  ...... // Add prismic property
  prismic: {
    endpoint: INSERT_API_ENDPOINT_HERE",
    linkResolver: '@/plugins/link-resolver',
    htmlSerializer: '@/plugins/html-serializer'
  },
  ........
  generate: {  }
}

Here we are just adding prismic to our nuxt.config.js file, and importing Prismic which we will be using later. Please do not remove anything.

3. Using HTML serializer for link wrapping (Helps navigate to other Prismic documents)

Create a new file called html-serializer.js within the plugins and paste the following code:

/**
 * To learn more about HTML Serializer check out the Prismic documentation
 * https://prismic.io/docs/vuejs/beyond-the-api/html-serializer
 */

import linkResolver from "./link-resolver"
import prismicDOM from 'prismic-dom'

const Elements = prismicDOM.RichText.Elements

export default function (type, element, content, children) {
  // Generate links to Prismic Documents as <router-link> components
  // Present by default, it is recommended to keep this
  if (type === Elements.hyperlink) {
    let result = ''
    const url = prismicDOM.Link.url(element.data, linkResolver)

    if (element.data.link_type === 'Document') {
      result = `<nuxt-link to="${url}">${content}</nuxt-link>`
    } else {
      const target = element.data.target ? `target="'${element.data.target}'" rel="noopener"` : ''
      result = `<a href="${url}" ${target}>${content}</a>`
    }
    return result
  }

  // If the image is also a link to a Prismic Document, it will return a <router-link> component
  // Present by default, it is recommended to keep this
  if (type === Elements.image) {
    let result = `<img src="${element.url}" alt="${element.alt || ''}" copyright="${element.copyright || ''}">`

    if (element.linkTo) {
      const url = prismicDOM.Link.url(element.linkTo, linkResolver)

      if (element.linkTo.link_type === 'Document') {
        result = `<nuxt-link to="${url}">${result}</nuxt-link>`
      } else {
        const target = element.linkTo.target ? `target="${element.linkTo.target}" rel="noopener"` : ''
        result = `<a href="${url}" ${target}>${result}</a>`
      }
    }
    const wrapperClassList = [element.label || '', 'block-img']
    result = `<p class="${wrapperClassList.join(' ')}">${result}</p>`
    return result
  }

  // Return null to stick with the default behavior for everything else
  return null
}

You can learn more about this code here: https://prismic.io/docs/vuejs/beyond-the-api/html-serializer

4. Using Link Resolver so links will return an appropriate path to our Prismic documents (used within HTML serializer)

Create a new file called link-resolver.js within the plugins and paste the following code:

/**
 * To learn more about Link Resolving check out the Prismic documentation
 */

export default function (doc) {
    if (doc.isBroken) {
      return '/not-found'
    }
  
    if (doc.type === 'blog_home') {
      return '/'
    }
  
    if (doc.type === 'post') {
      return '/blog/' + doc.uid
    }
  
    return '/not-found'
  }

This will help us to navigate to our other Prismic documents. Learn more about Link Resolver here: https://prismic.io/docs/vuejs/beyond-the-api/link-resolving

5. Create a Blog widget Component

Create a blog widget component. This a component we will be reusing for each blog item. Each blog widget will contain the blog's title,

<template>
  <nuxt-link :to="`/blog/${post.uid}`">
    <div>
        <h1>{{ $prismic.asText(post.data.title) }}</h1>
        <p>{{getFirstParagraph(post)}}</p>
        <div>
          <p>{{formattedDate}}</p>
        </div>
    </div>
  </nuxt-link>
</template>

<script>
import LinkResolver from "~/plugins/link-resolver.js";

export default {
  props: ["post"],
  data() {
    return {
      link: "",
      formattedDate: ""
    };
  },
  name: "blog-widget",
  methods: {
    // Function to get the first paragraph of text in a blog post and limit the displayed text at 50 characters
    getFirstParagraph(post) {
      const textLimit = 100;
      const slices = post.data.blog_content;
      let firstParagraph = "";
      let haveFirstParagraph = false;

      slices.map(function(slice) {
        if (slice.type == "paragraph" && !haveFirstParagraph) {
          firstParagraph += slice.text;
          haveFirstParagraph = true;
        }
      });

      const limitedText = firstParagraph.substr(0, textLimit);
      if (firstParagraph.length > textLimit) {
        return limitedText.substr(0, limitedText.lastIndexOf(" ")) + "...";
      } else {
        return firstParagraph;
      }
    }
  },
  created() {
    this.link = LinkResolver(this.post);
    this.formattedDate = Intl.DateTimeFormat("en-US", {
      year: "numeric",
      month: "short",
      day: "2-digit"
    }).format(new Date(this.post.data.date));
  }
};
</script>

<style scoped>
</style>
6. Add code to our index.vue and _uid.vue pages

Finally, all we have to do is add the code to our pages!

index.vue:
// Index.vue
<template>
  <div>
    <blog-widget v-for="post in posts" :key="post.id" :post="post"></blog-widget>
  </div>
</template>

<script>
import BlogWidget from "~/components/BlogWidget.vue";

export default {
  name: "Blog",
  components: { BlogWidget },
  async asyncData({ $prismic, error }) {
    try {
      const { results } = await $prismic.api.query(
        $prismic.predicates.at("document.type", "blog-post"),
        { orderings: "[my.blog-post.date desc]", pageSize: 5 }
      );
      return {
        posts: results
      };
    } catch (e) {
      error({ statusCode: 404, message: "Page not found" });
    }
  },
};
</script>

<style scoped>
</style>
_uid.vue
<template>
  <div>
    <h1>{{ $prismic.asText(post.data.title) }}</h1>
    <p>Posted on {{formattedDate}}</p>
    <div>
      <prismic-rich-text :field="post.data.blog_content" />
    </div>
  </div>
</template>

<script>
export default {
  async asyncData({ $prismic, error, route }) {
    try {
      const { results } = await $prismic.api.query(
        $prismic.predicates.at("my.blog-post.uid", route.params.uid)
      );

      console.log(results[0]);

      return {
        post: results[0],
        formattedDate: Intl.DateTimeFormat("en-US").format(
          new Date(results[0].data.date)
        )
      };
    } catch (e) {
      error({ statusCode: 404, message: "Page not found" });
    }
  }
};
</script>

<style scoped>
.content {
  margin-top: 30px;
}
</style>

Now, let me explain what these pages do. In index.vue, we're querying the API finding all of our blog posts by passing in the document.type of "blog-post." We order it by the most recent and the page size is set to 5. Then, we return an object containing those posts, and we use it to render a blog-widget for each of the posts we got back from Prismic!

In _uid.vue, we are displaying the whole entire blog's content. We are querying Prismic by passing in the uid that comes from the route params. Pretty straight forward right? ๐Ÿ˜‰

7. Return Dynamic routes in nuxt.config.js so when creating a build these pages will be generated

We're almost done. We need to configure the 'generate' property in our nuxt.config.js file so that all our dynamic routes are registered in our Nuxt app. The reason is that these pages have to be generated during the build process. Only then search engine crawlers will be able to see the content on our pages!

nuxt.config.js
  generate: {
    routes: async function () {
      try {
        const api = await Prismic.api("INSERT_YOUR_API_ENDPOINT_HERE");
        const posts = await api.query(Prismic.Predicates.at('document.type', 'blog-post'));
        const routes = await posts.results.map(payload => {
          return {
            route: `/blog/${payload.uid}`,
            payload
          }
        });

        return Promise.all([routes]).then(values => {
          return [...values[0]];
        });
      } catch (e) {
        console.log(e);
      }
    }
  }

This explains why we imported Prismic in the beginning of this tutorial. The property 'routes' must return an array of all the dynamic routes we need to generate. So let's say we created a blog post:

  • My first blog post!
(This is an example, don't copy this code!)

The routes property should be returning an array like this:

  generate: {
    routes: [
      {
        route: `/blog/my-first-blog-post`,
        payload: { title: 'My First Blog post', date: '03/28/2020', content: 'My blog content' }
      }
    ]
  }

So essentially what we're doing is we're grabbing all the uid's from each blog object from Prismic and creating a route from it. Then we set the payload to the blog's data. Finally, we return those values in an array.

8. Deploy to Netlify

Finally, the last step is to deploy it to Netlify. It's pretty simple. First, set up a repository on GitHub and push your project files to it. Then, create an account on Netlify and go through the process. The main thing is, it will ask you for the Build Command and Publish Directory. Set the Build command to npm run generate and set the Publish directory to dist.

Click 'New site from Git' and go through the process

netlify

Set your Build command and Publish directory to the following:

netlify build command and publish directory

Go through the remaining steps and your website should be deployed within minutes!

And there you have it! That's how you can create a blog using Nuxt.js and Prismic within minutes (hopefully? ๐Ÿ˜…)

If you have any questions, please don't hesitate to contact me! You can email me at faraazmotiwala3@gmail.com or even shoot me a DM on Instagram @faraaz.io!