Personal Website with Nuxt JS
I've wanted to build a website for many years to host a variety of things, including my resume and some project examples. The domain name was already purchased and attached it to a simple Google Sites layout but I simply wasn't happy with the result. Instead of paying for a big-name web development and hosting services, I took the opportunity to try out a flavor of the Vue 2 front-end framework.
Tech Stack
Even with a plan, I had to make a few detours. Here's what I ended up with.
Hosting with GitHub Pages
Pages was a great option for many reasons, the first being that it's free. GitHub allows for static hosting at no charge for public repositories. They also allow for hosting a Pages site off a private repo as long as you pay for the Pro account. I actually already pay for Pro, so there was no reason not to try Pages first.
There is also detailed documentation on how to use a custom domain name at no extra charge (outside buying the domain, of course). Plenty of frameworks work directly with GitHub Pages by default, but the service is very flexible. Many of the larger and more feature-robust frameworks are capable of hosting off the service.
Development with the Nuxt JS Framework
This project was originally planned to be done with vanilla Vue 3 but I ran into some problems with getting site preview cards to display correctly on social media.
At first, the cards wouldn't appear at all, which I learned was because I wasn't filling the site headers with the necessary metadata.
After a little bit of research into the Open Graph protocol and stumbling into the vue-meta
package, I tried again.
I had success! Well... kinda.
On publishing my changes, I noticed that the head was not being populated in time for the social media embedded cards to render properly.
A little more research told me that I needed to ensure my header content was being rendered at build time for any API requests looking for the metadata.
The way I had it configured, vue-meta
was populating the head on the client side, too late for a server only interested in the head to render an embed.
This lead me down the rabbit-hole of "static site generation".
In theory, this was something that Vue 3 should have been more than able to handle by default. I (un?)fortunately stumbled on this GitHub issue that describes a similar goal to what I hand in mind. It also looked like some of the frameworks built around Vue had not caught up to version 3. After accepting that I would have to downgrade to get a smooth development experience, I had a new choice to make.
VuePress, VitePress, Gridsome, and Nuxt JS were all suggested. I tried most of them and at the time still wanted to use both Bootstrap and TypeScript as well.
Nuxt JS seemed to have the most straight forward setup, and although TypeScript can work with it, I realized I wasn't going to be using enough scripting to need the rigid structure.
There are a ton of features that come pre-packaged, so dropping TypeScript was worth it.
It even came with built-in support for an older version of vue-meta
, which made porting my head content from my first draft even easier.
Styling with Bootstrap 5 and Sass
This one was an easy decision. It came up in the tutorial I was watching about Vue 3, so it only seemed natural to include it with my original plan. The same instructor even had an entirely separate course for the package. There are a few quirks with using it, primarily filling the DOM with more containers that should be necessary, but it otherwise produces very lovely, mobile-friendly results.
I had styled a good portion of my site with Bootstrap 5 before I had to switch gears on the framework, so I knew the final product had to also include it to avoid any further setbacks.
I also knew that Bootstrap worked well with the Sass language, which my stylesheets were also using. The nested declarations and use of variables helped keep things trim, even if they were tricky to configure at first.
Challenges
As I mentioned above, I had to switch course part-way through the project, but after I had settled with Nuxt, there were still some puzzles to figure out.
Configuring Bootstrap Styles and JS
Bootstrap actually has a couple different components to it. Of course there are the stylesheets, but some functionality comes from JavaScript. In particular the collapsing menu on the mobile version of this site needs the JavaScript portion of Bootstrap.
Vanilla Vue
A plain Vue app can add both the JavaScript and the stylesheets fairly easily.
As long as bootstrap
is included in the package.json
dependencies, the script can be loaded in the app's entry point and doesn't need to be explicitly added to the Vue instance.
import 'bootstrap';
Adding the styles is just another import in the base Vue file. As long as the style isn't scoped, it will apply the class styles through the entire application. If you want to set up the brand colors (or any other variables), those can be declared before the file import to override any preset values.
<!-- Do not use scope! -->
<style lang="scss">
/* Add or change variables before importing */
$primary: rgb(0, 98, 204);
@import '../node_modules/bootstrap/scss/bootstrap.scss';
</style>
This can also be done in a separate stylesheet and imported into App.vue
.
Doing this with Nuxt is a little different though.
A Nuxt app has neither a main.js
nor an App.vue
file to launch from.
Instead, it has a nuxt.config.js
file where all of the components, plugins, and defaults are declared.
In order to get Bootstrap working the way I had for the first version of this project, I ended up doing the imports a little more manually.
Adding Bootstrap Styles
There is a dedicated entry in nuxt.config.js
for adding stylesheets.
In my case, I have a couple of files in my assets
directory that include the base import for Bootstrap.
I then linked my primary stylesheet my config.
Alternatively, Bootstrap can be included the same way I've included the icons package below.
export default {
// ...
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
{ src: '~/assets/style/site.scss', lang: 'sass' },
{
src: '~/node_modules/bootstrap-icons/font/bootstrap-icons.css',
lang: 'css',
},
],
// ...
};
The import line for Bootstrap is slightly different inside the stylesheet from my assets
directory than it was for the Vue 3 app.
For some reason, Nuxt understands where the Bootstrap module is from this path, even though it's entirely unclear that this would be pointing to ../../node_modules/bootstrap/scss/bootstrap.scss
during build time.
@import '~bootstrap/scss/bootstrap';
Otherwise the same rules apply, add or change any variables before the import and Bootstrap should pick them up with no problem.
Adding Bootstrap JS
Adding in the JavaScript wasn't as straight-forward.
I initially was using the bootstrap-vue
package, but that consistently produced warnings on build.
I tried adding imports to components' script tags, but for some reason the path wouldn't resolve and cause runtime issues. This wasn't a problem for other script files, just for Bootstrap itself.
One suggestion I came across was to simply copy the minified script (along with it's .map
file) to the static
directory and reference it in the header.
This does work, but it means adding an unnecessary file to the repository.
Fortunately Bootstrap has a CDN that can be referenced the same way.
export default {
// ...
head() {
return {
script: [
{ src: '/bootstrap.bundle.min.js' }, // This requires a copy in your `static` folder
{
src:
'https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js',
},
],
};
},
// ...
};
I prefer referencing the CDN in order to keep the repo slim but this may mean a slightly slower load time.
Enabling Prism Line Numbers and Highlighting
The @nuxt/content
module comes with Prism included, but the plugins for line numbers and highlighting features are applied on the server side.
Since this is a static site, Prism wasn't loading as expected and a few lines needed to be added both for the JavaScript and for the styles as described in the issue here.
I have a separate component for rendering the article layout and I don't intend to use Prism anywhere else, so I included my javascript modifications directly inside the component.
<script>
import Prism from 'prismjs';
import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
import 'prismjs/plugins/line-highlight/prism-line-highlight.js';
export default {
// ...
mounted() {
// ...
Prism.highlightAll();
// ...
},
// ...
};
</script>
The styles for these plugins also need to be included explicitly.
The GitHub issue simply copied and pasted from he plugin's examples, but the stylesheets are actually included as a dependency of @nuxt/content
.
I simply added them to nuxt.config.js
the same way I did with the Bootstrap Icons.
export default {
// ...
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
{ src: '~/assets/style/site.scss', lang: 'sass' },
{
src: '~/node_modules/bootstrap-icons/font/bootstrap-icons.css',
lang: 'css',
},
{
src:
'~/node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css',
lang: 'css',
},
{
src:
'~/node_modules/prismjs/plugins/line-highlight/prism-line-highlight.css',
lang: 'css',
},
],
// ...
};
Remarks
This project is still a work in progress. As I add more content, this page will be updated with additional snippets.
- Nuxt JS
- Bootstrap 5
- Sass
- GitHub Pages