How I Re-designed And Built My New Website Using Next.js And Markdown

Back in December 2020, as a junior developer, I proudly hit the launch button on my first portfolio website. I had spent hours coding, tinkering with design choices and weighing tech options. I had hopped on the JAM-stack bandwagon, which was very hyped at the time. My website had a frontend powered by Gatsby.js, coupled with a headless CMS setup that tapped into the WordPress API via GraphQL. The images and text were fetched from the API at build time, and the website was statically generated. It worked perfectly, but the problem was, I had made myself reliant on the WordPress backend.

Fast forward 3 years, and things had taken a turn. I had a new job and hadn't updated things in a while. My webhosting service had expired and the headless WordPress backend was no more. A rebuild of the repo would break the website, and it presented a dilemma. Should I uncouple the site from its WordPress roots and re-write all content manually? Or was it time for a fresh start? I chose the latter, and here's how the process went:

The design

Just to be perfectly clear. I'm not a designer. I'm not that good at it but I'm learning to enjoy it, and slowly but surely, it's growing on me. Coding is the fun part and therefore my process when starting a new side project in the past was often:

  1. Get an idea 💡
  2. Start coding 💻
  3. Play around with styles until it looks alright 🥴

The design was an after thought and it showed in my projects. This had to stop and this time I was determined to do it right!

So, my design journey began, with my first attempt at a dark mode look that I thought would be cool. But after a couple of hours I still wasn't feeling it so I decided to start again. This time, I went for a lighter color palette and a more minimalistic style. A while back I had went on fiverr and had a cartoon style profile picture made for me which really spiced up the design. 10€ well spent! After a few hours I started to get excited about this. It actually started to look decent!

Choosing a technical setup

Ok, so I had my design and I had an idea of what functionality was needed. What else? I wanted to be able to scale the website beyond just a landing page but it wasn't going to be that complex of a project so I also didn't want to over do it. I wanted to try something new but not go too far out of my comfort zone. I wrote down my needs:

  • Fast
  • Modern tech
  • Scalable, but keep it simple
  • Typescript

Considering I'm the only person who will curate the content on this website and since the headless CMS was a bit of a headache, I decided to ditch that idea. The content could be static. My old website had multilingual support but that felt like an unnecessary layer of complexity, so I stripped that as well.

Although I'm proud of the side projects I've made in the past, I wasn't always pround of the code quality. I wanted to show them off but I didn't want to throw them at potential future employers with the risk of it backfiring. So, I thought I could create a Blog and use it as a platform to present my projects, small ones and big ones, with more context. Even though I'd never written a blog post in my life (this is my first! 😱), I decided, screw it, let's try it out. If no one reads it, fine, and if someone does, even better. I could add one more bullet point to my list of needs:

  • A blog Section

At this point I was gravitating towards a technical setup where I could add content and dynamically generate sub pages. I wanted to get more used to using Typescript and felt right at home in a React environment. After doing a bit of research I found that Next.js could do all this for me.

Over the past few years Next.js has gotten a lot of praise online. With the introduction of server functions and server actions, where you can write server-side code right in your React components, I couldn't help but feel like I had been missing out. Next.js also has the capability of rendering components and pages server-side, client-side or statically depending on your needs. You can mix and match and this worked perfectly for me. I could use Markdown and static site generation for the blog pages and server-side render the rest of the website with the exception of a couple of components. This would give me blazing-fast performance ⚡ Next.js comes pre bundled with Typescript and Tailwind CSS. I decided to skip the Tailwind setup since I've always been hesitant towards bloating my HTML with CSS classes. I hated bootstrap and during my time working at a agency, I developed a great way of structuring my CSS for bigger projects using SCSS. So, for this project I decided to go with a setup of:

  • Next.js
  • Typescript
  • SCSS
  • Markdown

Base style and website skeleton

