This website itself that holds information about me, as well as the projects that I've been working on, a blog and whatever else my ego wants to add in the future (Spoiler) maybe a Guitar Tab Playground

Portfolio Website

This website itself that holds information about me, as well as the projects that I've been working on, a blog and whatever else my ego wants to add in the future (Spoiler) maybe a Guitar Tab Playground

Sun Jul 25 2021

26 min read

NaN

What a journey, after about 300 + 50 commits, endless time spent, and countless beers I finally published this website! It started mostly as a joke, I still cannot explain exactly its purpose but it's here, I own my domain and so, my little corner on the internet, where I can post whatever I want without fear of censure or other crap that social media got us used to, my precious!

Why I'm Reading This?

You're reading this because most probably you have nothing else better to do! Jokes aside, most probably you got here by reference from somewhere else, I think that spamming on other social media has made its job after all. What you're going to find here are various posts about topics that I enjoy, no need to explain again what you can get from the homepage.

The Idea Behind

I've been reading a lot recently, about how a portfolio or a blog might benefit the developer and the way such a tool could help interact more with people. I'm convinced that boring and empty social media posts and profiles became somehow not interesting and still, everyone relies on such methods to get in touch because it's also the most convenient, what I would like is to interact with individuals, not with machines, corporate soulless people that don't have any kind of personality

LinkedIn is the bay of such people.

The most precious perk that comes with having a personal site is that I don't have to fear the censorship imposed by other blogs such as Medium or Dev.to, I don't have to keep an eye on my post that might be removed overnight because of who knows what rule I broke or word I used that is not "aligning with the website policy". Having the possibility to publish content without censor is the step that everyone should be allowed to do and the fact that I know I can curse, post any picture I want and express any opinion no matter its context is something that helps my mind sanity drive through the other social media fake posts.

This is why I created this website, I wanted it to express my personality, being it good, bad, controvert or just dumb I don't mind, I'm too young to wear the mask of fake seriousness as the majority of people already did in order not to upset the service provider, everyone driven by just one thing, money!.

Content creators when the publisher transaction is confirmed

Content creators when the publisher transaction is confirmed source

About the Website

Besides just building a personal website I also wanted to make a bet, and see how much further I can go on without needing to pay for any of the services that I was going to implement. I can say that the experiment has been a success! I've been using some new top-notch cloud services including hosting, storage, routing, and all that black magic without spending a single Nuka Cola cap!

At its core, this website is a Next.js App served through CloudFlare Pages and backed up by an API made in CloudFlare Workers. To avoid confusion I will explain each separate module below under its category.

Frontend

As stated above the frontend side has been made with Next.js, a framework derived from its parent React.js that is capable of static site building and has different tools to speed up the development process such as an internal yet awesome router, a link component to prefetch on site pages, and also a prebuild Image component that I end up not using because of the building strategy I decided to use (and also the only one I was able to).

This strategy is called SSG (Server Side Generation) and implies the build of every page that the project contains, on the server-side before deploying. This is mostly useful for SEO but also generates a static site that doesn't require a reactive environment to run (CloudFlare Pages requires this) and also gets rid of the default /api/ path that is default set up in Next.js for SSR (Server Side Rendering)

You can find more information about how Next.js works everywhere on the internet, what I would like to focus on here are the components, style, and animations that I've been able to achieve with some libraries that have now become a must for any project I'm about to work on in the future.

The components state has been managed using just React Hooks and their useState() hook, as well as the requests to the API, have been invoked from component events or automatically on page load through the useEffect() hook.

Tailwind CSS

It's not an opinion, CSS is by far the most annoying part of web development. That's why Tailwind comes into help with its weird yet efficient class styling that, for me at least, has a couple of advantages:

  • Easy and fast to write, just assign to class or className the props you want the HTML element to have and you're good to go
  • Never leave the JSX file, no more swapping between CSS and JSX to remember the class name of which the style is attached to
  • Override of defaults and new styles made easy for any CSS prop through the tailwind.config.js file
  • Responsive by default with custom breakpoints for media query by adding : between the breakpoint and props
  • Dark mode made easy (flip the switch in the upper right corner of the navbar)

