tylerthompson.work
This website was a whole project in itself. I designed it to be an effortless platform to share my projects, coursework, hobbies, and really anything about myself. I built it from scratch using Next.js and have it deployed on Vercel.
Motivation
I built this site after letting my previous portfolio sit on the shelf for a couple years. It existed as an outdated homage to my past because I didn't bother to update it. Why? Because it was too much of a hassle! I don't want to go digging into code every time I want to add a new project to my website. I dreamed of a portfolio that let me push a single new Markdown file whenever I wanted to add a project, and it would magically appear on the site.
"That's just a blog!!" I hear you scream. Well yes, but I have one big problem with blogs: their lack of structure. Every time I browse a blog I get lost in their interminable feeds of posts. I dreamed of having a tree of posts organized into categories akin to a filesystem. For example, a reader could quickly find all of my university projects by navigating to Tufts > Courses > Projects
. And these would be distinct from side projects, which they could find in the Side Projects
directory.
I wanted to build a blog that allowed for tree-like organization without sacrificing the ease of adding new pages.
With all of this in mind, I set out with the following requirements:
- Adding a new page only requires one new Markdown file to be pushed to this repo
- New pages should be automatically added to some kind of navigation page
- Markdown files can be organized into deeply nested directories, and the resulting structure is displayed on the navigation page
- Support all the standard Markdown features as defined by CommonMark
A consequence of #3 is that the site's code cannot assume anything about how the content directory is structured. It must be dynamic enough to display any kind of structure in an understandable way, and fully convey the structure to the reader. This gives me unbounded freedom if I, for example, attended graduate school and wanted to move the Tufts
directory and newly created Grad
directory into a parent directory Education
. Doing this should be as easy as moving them around in my filesystem and pushing to GitHub.
Design
All of the content pages are stored as markdown files in the content
directory, which is located at the root of the project folder. This directory only contains markdown files and each one is accessible from the website's navigation system. When a request for a resource is made, eg. content/some/path.md
, a matching file is searched for in the content
directory. If it's found, then the file content is rendered into HTML using react-markdown
and inserted into the content view page. And if the file doesn't exist, then a 404 page is returned. Easy peasy, right?
Each content page is stored as a single Markdown file in the
content
directory.
The first challenge I faced with this design was gaining access to the filesystem during request handling. React is normally served as browser-side JavaScript, so it's impossible to access server-side files while rendering because the code has already been sent to the client's browser. The solution is to use server-side rendering with Next.js which allows the rendering code to run on the server, and thus access the content
directory using a tool like fs
.
While I originally chose to use Next.js just to solve this one issue, it's actually perfect for the application as a whole. Each page is completely static, meaning they will never change after compile time, so I was able to use Next.js's excellent static generation facilities to make the entire site statically generated. This means that the final HTML for each page is generated when the site is built and served immediately on request. So although a lot of filesystem work is done to render each page, requests are still lighting fast because all the work is done ahead of time. Neat!
By default Next.js tries to automatically determine which pages can be statically generated so the developer doesn't even have to think about it, but in this case it needs a little bit of help. It can't determine which pages exist because their existence is determined by a collection of Markdown files that Next doesn't know about. Using a feature called generateStaticParams
, I'm able to feed it a list of URIs to statically generate pages for, which I collect by recursively reading files in the content
directory.
Next.js is used to access the filesystem when requests are made. And in the end, all the pages are statically generated using Next's powerful static generation facilities.
To facilitate effortless navigation of all these pages, I built a tree-like navigation component. It organizes each directory and its files into boxes that can be expanded to explore deeper into the filesystem. I wanted the user to feel like they could very easily find related content after reading a page, so I added a directory at the end of each page that starts at the page's level in the filesystem. For example, after reading this page, you might scroll to the bottom to find a collection of my other side projects. Generating a new directory tree every single time a page is requested is an unfortunate bit of overhead... so it's awfully convenient that they're all generated at build time!
Navigation is made simple using a tree-like directory with collapsible branches.
Here are some other "nice to haves" that I ended up adding:
- Directories themselves can be navigated to if they contain an
index.md
file - Support some of the extended Markdown syntax like tables, emojis, highlighting, and frontmatter
- Frontmatter is used to add some extra info about pages. In particular, the
description
tag adds a description next to page links in the directory - Heading ids are automatically generated using remark-heading-id which allows Markdown heading links to function as expected after being converted to HTML
- Light and dark modes that respond to the user's system settings
Conclusion
The spirit I followed when designing these features was to create an airtight abstraction between the Markdown content and what is displayed on this website. I wanted to be able to write Markdown files that contain, images, heading links, links to each other, code blocks, quotes, etc. and have it all just work. It makes adding pages incredibly ergonomic because there are very, very few leaks in the abstraction. I can write Markdown as I would in something like Obsidian and it magically appears on the site with full formatting and navigation. Hopefully now I will actually add to my portfolio with some reasonable frequency 🤷♂️
signpost care to venture further?
Side Projects
tylerthompson.work - the website you're currently looking at