My portfolio
Introduction
This is the first post on this new portfolio website. The website was written using Nuxt.js and relies heavily on the Nuxt Content package for writing pages using markdown.
The purpose of creating this portfolio is twofold. Firstly it is an attempt to keep my hand in at web development by learning a new web framework. WIth experience working in a professional context with VUE.js and AngularJS I wanted to harness the benefits of a "universal" app development framework such as Nuxt.js and build something that renders first on the server for the benefits of speed and SEO. Secondly I want a place to upload blog posts in to accompany the machine learning projects that I am working on. Having recently completed a Master's in Artificial Intelligence and Machine Learning I am building a portfolio of toy ML projects to advance my understanding of the subject and ultimately gain employment as a data scientist or machine learning engineer.
Implementation
Pages
As mentioned above Nuxt Content is at the center of this web app. Posts are stored in the /blog folder and queried with the following code:-
const query = queryContent("/blog")
.where({ _path: { $ne: "/blog" } })
.only(["_path", "title", "publishedAt"])
.sort({ publishedAt: -1 });
The title is inferred from the h1 of the markdown, the publishedAt property is inserted in the markdown file using front matter. According to the documentation "Front-matter is a convention of Markdown-based CMS to provide meta-data to pages, like description or title." The pages are then sorted by the publication date and grouped according to year for display in the blog table.
Table of Contents
Each blog post displays a table of contents, these are obtained from the doc.body.toc.links
and passed as props to the <TocLinks>
component, which has the following template:-
<template>
<ul>
<li v-for="link in links" :key="link.id">
<NuxtLink
:to="{ path: route.path, hash: `#${link.id}` }"
:class="{
'ml-4': level === 1,
'ml-8': level === 2,
'ml-12': level === 3,
'ml-14': level === 4,
'ml-16': level === 5,
'text-green-600 dark:text-green-400':
activeId === link.id,
}"
>
{{ link.text }}
</NuxtLink>
<TocLinks
:links="link.children"
:level="level + 1"
:active-id="activeId"
/>
</li>
</ul>
</template>
As can be seen the component is called recursively to render progressively indented section headers. The IntersectionObserver
class is also used to display the current section as it scrolls into view, the highlighted section will change as the very top of the section scrolls across the top of the page:-
const callback = (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
activeId.value = entry.target.id;
break;
}
}
};
observer = new IntersectionObserver(callback, {
root: null,
rootMargin: "0% 0px -95% 0px",
threshold: 1,
});
elements = document.querySelectorAll("h2, h3, h4, h5, h6");
for (const element of elements) {
observer.observe(element);
}
Aesthetics
The overall style of the site is left deliberately minimal, save for some animations on page transition and some emojis 😉. The reason for this is that the primary focus is on machine learning projects, CSS is not my forte.
It is possible to toggle the theme between light, dark and system settings on the right of the navbar. The system setting will adopt the colour set in the users system preferences. The inbuilt useColorMode()
function returns the color mode and works out of the box with Tailwind css. Setting global css styles for the body element is performed in default.vue
along with setting the font as Roboto.
<style>
body {
font-family: "Roboto";
@apply
bg-white
dark:bg-gray-900
text-gray-700
dark:text-gray-300;
}
</style>
If your system setting is light mode... try the dark mode! It looks way better 🌙
Deployment
The site is hosted using render this provides a simple and intuitive service for publishing a static site directly from a git repository. As can be seen from the projects page my git provider of choice is gitlab. SSR (server side rendering) is enabled by default in Nuxt.js meaning universal rendering will occur. "Universal rendering allows a Nuxt application to provide quick page load times while preserving the benefits of client-side rendering. Furthermore, as the content is already present in the HTML document, crawlers can index it without overhead." (documentation)
Conclusion
This was a brief overview of what went into making this site. My overall feelings are that it will be easy to maintain as it is written in markdown. This means producing blog posts should require minimal coding and leaves me to focus on other projects.
I would like to acknowledge Piotr Jura, the instructor of the udemy course "Master Nuxt 3 - Full-Stack Complete Guide". The website is largely built on this course with a few small tweaks.
More blog posts will follow in 2025 as I build out my machine learning portfolio. There may also be some game dev posts. Until then, thankyou for reading and have a great 2025!