TailwindExample.jsx

const Tailwind = () => {
  return (
    <div className="flex flex-col desktop:flex-row items-center justify-center space-x-6 rounded-xl shadow-lg transition-colors ease-in-out bg-gray-100 dark:bg-gray-800 px-5 py-6">
      <div className="flex items-center justify-center w-40 p-3">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          className="w-full h-auto fill-current text-tailwindcss"
          viewBox="0 0 24 24"
        >
          <path
            fillRule="evenodd"
            d="M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z"
            clipRule="evenodd"
          />
        </svg>
      </div>
      <div className="flex flex-col items-center justify-start space-y-2 w-full">
        <p className="font-title text-left text-lg transition-colors ease-in-out text-gray-800 dark:text-gray-200 w-full">
          Tailwind CSS Example
        </p>
        <p className="font-medium text-base transition-colors ease-in-out text-gray-800 dark:text-gray-200">
          This is how the code looks like inside of JSX, note that the{" "}
          <strong>className</strong> is the place where the tailwind styles are
          declared, instead of <strong>class</strong>{" "}
        </p>
      </div>
    </div>
  );
};

Tailwind CSS Example

This is how the code looks like inside of JSX, note that the className is the place where the tailwind styles are declared, instead of class

I would say that the only disadvantage is that you might not immediately recognize the correct class in a nested tree if you need to make some changes later on but it's a price I'm ready to pay 10 times before going back to write raw CSS again!

Of course, this is mostly like a helper and not a shortcut, you have to learn first CSS and how the whole engine works before learning Tailwind.

Framer Motion

This one is awesome, everything you see moving on the screen, fading in and out and bouncing is the animation magic from the Framer Motion library! This tool is incredibly powerful, it adds a couple of pseudo HTML elements that can then be animated with different animation types such as Tween and Spring.

The tool has been around for just some time but it's incredible how flexible and efficiently came out, and it's still improving, I didn't even come to use all its features but it's again something that became a must for any future project, the biggest mistake you can make with such a tool is to abuse the animations (I also did this), sometimes static content with good design is more than enough to serve its purpose.

FramerMotionExample.jsx

import { motion } from "framer-motion";

const FramerMotion = () => {
  const constraintsRef = useRef(null);
  const [hovered, setHovered] = useState(false);

  const textAnime = {
    initial: { opacity: 0, y: -20 },
    animate: { opacity: 1, y: 0 },
    exit: { opacity: 0, y: 20 },
  };

  return (
    <div className="flex flex-row items-center justify-center w-full rounded-xl transition-colors ease-in-out bg-gray-100 dark:bg-gray-800 p-2 shadow-lg">
      <div
        ref={constraintsRef}
        className="flex flex-row items-center justify-center w-full rounded-lg transition-colors ease-in-out bg-gray-300 dark:bg-gray-400"
      >
        <motion.div
          className="grid place-items-center w-20 h-20 bg-gradient-to-r from-purple-500 to-purple-600 rounded-md"
          whileHover={{ scale: 1.1, cursor: "grab" }}
          whileTap={{ scale: 0.9, cursor: "grabbing" }}
          drag="x"
          dragConstraints={constraintsRef}
          dragMomentum={false}
          onHoverStart={() => setHovered(true)}
          onHoverEnd={() => setHovered(false)}
        >
          <AnimatePresence exitBeforeEnter>
            {
              {
                false: (
                  <motion.p
                    key="not-hovered"
                    className="font-title text-gray-200"
                    variants={textAnime}
                    initial="initial"
                    animate="animate"
                    exit="exit"
                  >
                    Hover
                  </motion.p>
                ),
                true: (
                  <motion.p
                    key="hovered"
                    className="font-title text-gray-200"
                    variants={textAnime}
                    initial="initial"
                    animate="animate"
                    exit="exit"
                  >
                    Drag
                  </motion.p>
                ),
              }[hovered]
            }
          </AnimatePresence>
        </motion.div>
      </div>
    </div>
  );
};

