How I built my Next.js Portfolio Site

Next.js can be a great choice for static sites and being a freelance developer specialized in fast websites, it was an obvious choice for me. Let's see how and what I used to build this site.


For my portfolio site the goal was rather clear.

  • I wanted the site to be fast and reliable (no down-times).
  • It should have a blog that is easy to edit. So basically blog posts should not be hardcoded as HTML or JSX.
  • The portfolio site should have great SEO.
  • Obviously the page needs to be responsive.
  • It should have a great developer experience (DX).

From my point of view, the site I created fulfills all these requirements. While there are still points that can be improved upon (as always), I'm quite happy with the current state of the site and will share in the following the details of how I built it and how I'm currently running it.


The initial version of this site was based up Material UI, which is a set of react components following the material design standards.
While this served me well in the beginning and got me up and running quickly, the feel of the components was not exactly what I've been looking for. Additionally, the components and styling are quite heavyweight and better suited for web apps instead of static sites that do not require much dynamic interaction.

For this reason I migrated the site to tailwindcss with some styled-components for more complex cases.

This switch has served me really well, as the styling of the site became much easier and faster. Nowadays tailwindcss + styled-components is my go-to styling approach for all react based websites, since it's easy to work with and can be adapted to the needs of any website.

Performance Optimization

I definitely wanted the site to be fast and next.js is a great choice for that. However, there are some things I needed to look out for.

The most obvious and common reason for a slow static site, are unoptimized images. Next.js theoretically solves this problem with its specific next image component, which is constantly worked at and improved upon. However, unlike gatsby, next.js doesn't generate the optimized images at build time, but rather generates and caches them on demand when served as a dynamic site with SSR.

Yet, I definitely wanted the site to be statically exported, as there's really no need for a running server. For this reason I'm using the (next-optimized-images)[] plugin, which is able to generate optimized images for next.js during build time. If you want to learn more about the plugin, I also wrote a blog post on next-optimized-images.

With the image issue out of the way, the next challenge were some heavyweight components (provided by external) libraries, which caused a bloat in size of initial load JavaScript. This can be easily solved with the dynamic import capabilities provided by next.js via dynamic.


Since the performance of the website was already really good, I just had to look out for other core web vitals.

To verify the site's SEO in terms of structure and elements, I mainly use two tools:

  1. Google Lighthouse, which checks a site for performance, accessibility, best-practices and SEO. Although there is nothing official on this, I think it's fair to assume that an automated tool provided by google reflects also how their crawler determines a website's quality.
  2. The Meta SEO Inspector Chrome Plugin to perform additional SEO checks, and especially to test if pages are shareable and fulfill the open graph protocol, which is especially used by social sites, like twitter, to create preview cards of a link.

For next.js I found the next-seo plugin especially useful.


Since many of my applications run on virtual servers and I'm quite familiar with Docker, I initially hosted the portfolio on one of the existing servers. To this end I created a Docker container with NGINX and wrote a simple script for the build & deploy pipeline.
However, since building the site became more complex over time and the stability of the VPS with other applications running it become suboptimal, I had two decide between two options.
Either set the site up on it's own VPS for better performance and stability, or go with a hosting service.

I think it's obvious that for a static site the latter option is preferable. Hosting via Netlify took away all the effort of maintaining my own build & deploy pipeline and provided better performance and reliability of the site as a whole.

Publishing Workflow

It was a clear no-go to write each blog post in code, therefore creating a separate jsx page each time, was clearly no option.

However, being a coder, I was able to go an easy route in the beginning by simply writing my posts in markdown and having them in the repository itself, as there was clearly no need to run a CMS like Wordpress or Strapi just for this, especially considering the cost and effort to host such a CMS.

But... as time goes by and I published posts regularly, it sort of became a hassle to write posts inside my IDE. Luckily there are a ton of great headless CMS options to use for JAM stack sites.

For my use case I settled on using NetlifyCMS. The reasons for this are that NetlifyCMS is a pleasure to work with. Its admin interface is super simple and it's possible to create your own preview components in the netlifyCMS admin interface.
Furthermore, since many of my simpler sites are hosted on netlify, it reduces friction to use multiple tools by the same vendor.

Things I might want to change

While I'm super happy with the current state of the website, I'm considering to move all images over to Cloudinary or a similar image hosting service. With the improvements of the next.js image component in version 11, I'd really love to make use of the integrated component. Additionally, my build pipeline would be simplified and with an ability to crop images in height (next-optimized-images just scales images, but doesn't crop them), I could improve the site's performance further.
I'll have to keep this improvement in mind and periodically reevaluate if the site reached a point where the benefits would outweight the effort of the migration.