Generate Static Websites with Nuxt
Tags: javascript, vue, tutorial
Published: 2019-12-30 00:25:34

**Subscribe to my email list now at http://jauyeung.net/subscribe/**

**Follow me on Twitter at https://twitter.com/AuMayeung**

**Many more articles at https://medium.com/@hohanga**

Static websites are getting popular again nowadays. Informational and brochure sites no longer need to use content management systems like WordPress to be updated dynamically.

With static site generators, you can get your content from dynamic sources like headless CMS’s, APIs, and also from files like Markdown files.

Nuxt is a great static site generator based on Vue.js that is easy to use to build static websites. With Nuxt, all we have to do to build static websites from dynamic content is that we create the templates for showing the content dynamically from the dynamic sources like APIs and Markdown files. Then in the Nuxt configuration file, we define the routes statically so that it can go through the same routes to generate the content into static files.

In this article, we will build a news website using Nuxt and will use the News API, located at [https://newsapi.org/](https://newsapi.org/), for the content. You have to know Vue.js before you can build a website using Nuxt since Nuxt is a framework based on Vue.js.

To start, first we register for an API key at the News API website. It is free if we only want the headlines. We start building the website by using the Nuxt CLI. We run the `create-nuxt-app` command by typing in:

```
npx create-nuxt-app news-website
```

This will create the initial project files in the `news-website` folder. When the wizard is run, we select none for server side frameworks, none for UI framework, none for test framework, Universal for the Nuxt mode, and choose to include Axios, linting and prettifying choices are up to you.

Next we need to install some packages. We need the `@nuxtjs/dotenv` package for reading the environment variables locally and the `country-list` library for getting a list of countries in our website. To install them we run:

```bash
npm i @nuxtjs/dotenv country-list
```

Now we can start building our website. In the `default.vue` file, we replace the existing code with:

```javascript
<template>  
  <div>  
    <nav class="navbar navbar-expand-lg navbar-light bg-light">  
      <nuxt-link class="navbar-brand" to="/">News Website</nuxt-link>  
      <button  
        class="navbar-toggler"  
        type="button"  
        data-toggle="collapse"  
        data-target="#navbarSupportedContent"  
        aria-controls="navbarSupportedContent"  
        aria-expanded="false"  
        aria-label="Toggle navigation"  
      >  
        <span class="navbar-toggler-icon"></span>  
      </button> <div class="collapse navbar-collapse" id="navbarSupportedContent">  
        <ul class="navbar-nav mr-auto">  
          <li class="nav-item active">  
            <nuxt-link class="nav-link" to="/">Home</nuxt-link>  
          </li>  
          <li class="nav-item dropdown">  
            <a  
              class="nav-link dropdown-toggle"  
              href="#"  
              id="navbarDropdown"  
              role="button"  
              data-toggle="dropdown"  
              aria-haspopup="true"  
              aria-expanded="false"  
            >Headliny by Country</a>  
            <div class="dropdown-menu" aria-labelledby="navbarDropdown">  
              <nuxt-link  
                class="dropdown-item"  
                :to="`/headlines/${c.code}`"  
                v-for="(c, i) of countries"  
                :key="i"  
              >{{c.name}}</nuxt-link>  
            </div>  
          </li>  
        </ul>  
      </div>  
    </nav>  
    <nuxt />  
  </div>  
</template>

<script>  
import { requestsMixin } from "~/mixins/requestsMixin";  
const { getData } = require("country-list");

export default {  
  mixins: [requestsMixin],  
  data() {  
    return {  
      countries: getData()  
    };  
  }  
};  
</script>

<style>  
.bg-light {  
  background-color: lightcoral !important;  
}  
</style>
```

This is the file for defining the layout of our website. We added the Bootstrap navigation bar here. The bar has links for the home page and a drop-down for the list of countries. The `nuxt-link` components are all links to pages for getting the headlines for the country when the static files are generated. The countries are obtained from the `country-list` package in the `script` section by calling the `getData` function. In the `style` section, we changed the background color of our navigation bar by overriding the default color of the `.bg-light` class. The `nuxt` component in the bottom of the `template` section is where our content will be displayed.

Next we create a`mixins` folder and create a file called `requestsMixin.js` file. In there, we add:

```javascript
const APIURL = "[https://newsapi.org/v2](https://newsapi.org/v2)";  
const axios = require("axios");export const requestsMixin = {  
  methods: {  
    getHeadlines(country) {  
      return axios.get(  
        `${APIURL}/top-headlines?country=${country}&apiKey=${process.env.VUE_APP_APIKEY}`  
      );  
    }, getEverything(keyword) {  
      return axios.get(  
        `${APIURL}/everything?q=${keyword}&apiKey=${process.env.VUE_APP_APIKEY}`  
      );  
    }  
  }  
};
```

This file has the code to get the headlines by country and keyword from the News API.

Then in the `pages` folder, we create the `headlines` folder and in the `headlines` folder, create a `_countryCode.vue` file. In the file, we add:

```javascript
<template>  
  <div class="container">  
    <h1 class="text-center">Headlines in {{getCountryName()}}</h1>  
    <div v-if="headlines.length > 0">  
      <div class="card" v-for="(h, i) of headlines" :key="i">  
        <div class="card-body">  
          <h5 class="card-title">{{h.title}}</h5>  
          <p class="card-text">{{h.content}}</p>  
          <button class="btn btn-primary" :href="h.url" target="_blank" variant="primary">Read</button>  
        </div>  
        <img :src="h.urlToImage" class="card-img-bottom" />  
      </div>  
    </div>  
    <div v-else>  
      <h2 class="text-center">No headlines found.</h2>  
    </div>  
  </div>  
</template>

<script>  
import { requestsMixin } from "~/mixins/requestsMixin";  
const { getData } = require("country-list");

export default {  
  mixins: [requestsMixin],  
  data() {  
    return {  
      headlines: [],  
      countries: getData()  
    };  
  },  
  beforeMount() {  
    this.getHeadlinesByCountry();  
  },  
  methods: {  
    async getHeadlinesByCountry() {  
      this.country = this.$route.params.countryCode;  
      const { data } = await this.getHeadlines(this.country);  
      this.headlines = data.articles;  
    }, 

    getCountryName() {  
      const country = this.countries.find(  
        c => c.code == this.$route.params.countryCode  
      );  
      return country ? country.name : "";  
    }  
  }  
};  
</script>
```

In the file, we accept the route parameter `countryCode` and from there, we call the `this.getHeadlines` function from the `requestsMixin` that we made earlier and included in this component to get the headlines from the News API. Then the results are displayed in Bootstrap cards in the `template` section. In the template, we get the country name by finding it from the `country-list` data. We display a message if there are no headlines found. In general, if we want to make a page that accepts URL parameters, we have to make a file with an underscore as the first character and the variable name of the URL parameter that we want. So `_countryCode.vue` will let us get the `countryCode` parameter by using `this.$route.params.countryCode` in this example.

Next in `index.vue` in the `pages` folder, we replace the exist code with:

```javascript
<template>  
  <div class="container">  
    <h1 class="text-center">Home</h1>  
    <div class="card" v-for="(h, i) of headlines" :key="i">  
      <div class="card-body">  
        <h5 class="card-title">{{h.title}}</h5>  
        <p class="card-text">{{h.content}}</p>  
        <button class="btn btn-primary" :href="h.url" target="_blank" variant="primary">Read</button>  
      </div>  
      <img :src="h.urlToImage" class="card-img-bottom" />  
    </div>  
  </div>  
</template><script>  
import { requestsMixin } from "~/mixins/requestsMixin";  
const { getData } = require("country-list");

export default {  
  mixins: [requestsMixin],  
  data() {  
    return {  
      headlines: []  
    };  
  },  
  beforeMount() {  
    this.getHeadlinesByCountry();  
  },  
  methods: {  
    async getHeadlinesByCountry() {  
      const { data } = await this.getHeadlines("us");  
      this.headlines = data.articles;  
    }  
  }  
};  
</script>

<style>  
</style>
```

This lets us display the headlines for the US in the home page. It works similarly to the `_countryCode.vue` page except that we only get the US headlines instead of accepting a URL parameter to get headlines from different countries depending on the URL.

Next we create an `create-env.js` in the project’s root folder and add the following:

```javascript
const fs = require('fs')  
fs.writeFileSync('./.env', `API_KEY=${process.env.API_KEY}`)
```

This allows us to deploy to Netlify because we need to create the `.env` file on the fly there from the entered environment variables. Also, we create the `.env` file manually and put the `API_KEY` as the key and the News API API key as the value.

Next in `nuxt.config.js` , we replace the existing code with:

```javascript
require("dotenv").config();  
const { getData } = require("country-list");

export default {  
  mode: "universal",  
  /*  
   ** Headers of the page  
   */  
  head: {  
    title: "News Website",  
    meta: [  
      { charset: "utf-8" },  
      { name: "viewport", content: "width=device-width, initial-scale=1" },  
      {  
        hid: "description",  
        name: "description",  
        content: process.env.npm_package_description || ""  
      }  
    ],  
    link: [  
      { rel: "icon", type: "image/x-icon", href: "/favicon.ico" },  
      {  
        rel: "stylesheet",  
        href:  
         "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"  
      }  
    ],  
    script: [  
      { src: "https://code.jquery.com/jquery-3.3.1.slim.min.js" },  
      {  
        src:  
          "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"  
      },  
      {  
        src:  
          "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"  
      }  
    ]  
  },  
  /*  
   ** Customize the progress-bar color  
   */  
  loading: { color: "#fff" },  
  /*  
   ** Global CSS  
   */  
  css: [],  
  /*  
   ** Plugins to load before mounting the App  
   */  
  plugins: [],  
  /*  
   ** Nuxt.js dev-modules  
   */  
  buildModules: [],  
  /*  
   ** Nuxt.js modules  
   */  
  modules: [  
    // Doc: https://axios.nuxtjs.org/usage    
    "@nuxtjs/axios",  
    "@nuxtjs/dotenv"  
  ],  
  /*  
   ** Axios module configuration  
   ** See https://axios.nuxtjs.org/options
   */  
  axios: {},  
  /*  
   ** Build configuration  
   */  
  build: {  
    /*  
     ** You can extend webpack config here  
     */  
    extend(config, ctx) {}  
  },  
  env: {  
    apiKey: process.env.API_KEY || ""  
  },  
  router: {  
    routes: [  
      {  
        name: "index",  
        path: "/",  
        component: "pages/index.vue"  
      },  
      {  
        name: "headlines-id",  
        path: "/headlines/:countryCode?",  
        component: "pages/headlines/_countryCode.vue"  
      }  
    ]  
  },  
  generate: {  
    routes() {  
      return getData().map(d => `headlines/${d.code}`);  
    }  
  }  
};
```

In the `head` object, we changed the `title` so that we display the title we want instead of the default title. In the `link` section, we add the Bootstrap CSS, and in the `script` section, we add the Bootstrap JavaScript files and jQuery, which is a dependency of Bootstrap. Since we want to build a static site, we cannot use BootstrapVue because it is dynamic. We do not want any dynamic JavaScript in the generated output, so we have to use plain Bootstrap. In the `modules` section, we added `”@nuxtjs/dotenv”` to read the environment variables from the `.env` file that we created into our Nuxt app. We also added `require(“dotenv”).config();` so that we get the `process.env.API_KEY` which can be added to this configuration file. We have to do this so that we don’t have to check in our `.env` file. In the `env` section, we have the `apiKey: process.env.API_KEY || “”`, which is what we get by reading the API KEY in the `.env` file with `dotenv`.

In the `router` section, we define the dynamic routes so that they can be viewed when users click on links with the given URLs or click on a link with such URLs. Nuxt also uses these routes to generate static files. In the `generate` section, we define the routes that Nuxt will traverse to generate the static files for the static website. In this case, the array of routes consist of routes for the headlines page that we created earlier. It will loop through them to get the data for them, then render them and generate the file from the rendered results. The folder structure will correspond to the routes. So since our `path` is `/headlines/:countryCode` , the generated artifact will have the `headlines` folder along withe all the country code as names of subfolders, and inside each folder there will be a `index.html` with the rendered content.

Now we are ready to deploy our website to Netlify. Create a Netlify account by going to [https://www.netlify.com/](https://www.netlify.com/). The free plan will work for our needs. Then commit your code to a Git repository hosted on GitHub, Gitlab or Bitbucket. Then when you log in to Netlify, click on New site from Git. From there, you can add your Git repository that’s hosted in one of those services. Then when you’re asked to enter the Build Command, enter `node ./create-env.js && npm run generate`, and the Publish directory would be `dist` .

After that, enter the API Key in the `.env` file into the Environment variables section of the website settings, which you can go to by clicking on the Environment link on the Build & deploy menu. Enter `API_KEY` as the key and your News API API key as the value. Then click the save button.

Once you commit and push everything in a Git repository hosted by GitHub, Gitlab or Bitbucket, Netlify will build and deploy automatically.