Here we are, following the centuries-old tradition of having a blog's first article be about the tools that make it tick.
But why
The intention behind building this blog & little corner of the internet (my first one, actually 🪴), that in part informed the tools chosen, was for it to be a place to:
- Share the things I learn, hoping that it may be useful to someone else tackling a similar problem/idea; and to future me, that has the (not recommended, would not buy again) super-power of forgetting any bit of info acquired more than two weeks ago.
- Play with new and fancy tools/libraries. Or maybe not so new and fancy, but that I simply hadn't had the chance to use yet (looking at you, grandpa styled-components 👀).
- Scratch (wait, what?) my let's take a problem and make it as approachable as possible itch (oh ... ok), developed/discovered during my years as a teaching assistant at uni.
- Design & Make things (little bits of UI, interactions, custom React hooks, etc).
That's nice, but what about the stack? Glad you asked, let's get to it.
Choices choices
There is a bunch of things to decide when building, in this case, a blog. Which framework/library do I use, if any? How do I style it? Will it have any data(base) needs? If giving the user the ability to Like ❤️ an article for example, that like will have to be persisted somewhere, and there will have to be an API in place to access it. How will I write the articles? Will they be just text and images? If so, Markdown sounds like a great choice. Will they have an interactive aspect to them? Like live interactive demos? Then Fancy Markdown (aka MDX) may be the way to go. How often will I write an article? If not too often, making the writing process as painless as possible may not be a priority. The answers to those questions will add to or reduce the stack.
This blog in particular, is a Next.js application (i.e. uses React), is written in TypeScript, styled with styled-components, and deployed via Vercel. And it has a tiny little backend made out of a PlanetScale database, accessed via Prisma and Next.js' API Routes. All the code, front & back, lives in the same repo, which you can find right here.
Next.js
From their home page:
Next.js gives you the best developer experience with all the features you need [...]
And I can't say I disagree. These are a few of those features that I particularly enjoy:
- Intuitive file-based routing (which looks like is getting an upgrade soon). Each page/view (the Home page, the About page, etc) is just another React component, with its route being defined by the name of the file that exports it (
<About /> → pages/about.tsx → [my-site]/about
). - Excellent TypeScript support. From starter projects via the
create-next-app
CLI, to properly defined/documented types. And I suspect it may only get better now that TS Gandalf Matt Pocock is part of the team. - IT'S FAST. From the snappy local build times, to its deployment times. With performance improvements always around the corner.
- Easy mode APIs via API Routes. Set up your API endpoints just like pages, i.e. each file inside the
pages/api
becomes an API endpoint (e.g.pages/api/views.ts → [my-site]/api/views
) *chef's kiss*. - All of the rendering options. SSR, SSG, CSR, SVG, GoT, etc (wait, those are not ... nevermind).
- And an excellent developer experience, thanks to all of the above, and more.
The Content (aka the articles)
The articles are little more than a collection of markdown files inside an articles
folder, with a few extras.
Going back to the itch-scratching point of making this blog, if the intention was to create kinda good tutorials, the educational content to keep as reference is, in my opinion, the one that makes us, the reader, part of the learning process. Be it through interactive demos, mini-games, inline code editors, and the like; and not just pure text (like this article so far ... 😅). That's where MDX comes in.
MDX to the rescue
Good ol' markdown is great, but MDX allows us to go beyond the classic markdown-known elements (like lists, headings, code blocks, etc), and use any React component we may come up with. If writing a tutorial for a component, let's say a toggle, it allows us to not just have a picture or a gif of the toggle saying "And finally, this is how our finished component should look like", but to actually have it right there, to play with and inspect (as in Right click → Inspect Element). Like so:
Or if wanting to let the reader think something through before seeing the solution, instead of a WAIT DON'T SCROLL YET, we can have something like:
This custom anti-spoilers component ✨
Thanks, Fancy Markdown 🫶
Plugins
Whenever markdown is involved, there is usually a transformation step that takes it from what we write:
# Some text
With some **very** important info.
To HTML:
<h1>Some text</h1>
<p>
With some <strong>very</strong> important info.
</p>
MDX lets us jump-in and tweak that transformation step, and extend its capabilities via plugins. There are a ton of them, which come from the vast rehype/remark ecosystems, and will cover most needs. For any missing specific use-cases, you can even write your own.
Those plugins power things such as:
- Code blocks syntax-highlighting, which usually entails the assignment of classes to each bit of code (keywords, variables, comments, etc) to then be able to target them via our stylesheets.
- Tables of content (like the one on this article), which count on each section title/heading having a unique
id
, so they can be navigated to viamy-site/super-cool-article#heading-id
. Any time you come across a table of contents, it's usually just a collection of links to each of those elements' ids, auto-generated via plugins like this one. - Frontmatter, which is a metadata format that lets us add extra info to a markdown file, usually used (as in this blog) for things like the publication date, title, authors, etc.
- Github Flavored Markdown, which extends the base markdown syntax with features like footnotes, strikethrough, tables, and task lists.
Backend & API
The data(base) needs of the site are quite basic. It keeps track of the number of views on each article, and lets the reader say "Thanks for the mildly entertaining and informative content" via a like button. Were those bits of info reason enough to go and try out a bunch of tools? Of course they were, and they are all a delight to use.
Prisma
Prisma makes interacting with databases a breeze. It lets us do a few things:
- Define our database models and their relationships via Schema files, which use a straightforward syntax, and even come with their own VS Code extension (with syntax highlighting, auto-completion, formatting, jump-to-definition and linting).
- Interact with the database using their type-safe (!) Prisma Client. No writing database specific queries, with just a
prisma.article.findMany()
, you get all theArticle
s' data, beautifully typed. - Explore and edit the data in the database via the Prisma Studio GUI, by just running
npx prisma studio
.
Database
There were two base requirements for the database:
- Integrate nicely with Prisma.
- Have a decent free tier.
PlanetScale not only checks those, but is also extremely easy to set-up, has excellent docs, and both a great CLI and web interface to manage your databases. As close to an It just works database as you can get. Wait, an integration guide from the Prisma team itself? Of course there is.
API Routes
API routes let us build our API endpoints with Next.js, right there beside the pages that consume it. From the docs:
Any file inside the folder
pages/api
is mapped to/api/*
and will be treated as an API endpoint instead of apage
.
It's the place (among others, like Next's getStaticProps
function) where we can access our database (via the Prisma Client). An (albeit basic) example of an endpoint that returns all articles' data could look something like:
const prisma = new PrismaClient()
async function handler(req, res) {
const articles = await prisma.article.findMany()
res.status(200).json(articles)
}
export default handler
In this site, there are just a couple of endpoints: One for the articles's view count, and another for their likes (which make use of dynamic API routes to specify the article to work with).
SWR
The aforementioned endpoints' data is consumed on the frontend via custom React hooks. The view count has its corresponding useViewCount
hook, and the same goes for the likes. Each of them using Vercel's SWR, an excellent React hooks library for data fetching.
function useViewCount(slug) {
const { data, error } = useSWR(`/api/views/${slug}`)
return {
value: data.viewCount,
isLoading: !data && !error,
}
}
Then, from any component:
function Views(slug) {
const viewCount = useViewCount(slug)
if (viewCount.isLoading) return <Spinner />
return <p>{viewCount.value}</p>
}
Design & Styling
I'm no designer 👨🏻🎨, but designing is something I thoroughly enjoy; from the early sketches on a notepad, to the closer-to-final mockups/prototypes on Figma (at least for UIs). When the time came to choose the tool to implement the site's designs, I happened to be going through Josh Comeau's fantastic CSS for JS developers course, where he'd picked styled-components as his CSS-in-JS library of choice.
Styled-components
Even though nowadays it's not the shiniest/coolest way of handling styles (i.e. Tailwind), I fall on the (seems like) continuously shrinking group of people that actually ... dramatic pause ... enjoy writing CSS (audible gasps), and styled-components let's you write your styles very much as if using plain old CSS (thanks to its use of tagged template literals), with all of the benefits common among CSS-in-JS solutions sprinkled on top 🧁 (like scoping, automatic vendor prefixing, Sass-like syntax support, cmd/ctrl + Click to jump to a component's styles definition, etc). It's also an industry-standard that I'd somehow managed to not use yet 🤷🏻♂️
Theming
The site has (the nowadays kind of expected) support for light and dark appearances, and even though theming and all its little details (persisting it to local storage, matching the system, avoiding flashes on load, cross-tab syncing, etc) is worth its own article (and been neatly solved by next-themes 📦), I wanted to show the basis of how it works here.
CSS-in-JS solutions usually come equipped with theming support via <ThemeProvider>
s, which rely on React's contexts to make the theme variables (colors, borders, font-sizes, etc) available to the entire app. Those are all great, but what if we could stick to things that the browser already gives us? Turns out that app-wide dynamic variables is one of those things. Enter: CSS Custom Properties (aka CSS Variables).
Even though CSS variables are not app-wide per se, they can be, if applied to a root-enough element. Then, any component can access them from anywhere, without the need for re-renders on theme changes, props, contexts, etc. It's all part of the vanilla CSS that comes with the browser. Here's how styling a (super ultra basic) <Button>
component could look like:
const Button = styled.button`
color: var(--color-foreground);
background: var(--color-background);
`
/* Light/default theme color palette */
:root {
--color-foreground: white;
--color-background: black:
}
/* Dark theme color palette */
:root.dark {
--color-foreground: black;
--color-background: white:
}
Notice the .dark
class on the second ruleset, which simply sets the colors to their dark variants. Changing the theme then, becomes a matter of toggling a class on the root element. If and when to apply that class is where the extra bits mentioned before come into play, like remembering the user choice via local storage.
Deployment
The site's deployed via Vercel, which's built by the same people that created Next.js, and shares the same excellent developer experience. Point Vercel to your site's repo, and in a few secs, you've got it globally available through a URL and kept up to date with any changes pushed to the main
branch. Want to try some new feature and get feedback before merging it? Just create a PR, and Vercel will create a URL with a preview of the changes. Share it, get feedback, merge it. Big fan.
Closing thoughts
🚧 WIP
Thanks for reading!
Feel free to reach out on Twitter or via email, I would love to hear your thoughts. All feedback is more than welcome.