Hover

Notice the motion attached to regular HTML components that accept more parameters such as drag, whileHover, and whileTap. Framer Motion will compile the marked elements in the DOM with specific transition props related to the configuration given. In this example, the textAnime object is given to the variants parameter and it describes the animation that the element is going to do based on the current hovered state.

It also provides its own Hooks and Wrapper Components for more complicated animations yet it's still well documented and the community around it it's doing a great job on both adding new features as well as helping newbies with its integration into projects.

Next Optimized Img

DEPRECATED! Implemented the Next Image component with my custom Serverless Image-Service

As stated before the use of the <Image layout="responsive" /> component was excluded due to the hosting solution with CloudFlare Pages but this doesn't mean that the images you're seeing are raw. I used another module called Next Optimized Images that is sort of an alternative to the official image loader with the difference that this one is going to run at build time and optimizes images by compressing and converting them to WebP and creating bindings inside <picture> elements in the compiled DOM to load the most efficient data type based on browser support and the user device.

ImgComponent.jsx

import Image from "@components/Image"

<Image layout="responsive"
    className="w-full h-auto desktop:h-32 rounded-md object-cover select-none"
    src={`../path/to/images/${id}.png`)}
    alt={alt}
    width="100%"
    height="100%"
    draggable={false}

/>

This method of optimizing images is accepted with the SSG strategy and besides some glitches during the integration and the fact that looks no longer maintained, it's still a good library that does its job.

MDX