I started out with the layout, which included a header, a sidebar, a footer, and a main section in the middle where I would render my dynamic pages. Using a sidebar felt oddly '90s, but I thought that it gave the website a unique look. I decided to add a box shadow around the page container to make it pop a bit. I created few SCSS mixins to adjust the main font size based on the viewport width and converted all pixel values into rem units to create a consistent feel througout the website. I created variables for the spacings and colors from the design and freestyled my way into something that looked good on all devices. The layout started to take shape, unfortunetely I had too much fun and couldn't stick to one component at the time so I just started adding things all over the place. Yeah, this is not the way we do things at the office, but hey, it was my project, and I made the rules! 😄

The first pleasant surprise working with Next.js was that the automatic font loading using next/font. Especially since I have a habit of using google fonts. Using this functionality, no requests are sent to Google by the browser, everything is fetched at build time and self-hosted with the rest of the static assets. I love it!

Another feature I absolutely love is the built in Image component that you can import from next/image. You can pass a src attribute to it and it will automatically optimize your image in terms of file size and it will also create different versions of the image for different viewports. All to make image loading super fast and efficient.

Self-hosting fonts with Next.js Image Optimization 4 Techniques for responsive font sizing with css

Functionality

The first thing I wanted to do was to sort out the routes and markdown implementation. With the help of the excellent Next.js documentation and this youtube video I was able to get everything up and running. I had to tweak the code slightly to meet my personal needs but it was very straight forward. Cool, the base styles and the initial setup where finished, whats next?

Navigation

This started to look nice so I decided to gradually implement the interactive stuff. I started off with the header and navigation section, where some Javascript was needed. It was mostly class toggling, but sometimes these tasks can be a bit time-consuming. I added a hamburger menu to make things look cleaner. In the mobile version I wanted the menu to appear as a fullscreen overlay. This required that I put a lock on the page scroll while the mobile menu is open. Fair enough I thought, not that hard, and started coding. I soon realized that there where some edge cases. What if you open the menu on mobile and then resize the window to desktop size? The scroll lock would still be active and block you from scrolling down the page. Fine, this is a edge case, who the hell resizes their screen while browsing a website? But this is not a good mindset to have as a developer and I wanted to do things right. I've had tackled this issue in the past and I couldn't just ignore it. I had to fix this..

So, I created this custom React hook to trigger on window resize and added a debounce so that it wouldnt be affecting the performance too much. Now that I had solved that issue, I could continue to finish up the main navigation and header and move on to the timeline.

const debounce = (func: () => void, delay: number) => {
  let timeoutId: NodeJS.Timeout;

  return () => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(), delay);
  };
};

export const useDebouncedResize = (handleResize: () => void, delay = 250) => {
  useEffect(() => {
    const debouncedResize = debounce(handleResize, delay);

    window.addEventListener('resize', debouncedResize);

    return () => {
      window.removeEventListener('resize', debouncedResize);
    };
  }, [handleResize, delay]);
};

Timeline

The animated timeline was an idea I've had for a while. I built a website in the past and realized that this effect is quite easy to implement using only css, position: sticky. This took way longer than I expected, not gonna lie, but I managed to figure out a way to make it work on every viewport without doing too much "hacking". I created a wrapper with a :before and :after element. One element was representing the blue timeline and the other was just a empty div with a fill color that matches the background. The blue dots are coming from the list elements and are positioned in place using position: absolute. This is how it works:

Blog image

To avoid using a 3rd party library like AOS, I added a intersection observer to detect when the timeline is in view to add a nice slide in/fade in effect to the text boxes. So I quickly set that up:

useEffect(() => {
  const handleIntersection: IntersectionObserverCallback = (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add('timeline__list--isVisible');
      }
    });
  };

  const observer = new IntersectionObserver(handleIntersection, {
    threshold: 0.33
  });

  if (timelineListRef.current) {
    observer.observe(timelineListRef.current);
  }

  return () => observer.disconnect();
}, []);

Contact form

