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/, 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:
npm i @nuxtjs/dotenv country-list
Now we can start building our website. In the default.vue
file, we replace the existing code with:
<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 amixins
folder and create a file called requestsMixin.js
file. In there, we add:
const APIURL = "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:
<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:
<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:
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:
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/. 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.
One reply on “Generate Static Websites with Nuxt”
great article, thank you