Since I decided not to write the content of my website in HTML (you don't want this) I opted for a Markdown solution with the MDX extension which allows embedding React Components within the MD content. The examples of the code for Tailwind and Framer Motion are an example of it. What you are reading now it's also MDX that has been compiled to HTML at build time with the help of the graymatter module.

Some metadata useful for the getStaticProps() function that is run at build time by Next.js are also declared within the Markdown file at the beginning. Gray Matter will separate such information and parse it into a separate object from the content itself.

Post.mdx

---
title: Portfolio Website
description: This website itself that holds information about me, as well as the projects that I've been working on, a blog and whatever else my ego wants to add in the future (Spoiler) maybe a Guitar Tab Playground
category: quests
publishedAt: "2021-07-25"
inProgress: false
stack: [typescript, nextjs, framer, tailwindcss, cloudflare, aws]
---

The post itself here...

The parsed post from MDX is then serialized through the next-mdx-remote module and then returned with some styling rules applied with remark and rehype plugins to the base component that renders them to the screen.

To establish a set o styling rules for the MDX Parsed output and integrate the content with the website design, I've been using the Tailwind Typography module that provides the prose CSS class to be used for an <article> component that will apply styles based on the configuration that has been declared under the tailwind.config.js file of my project.

tailwind.config.js

const defaultTheme = require("tailwindcss/defaultTheme");

module.exports = {
  purge: [
    "./components/**/*.{js,ts,jsx,tsx}",
    "./pages/**/*.{js,ts,jsx,tsx,md,mdx}",
    "./animations/**/*.{js,ts,jsx,tsx}",
    "./lib/**/*.{js,ts,jsx,tsx}",
  ],
  darkMode: "class",
  theme: {
    extend: {
      typography: (theme) => ({
        DEFAULT: {
          css: {
            fontSize: "1rem",
            fontWeight: 500,
            color: theme("colors.gray.800"),
            lineHeight: "1.50rem",
            transitionDuration: "150ms",
            transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
            transitionProperty:
              "background-color, border-color, color, fill, stroke",
            strong: {
              fontWeight: "700",
              color: theme("colors.gray.800"),
              transitionDuration: "150ms",
              transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
              transitionProperty:
                "background-color, border-color, color, fill, stroke",
            },
            h3: {
              fontSize: "1.875rem",
              lineHeight: "2.25rem",
              marginBottom: "0.5rem",
              scrollMarginTop: "5em",
              color: theme("colors.gray.800"),
              transitionDuration: "150ms",
              transitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
              transitionProperty:
                "background-color, border-color, color, fill, stroke",
            },
            // And so on...
          },
        }
      })
    }
  }
}

Backend

Since the static nature of this project, the backend is limited and is mostly used as a bridge to the few data that is collected from user interaction, mainly with the like and share systems, but it also has some schedules for the Spotify integration and is everything based on a Rest API made with TypeScript and a router module called itty-router.

As stated before, since the Static nature of the website I wasn't able to use the API provided by default by Next.js but I still needed a backend service to process some dynamic data that is going to display at the request time.

Serverless API

The name might be confusing but in this case, I'm not referring to the Serverless Tool but instead of the definition itself. CloudFlare Workers allow deploying any script that is not more than 1Mb in size once bundled. Seems like a really short space to work with and it is, but it's also true that is more than enough even for a modest API that is not relying too much on node modules but instead on pure code.

The idea is to create a single CF Worker and attach it to a /api/ or whatever name route on the main domain to be able to make requests from the frontend and get a response with the data needed. Reintroducing the missing API from Next.js without relying on a running server for the frontend hosting.

The Workers Platform provides 2 types of events handlers:

  • Request Based: Are those events that trigger when a specific request against the worker endpoint is fired.
  • Scheduled Based: Are those events that trigger periodically based on a CRON schedule.

index.ts

import { handleRequest, handleScheduled } from "./handler";

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

addEventListener("scheduled", (event) => {
  event.waitUntil(handleScheduled());
});

Request Events

As part of the Request Events, each API Route is going to be managed by the itty-router that will execute the corresponding code once a request is triggered against it. If the API Route does not exist the router will redirect the users to a default 404 Page from the frontend app. I like the way this approach is handled by the router because it allows to split the main router into sub routers and assign them to the base route, as it happens below for the baseRouter and the questsRouter, it helps you keep the working environment clean and organized.

handler.ts

import { Router } from "itty-router";
import {
  GetNestedRoute,
  DeleteNestedRoute,
  PostNestedRoute,
} from "./handlers/NestedRoutes";
import { Contact } from "./handlers/Contact";

import corsHeaders from "./lib/corsHeaders";
import { checkOrigin } from "./lib/corsHeaders";

const baseRouter = Router({ base: "/route/to/api" });
const subRouter = Router({ base: "/route/to/api/subrouter" });

baseRouter
  .all("/nested/*", subRouter.handle)
  .post("/contact", Contact)
  .all("*", () => Response.redirect("Redirect Endpoint Here", 301));

subRouter
  .get("/like/:id", GetNestedRoute)
  .post("/like", PostNestedRoute)
  .delete("/like/:id", DeleteNestedRoute);

export const handleRequest = async (request: Request): Promise<Response> => {
  if (request.method === "OPTIONS") {
    const allowedOrigin = checkOrigin(request);
    return new Response("OK", { headers: corsHeaders(allowedOrigin) });
  }
  return baseRouter.handle(request);
};

The imported handler functions contain the code that is going to be executed once a correct method is invoked through a request over the corresponding endpoint. An example is the Share function that is incrementing the counter of the Share buttons each time they're pressed.

PostShare.ts

export const PostShare = async (request: any) => {
  // Check the Origin matches one of the Env
  const allowedOrigin = checkOrigin(request);
  const headers = {
    ...corsHeaders(allowedOrigin),
    "Content-type": "application/json",
  };

  const data: PostResponse = {
    status: "Success",
    message: "New share for post",
    shares: { Twitter: 0, LinkedIn: 0, Facebook: 0 },
  };

  const { slug, social } = await readRequestBody(request);
  if (!["Twitter", "LinkedIn", "Facebook"].includes(social)) {
    (data.status = "Failed"), (data.message = "Unrecognized Social Network");
    return new Response(JSON.stringify(data, null, 2), { headers });
  }
  const raw = (await SHARE_NAMESPACE.get(slug)) || null;

  if (raw) {
    try {
      let parsedShared = await JSON.parse(raw);
      parsedShared[social] += 1;
      await SHARE_NAMESPACE.put(slug, await JSON.stringify(parsedShared));
      data.shares = parsedShared;
    } catch (error) {
      (data.status = "Failed"), (data.message = error);
    }
  } else {
    try {
      data.shares[social] += 1;
      const parsedShared = await JSON.stringify(data.shares);
      await SHARE_NAMESPACE.put(slug, parsedShared);
    } catch (error) {
      (data.status = "Failed"), (data.message = error);
    }
  }

  return new Response(JSON.stringify(data, null, 2), { headers });
};

The Worker is also synced with CORS to Next.js by checking each request, before executing the function code, for the Origin to be the domain name of the website, a useful tutorial about this can be seen here for more details.

Scheduled Events

While Request Events are based on explicit user action, to manage the Spotify OAuth and new Token Refresh to use its API, I've been using also Scheduled Events that is going to fire up based on a specified interval with CRON syntax. The Auth process for Spotify has many options based on the app type but at the core, it's just an OAuth routine that is token and role-based. This is necessary since the Spotify API cannot be queried otherwise.

handler.ts

export const handleScheduled = async (): Promise<Response> => {
  await SporifyAuth();
  return new Response("Cron Works", {
    headers: { "Content-type": "application/json" },
  });
};

The Scheduled Event that I ended up using has the main objective to try to request my latest played song on Spotify to be then displayed at the end of the homepage. If the request returns any code besides 200 it means that the Spotify Auth Token has been expired, the TTL of the token is 1hr. If the Token has expired then the the function will take care of refreshing it automatically and store it for further use in KV.

Spotify.ts

export const SporifyAuth = async () => {
  try {
    const parsedToken = await JSON.parse(await SPOTIFY_NAMESPACE.get("current_auth_token"));
    const accessToken = `${parsedToken.token_type} ${parsedToken.access_token}`;

    // Try to get the current playing track with existing Token
    const currentTrack = await fetch(
      "https://api.spotify.com/v1/me/player/currently-playing?market=from_token",
      {
        method: "GET",
        headers: {
          "Content-type": "application/json",
          Accept: "application/json",
          Authorization: accessToken,
        },
      },
    );

    const song = await JSON.parse(await currentTrack.text());

    // If the Token Expired request a new one and repeat above
    if (song.hasOwnProperty("error")) {
      const auth = `Basic ${btoa(`${SPOTIFY_ID}:${SPOTIFY_SECRET}`)}`; // encode in Base64 the credentials and format
      const newAuth = await fetch(
        `https://accounts.spotify.com/api/token?grant_type=refresh_token&refresh_token=${SPOTIFY_REFRESH_TOKEN}`,
        {
          method: "POST",
          headers: {
            "Content-type": "application/x-www-form-urlencoded",
            Authorization: auth,
          },
        },
      );

      const newToken = await JSON.parse(await newAuth.text());
      await SPOTIFY_NAMESPACE.put("current_auth_token", JSON.stringify(newToken, null, 2));

      const newAccessToken = `${newToken.token_type} ${newToken.access_token}`;
      const newTrack = await fetch(
        "https://api.spotify.com/v1/me/player/currently-playing?market=from_token",
        {
          method: "GET",
          headers: {
            "Content-type": "application/json",
            Accept: "application/json",
            Authorization: newAccessToken,
          },
        },
      );

      const newParsed = await JSON.parse(await newTrack.text());
      await SPOTIFY_NAMESPACE.put("current_playing_song", JSON.stringify(newParsed, null, 2));
    } else {
      await SPOTIFY_NAMESPACE.put("current_playing_song", JSON.stringify(song, null, 2));
    }
  } catch (ex) {
  }
};

The data about the last played song is then also stored in KV under a separate key, and a GET Request Event has been set up for the frontend to fetch it without the need of firing a POST request on the Spotify API at each Homepage refresh but instead, take advantage of the Workers low latency and optimization for reading operations to deliver the data the quicker possible and not impacting the Homepage loading and rendering.

The CRON schedules, as well as all the other properties of the Worker, are all declared in a wrangler.toml file at the root of the project repo, and of course, the tool used for building and deploying is called Wrangler that is a CLI made by CloudFlare to manage such scripts, it also provides dev preview and debugging.

wrangler.toml

# Dev Environment
type = "webpack"
zone_id = "your-cloudflare-zone-id"
account_id = "your-cloudflare-account-id"
route = "your-domain-route/api/*"
workers_dev = true
webpack_config = "webpack.config.js"
name = "your-project-name"
vars = { ENV_VAR = "secrets-here" }
kv_namespaces = [
    { binding = "NAMESPACE_NAME", id = "NAMESPACE_ID", preview_id = "PREVIEW_NAMESPACE_ID" },
    # ...
]

# Prod Environment
[env.production]
# Same as above...
workers_dev = false

# Dev Environment
[triggers]
crons = ["* * * * *"] # This CRON is going to run each minute, forever.

# Prod Environment
[env.production.triggers]
# Same as above...

The limit of the CloudFlare Workers Free Tier include 100k executions every 24hrs, the above Scheduled Event is going to consume me just 1.5k executions every day and it will update the song I play on Spotify every minute, I would say that is a reasonable price!

Storage

When I talk about storage I mainly refer to the place where data is stored and retrieved. There are many places where the static files are hosted, if we're talking about the compiled code that is serving the frontend app then the code is inside CloudFlare Datacenters as well as the static files such as images and markdown posts. The dynamic data such as the number of likes, the shares, and the current Spotify playing song are stored within the CloudFlare Workers KV, a key-value data storage that has low latency and fast read ops.

Since it's nature of key-value this imposes a kind of a big limit to the data modeling we can implement in such "database", but I ended up with a more simple and not so conventional solution. The KV platform allows infinite keys with each value up to 25Mb which is a lot, you might store parsed Base64 RAW images in here with no problems until you keep the 1Gb total size of all the objects.

So I decided to keep the data under JSON objects within the values and parse them each time a Worker function is requesting that data, do operations with the data, and finally, stringify it back into the KV value to save it for future operations.

DataExample.ts

// Get the key from the request
const { slug } = await readRequestBody(request);
const raw = (await DATA_NAMESPACE.get(slug)) || null; // QUEST_LIKES is the name of a Workers KV Namespace where data is stored

const current = await JSON.parse(raw); // Parse the JSON Object from the KV Value
const currentUpdated; // Do operations on data

// Stringify the updated object and put it back to the Namespace under the corresponding key
await DATA_NAMESPACE.put(
    slug,
    JSON.stringify(currentUpdated),
);

I'm aware of the fact that there are limits to both these services, being it the number of files or their size, at one point I might need to migrate them elsewhere and that's why I've created an AWS S3 Bucket to serve this purpose in the future. Even though the Free tier of CloudFlare services is generous and an upgrade to a paid version would be insignificant from a budget perspective being them cheap, it's good to have this backup solution, I might also use it before any migration to keep there more heavy data that I don't want to push to my GitHub Repo as well as the CloudFlare Network such as Videos.

Cloud Services

I love the Serverless concept, being cheap, scalable, secure by default lets me focus more on development and avoid the boring System Administration. Since I wanted to try them, I decided to rely on CloudFlare services for almost anything, frontend, backend, storage, DNS... I've been working with some of their products in the past and was always satisfied with the quality and the support, so why not give it a try for this project where I had the time to analyze and test all the solutions without a project manager barking at me to move faster.

They have been mentioned above but as a summary, I've ended up using:

  • CloudFlare Pages | Frontend App with Next.js
  • CloudFlare Workers | Backend API with TypeScript
  • CloudFlare Workers KV | Storage as Database replacement
  • CloudFlare DNS | Domain Name System management

Along with these services, there are a bunch more that are offered by CloudFlare by default. They mostly imply the automatic protection against DDoS attacks, automatic SSL, and security tools, since CloudFlare is a middleware between the User and the Origin, it means that before hitting any App you'll first hit CloudFlare Servers, which act as a shield for your whole environment making it one of the most if not the most secure ways to approach to deploy a website.

The Network Tab in the CloudFlare Console, along all the other services tabs

The Network Tab in the CloudFlare Console, along all the other services tabs

The most useful tool that CloudFlare provides anyway is the Cache. Content that has been cached after the first visit is going to be way faster to load in the future, and CloudFlare has the best Edge Cache you can get out there thanks also to their many datacenters around the world! Each data center of these is going to have my website deployed to, highly available to requests from all over the world, this feature together with the cache system and the lowest latency of the Workers Platform in front of other Cloud Services Providers, is the fastest combination for page loading and performance.

Some amazing features have been built and are still in progress about CloudFlare in general, you can read more about on their blog, I recommend this post where they compiled the original Doom Multiplayer to work entirely serverless into Workers on the Edge! It's pure madness!

Portfolio Homepage

Let me introduce you to the so-called Portfolio. This is the page where you can have a briefing about myself both professionally and personally. It's supposed to be viewed by everyone and hopefully will also stamp a smile on your face, you sad dude. Most likely recruiters are going to analyze it and that's why I called it Portfolio, again because I wanted it to reflect my taste and personality It has a unique style (mostly broken) that is going to be assigned to me. I never liked the Template CV, I think they are the most boring thing for a recruiter to read, I hope this way their experience will at least be more diverse during their working tasks.

Quests aka. Projects

Do you know how dev portfolios have the Projects page where everyone showcases their projects? I thought that this was too mainstream so just for the sake of originality, I decided to name this page Quests. It reminds me of the feeling I got each time I need to set up or create a boilerplate for a new project, I'm something like this:

How I feel each time I need to start working on a new project from scratch

How I feel each time I need to start working on a new project from scratch source

Here I'll collect posts for each project I've been working on in my free time or even professionally, detailing the problem and the solution I came up with as well as the technology stack and some further reference if there is any. Usually, these posts are dedicated to other engineers who might be curious about how to solve a specific problem or just for a relaxing reading during the coffee break, but everyone is welcome to read them and of course, try the products themselves!

Tavern aka. Blog

Same as before, why just having a Blog page with posts when you can instead have a Tavern page, in the true spirit of DnD that this website comes from this will of course be the page where blog posts are going to be shared with you, but the name is different, just like Apple products, same crap but named differently, the difference here is that I offer it for free, now and forever, thank me later.

Contact

Instead of opting for a separate page for the Contact Me form, I decided to overlay it on top of the router as it was a separate window. You cannot move it across the screen (even though that's not a bad idea after all) but you are allowed to minimize it or close it whenever you want. It can be activated by pressing the CONTACT button in the top right navbar. You can use this form to send me a message that is also going to be stored in Workers KV. I will then reply to you with the email you can see attached to the form window.

This solution is a temporary one until I finish the experiment of "broke student, no money" I'm running, and I didn't want to set up a mail service just yet but it will come soon for sure.

Source Code

There are a couple of reasons why I'm not releasing the source code for this website:

  • I worked hard to create my unique style and shape it into this website, by accessing the source code some people might adapt it to their website without learning anything about how it's working behind the scenes, as well as making the template generic across web devs portfolios.
  • There is nothing I've done here that cannot be implemented by others from scratch, I provided some info about how to do it in this post and there will come others in the future, the copy/paste culture is not always the most efficient.

What's Next?

I'm going to take care of this website as it was my son. Been working so much to make it that I'm not going to let it go without a good reason (I die). Of course, I'll keep it up to date with Facts, Projects, and mostly Blog posts. I also have ideas on new sections such as a /stage/ route where I want to integrate a Guitar Tab System similar to what you can find on Songsterr but with original tabs and solos that I never saw covered on the internet, stay tuned!

Thanks for reading, and don't forget to drink your beer, is good for you!

Also don't forget to leave a like by tapping the heart in the upper left corner of the Table of Content if you appreciated any post, you'll also add the flag of the country, you're located in (unless you're using a VPN) to the list and I'm really curious to see which flags will come up in the future, as well as the number counters!

© Șerban Mihai-Ciprian 2022 • All Rights Reserved

Terms and Conditions Privacy Policy