Now things really started to look good but I still had two things I wanted to add. First off is the contact form. Old school, I know, but I've always thought its neat to have a way of contacting someone through a form right there on the website. Luckly I had already implemented a form on my old website so this would be a copy/past kind of thing. To make things a bit more interesting I decided to utilize the Next.js server actions which allows you to submit form data to a server-side function without creating a separate API route. You can place your server-side code inside of your React components 🤯

Next.js 14 - server actions

Footer with page views counter

I got inspired by the website of an old colleage who had implemented a old school page views counter on the bottom of his website and I decided to steal the idea. I wanted to add a page views counter that would send the current route name to my backend and from there I wanted to access a remote database to find the current amout of page views, increment it by one and send back the response to the frontend displaying the views. Easy enough, right? Wrong.

Ok, I know, It's not rocket science but I wanted to implement this in the best way possible since I want to utilize all the bells and whistles of Next.js and maybe actually learn something new. My first thought was to just use a Next.js server function and do this database magic on the server side before the footer component was rendered. I soon realized that it wasn't going to work since I need some frontend data to figure out what page the user is actually on and update it on every route change. Plus: if I wanted to implement some kind of limiter where the view counts would only count unique visits, I would have to use something like sessionStorage which makes it impossible to do server side. Fine. I created a Next.js API route the old fashioned way (still love this feature!). I decided to go with vercels redis cloud database: KV. Since my storing needs are minimal, and the database is super fast, this worked perfectly for my use case. I could handle the database communication with only one line of code.

const pageViewsIncremented = await kv.hincrby('pageviews', route, 1);

This code will increment the pageviews on the current route by 1. If the route is new and doesn't exist in the database it will be created. Finally it will return the new value and save it in the const pageViewsIncremented which will be sent to the frontend.

Wrapping up and writing blog content

At this point my website actually looked pretty damn good. But there was one thing left to do, the blog page. Since I never created a design for this page a had to wing it. I tried different styles and eventually found an approach that worked pretty good. I decided to keep things this way and re-visit this section after the launch to tweak things a bit. I knew this could hold me back for a long time if I let it 😇

I then started the testing phase. I tried acceessing the website on different devices, doing light house tests and refactoring the code. I decided to re-visit this as well and tweak things even more after launch. I still hadn't written a single blog post and didn't want to put the site onlie without ANY content. I wanted this thing to be online quite quickly since I was actively looking for jobs and thought this might be a nice-to-have addition to my applications.

I came up with some fun subjects and created a couple of drafts and then got started writing this text.

Deployment

Deploying the project on vercel was a given. I started by synching my github account and cloned the project to vercel. From here everything went very smooth. I edited my domain settings and I was upp and running! Since I was hosting the project on vercel I took the opportunity to try their OG:image generation service, running Satori. I won't go into detail too much here but I'll say I'm a big fan! It's increadible to me how well Next.js and vercel works together. I decided to spice things up with a new domain and went on namecheap and bought petterastedt.dev. I was finally done!

Open Graph (OG) Image Generation

Conclusion

More than 3 years had past since I started working on my first portfolio website. I remember wanting to experiment and try a setup that I had never used before. This time around I found myself still having that curious mindset. Although the design is relatively simple, I'm proud of pushing myself to try to create it, the right way, from scratch. Choosing everything from color palette to fonts to spacing and layout.

Technology wise Next.js really blew me away, especially the new stuff added in version 13 and 14. Server functions, server actions, image optimization, font loading. I see less and less need for creating a dedicated backend in node.js. I already know that this will be my go-to setup for many future projects.

I also got to tap into the world of blog writing. This being my first blog post ever. I still have a lot to learn but the process of writing everything you did down has been a great way to reflect on what you did and why you did it.

So, whether you're here to learn more about how I work, pick up a few tips, or just read through my process, I hope you find something interesting. And if not, that's totally fine because, honestly, I'm just here to learn and to have a good time 😊