Personal Website

My personal website, created using Next JS and Nest JS

  • HTML
  • CSS
  • Javascript
  • React
  • Nest
  • Next

Introduction

I feel like it is an unspoken rite of passage for every web developer to work on a personal website of some sort. My last personal website died circa 2019 and it was time that I worked on a new one. My last personal website was using, ehem Django, and JQuery (very ancient tech by today's standards) and I wanted to learn Next, so I decided that it's the perfect time to start on a new website of my own.

Tech Stack

I was using Vue exclusively at work, and I have long wanted to learn react since hooks were introduced but never had the time to properly sit down and work on a React project, so instead of reading and watching tutorials, I forced myself to develop this website in React, using Next.

Backend-wise, I was already familiar with Nest from using it at work, plus I did not feel like going all hipster and use golang, so Nest it is. I am using Postgres as my database, with Prisma as my ORM of choice.

Deployment

With Vercel offering free frontend hosting (love you Vercel!), that was one part of deployment that I can cross out from my list. However deploying my backend server was a whole nother monster in itself. Initially, I tried using AWS, because, well AWS? And there was a free tier for first time users, but I learned quickly that there is no such thing as free hosting cause I had no idea what I was paying for but it was for sure not $0, and I already had ECS with load-balancers all deployed. I closed my account after a month and moved to Digital Ocean.

Digitial Ocean offers fixed pricing per month for their services, which I gladly took advantage of, and spun an app instance, created a bucket, and moved my Postgres database without taking a look back at AWS. I wrote up a build file that integrates with github actions, automatically building my docker image, pushing it to Digital Ocean's container repository and deploying it to my instance.

main.yml

---
name: Deploy to production
on:
  push:
    branches:
      - master
jobs:
  create-docker-image:
    name: Build and push the Docker image to DOCR
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repository
        uses: actions/checkout@v3
      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITAL_OCEAN_TOKEN }}
      - name: Log in to DOCR
        run: doctl registry login --expiry-seconds 1200
      - name: Build, tag, and push docker image to DOCR
        env:
          REGISTRY: portfolio-backend
          IMAGE_NAME: portfolio-backend
        run: >
          docker build --platform linux/amd64 -t $IMAGE_NAME .

          docker tag $IMAGE_NAME:latest registry.digitalocean.com/$REGISTRY/$IMAGE_NAME:latest

          docker push registry.digitalocean.com/$REGISTRY/$IMAGE_NAME:latest
  deploy:
    name: Deploy the new Docker image to Digital Ocean App
    runs-on: ubuntu-latest
    needs: create-docker-image
    steps:
      - name: DigitalOcean App Platform deployment
        uses: digitalocean/app_action@v1.1.5
        with:
          app_name: portfolio-backend
          token: ${{ secrets.DIGITAL_OCEAN_TOKEN }}
          images: '[ { "name": "portfolio-backend", "image":{ "registry_type": "DOCR",
            "registry": "portfolio-backend", "repository": "portfolio-backend",
            "tag": "latest" } } ]'

  garbage-collection:
    name: Clear untagged manifests
    runs-on: ubuntu-latest
    needs: deploy
    steps: 
      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with: 
          token: ${{ secrets.DIGITAL_OCEAN_TOKEN }}
      - name: Log in to DigitalOcean Container Registry
        run: doctl registry login --expiry-seconds 1200
      - name: Remove untagged manifests
        env: 
          REGISTRY: portfolio-backend
        run: doctl registry garbage-collection start --include-untagged-manifests --force $REGISTRY

Design

Design is not my strongest suit. I am not lying when I say that I have developed 4 different versions before landing on this one. I took inspiration from Maciej's website (which is absolutely stunning btw), and emulated the bento design system in my own style. Oh and not forgetting the Pantoneā„¢ 2022 Color of the Year, Very Peri, which is reason why all the buttons are purple.

Very Peri color tag
Very Peri color tag from Fashion Trendsetter

For the background, I decided to use analog TV static, inspired by Rogie's website, and just like their website, I decided to use a canvas instead of a looping video or gifs, as I wanted the element of randomness.

This is the code that I ended up using, which I found on stackoverflow and repurposed for my use:

Background.jsx

export const CRTBackground = () => {
    const containerRef = React.useRef<HTMLDivElement>(null);
    const containerWidth = React.useRef(1000);
    const containerHeight = React.useRef(1000);
    const staticInterval = React.useRef<null | number>(null);
    const sinValue = React.useRef(0);
    const canvasRef = React.useRef<HTMLCanvasElement>(null);
    const canvasCtx = React.useRef<CanvasRenderingContext2D | null>(null);

    const animateBackground = React.useCallback(() => {
        canvasCtx.current?.clearRect(0, 0, containerWidth.current, containerHeight.current);
        const imageData = canvasCtx.current?.createImageData(containerWidth.current, containerHeight.current);
        const pixel = imageData?.data;

        if (pixel) {
            for (let i = 0, n = pixel?.length || 1000; i < n; i += 4) {
                const sinCurve = 10 + Math.sin(i / 200000 + sinValue.current / 7);
                pixel[i] = pixel[i + 1] = pixel[i + 2] = 6 * Math.random() * sinCurve;
                pixel[i + 3] = 255;
            }
        }
        sinValue.current = (sinValue.current + 1) % containerHeight.current;
        imageData && canvasCtx.current?.putImageData(imageData, 0, 0);
    }, []);

    React.useEffect(() => {
        if (canvasRef.current) {
            canvasCtx.current = canvasRef.current.getContext("2d");
        }

        function resizeCanvas() {
            if (canvasRef.current) {
                containerWidth.current =
                    containerRef.current?.getBoundingClientRect().width || 1000;
                containerHeight.current =
                    containerRef.current?.getBoundingClientRect().height || 1000;
                canvasRef.current.width =
                    containerRef.current?.getBoundingClientRect().width || 1000;
                canvasRef.current.height =
                    containerRef.current?.getBoundingClientRect().height || 1000;
            }
        }

        resizeCanvas();

        window.addEventListener("resize", resizeCanvas);
        staticInterval.current = window.setInterval(animateBackground, 120);

        return () => {
            window.removeEventListener("resize", resizeCanvas);
            staticInterval.current && window.clearInterval(staticInterval.current);
        };
    }, [animateBackground]);

    return (
        <div ref={containerRef} className={styles.background}>
            <canvas ref={canvasRef} />
        </div>
    );
};

Conclusion

All in all I had fun, and am absolutely relieved that I can now say that this website is at least 75% done, cause let's face it, no one will look at their website in a month's time and say "Wow that is perfect".