The Story of My Blog Overhaul
The Story of My Blog Overhaul
I remember first building this blog back around May. I'd gotten into React starting in April, and this blog was a rather haphazard first project, so I figured the result was probably even rougher than I thought.
Sure enough (unfortunately?), the blog didn't disappoint that belief, and it kept showing all sorts of big and small issues along the way.
None of them were critical enough to actually break functionality, but there were a few things that bugged me enough to be distracting. Nobody's really interested, but since I already did the work, I wanted to use this post to lay out the improvements I made to the blog.
There were several problems, big and small, listed below.
Of all the issues, this was the most visible and annoying one. For some reason, rendering was strangely slow. On first visiting the page, you could actually see it stutter while rendering. Depending on the device, you might even briefly see the raw, unstyled HTML frame before any CSS was applied.
This bizarre rendering delay was one of React's classic black-box problems, and it was barely possible to debug.
Black... what box?
When source code runs, it frequently does so through some specific module. Well-designed modules are usually well encapsulated and modularized. But that same encapsulation makes it very difficult for a user of the module to debug issues arising from errors inside the module.
This hidden area of a module is referred to as a black box.
Occasionally, some pages would even render twice. To make matters worse, apparently React's double-rendering issue is a fairly well-known, chronic problem.
This blog runs on a JAMStack foundation. Typically, JAMStack blogs manage posts in Markdown. But unfortunately, web pages run on HTML. In other words, you need logic that properly converts Markdown into HTML.
JAMStack
JavaScript
API
Markup
Simply converting it isn't enough, though. You also need to apply various plugins, like Prism.js for code highlighting and LaTeX for writing formulas.
But contrary to what I'd hoped, Unified.js's official documentation wasn't especially friendly, and it was hard to find an API for customization. That meant relying on plugins like remark-prism and remark-toc, which only convert content into a fixed format, leaving little room for me to step in and customize things.
This blog's CSS heavily relied on Material-UI. When applying CSS, Material-UI guides you to style things inside JavaScript using methods like makeStyles.
Since I'd learned React on my own and wasn't familiar with its conventions, at the time I thought this was simply how it had to be done.
Next.js supports dynamic routing. You create a file named something like [page].js, and by assigning the appropriate value to the page variable inside the getStaticPaths method, it generates a URL carrying that value.
To host on GitHub, I had no choice but to adopt a static CSR approach, whether I liked it or not. Because of that, I ended up falling back on the query-string approach I was already comfortable with.
The URL for page 2 of the post list looked something like /posts?page=2. Given current trends, that's probably not the best approach.
Leaving aside the minor issues, here are the major ones I could think of off the top of my head.
Between when the blog was first built in May and when I started this overhaul in mid-July, I guess I'd grown a bit as a developer, because I was able to improve things in a much better direction than before.
The improvements are as follows.
JavaScript's biggest strength and biggest weakness at the same time is the ambiguity of variable types. Having learned my first language in a strict object-oriented language like C# or Java, this was one of the things that bugged me.
🖼️ TypsScript
TypeScript introduces the concept of variable types into JavaScript, minimizing errors caused by type ambiguity.
Next.js provides a TypeScript-based template. I'd never used TypeScript before, but I figured, "It's basically just JavaScript with type declarations added, right?" and gave it a shot.
I don't remember it taking very long to get used to TypeScript. Just as I expected, nothing much changes besides the type declarations. If anything, having types explicitly declared meant autocomplete and access to the correct internal functions available for a given variable, making development much more pleasant.
There was one annoying thing, though: the discrepancy between the live server and the actual compilation. Next.js lets you run a kind of live server that instantly reflects your source changes. Here's where an irritating discrepancy shows up: statements or syntax that cause TypeScript errors have no effect on the live server. In other words, code that looks perfectly fine and runs just fine on the live server will spit out all kinds of errors the moment you run a build.
The type isn't correcttttttt aaaaaaaargh!! Get this "ambiguous" garbage away from me!!!
Was it you who didn't declare a type here?? Hahahaha, if you don't want to see compile errors, you'd better behave yourself—
Hold on, this could be "null," couldn't it?? Oh, it's a DOM element that's guaranteed to exist after rendering, so it's definitely there, you say? I don't care about your DOM logic, it could still be null, hahahaha
What's even more maddening is that the code is often perfectly fine in terms of actual behavior. In other words, it's completely valid code that would sail right through in plain JavaScript.
But my small, precious TypeScript can't be stopped by anyone, and the moment there's even the slightest hint of ambiguity, it immediately starts complaining.
Using internal library functions was especially painful. To declare the exact types, I'd have to dig into the library's source code to check what parameter types it accepted and what type it returned. Of course, that's exactly the point of TypeScript existing in the first place...
Despite these issues, TypeScript resolved the things that used to bother me about JavaScript. The problem is it hands you a whole different set of headaches instead...
Still, I think it gave me a fairly fun development experience overall.
When you were young, you've probably heard a phrase like this before.
Leave diagnosis to the doctor, medicine to the pharmacist
It's a catchphrase promoting the separation of prescribing and dispensing. It's also a phrase that never applied to my blog.
Up until now, my blog used CSS-in-JS. As mentioned above, my blog's CSS relied entirely on Material-UI. Because of this, there weren't many references to draw on, and unfortunately, since Material-UI's own references used the CSS-in-JS approach, I'd been following that same approach up until now.
CSS-in-JS? CSS-in-CSS??
CSS-in-JS: CSS handled from within JavaScript
CSS-in-CSS: CSS handled within CSS
Of course, there were upsides too. CSS-in-JS's biggest advantage is that, since CSS is managed from JavaScript, dynamically generating CSS is easy. And since my blog supports toggling between dark and light modes, that advantage felt especially powerful.
But as mentioned above, rendering and performance issues emerged as the biggest problems, and among the possible causes, the styling syntax itself was the prime suspect.
Every single component required generating styling code in JavaScript, and for something like a blog post with a lot of styled elements, the styling code would end up far longer than the actual JSX code—the tail wagging the dog.
On top of that, there's a critical issue that shows up with CSS-in-JS: briefly seeing the raw, unstyled HTML before CSS gets applied. This phenomenon is called FOUC (Flash of Unstyled Content).
Apparently Next.js reduces this issue by encouraging heavy use of emotion.js... but unless there's a specific reason not to, I think CSS should be handled by CSS, so I decided to adopt CSS-in-CSS instead.
To achieve the CSS-in-CSS approach mentioned above, I initially wanted to just use plain CSS. But there was one small problem: plain CSS alone wasn't enough to maintain compatibility with the existing source.
JAVASCRIPT
/** * 스타일 객체 반환 함수 * * @returns {JSON} 스타일 객체 */ function getStyles() { return makeStyles((theme) => ({ fab_bright: { position: "fixed", bottom: 50, right: 50, backgroundColor: grey[800], color: grey[200], "&:hover": { backgroundColor: grey[700] }, "& svg": { color: orange[600] }, [theme.breakpoints.up("md")]: { "& span": { marginLeft: theme.spacing(1) } }, [theme.breakpoints.down("sm")]: { bottom: 70, right: 20 } }, fab_dark: { position: "fixed", bottom: 50, right: 50, backgroundColor: grey[200], color: grey[900], "&:hover": { backgroundColor: grey[300] }, "& svg": { color: blue[600] }, [theme.breakpoints.up("md")]: { "& span": { marginLeft: theme.spacing(1) } }, [theme.breakpoints.down("sm")]: { bottom: 70, right: 20 } }, div: { height: 24 } }))(); }
The existing CSS-in-JS source looked like this. It uses Material-UI's makeStyles, and as you can see from the structure, it's quite different from regular CSS.
CSS
.alpha { color: red; } .alpha:hover { color: blue; } .alpha .beta { background: black; }
SCSS
.alpha { color: red; &:hover { color: blue; } .beta { background: black; } }
The same behavior is expressed in both CSS and SCSS above. As you can see, makeStyles and SCSS syntax are remarkably similar. In other words, to maximize compatibility with the old styling system, I definitely needed a style preprocessor like SCSS or SASS.
🖼️ SCSS
The one I ended up adopting was SCSS. SASS bothered me a bit since its syntax differs slightly from regular CSS. SCSS, beyond simple styling, offers stronger programming-like features such as @mixin, @for, and variable declarations, giving a more convenient styling development experience.
Unlike TypeScript, which had both pros and cons, I liked SCSS enough that I want to keep using it going forward.
Maybe it's just because I didn't look into it thoroughly enough, but as I recall, the unified.js, remark, and rehype family of plugins I'd been using left very little room for user customization. Because of that, I had to install a huge number of related plugins.
What's even more frustrating is that they didn't provide a clear way to customize the resulting output. In other words, you're supposed to just use what you're given as-is... which was unwelcome in a lot of ways.
For code blocks in particular, I wanted to add various features like a copy button and a language label, but I had no way to do that, which was frustrating. Even without some elaborate plugin, if it had just handed me the converted HTML markup, I could have wrapped it in my own wrapper—but there wasn't even that option, which left me stuck. Sure, I had access to the entire HTML output, but manually parsing and extracting everything from that felt like a rather crude approach too.
The alternative I eventually found was marked.js. There was one simple reason: it let users freely customize the output, either by default syntax or by custom syntax they defined themselves.
Even so, getting Prism.js and LaTeX working wasn't easy either. LaTeX in particular took about 3 days to get fully working. LaTeX syntax is wrapped in $, and since Markdown doesn't inherently treat $ as anything special, I had to write logic myself to detect that string token by syntax.
Prism.js was relatively manageable since Markdown already has built-in code block syntax, but building LaTeX support from splitting the syntax all the way to the end was genuinely tough.
Still, unlike unified.js, the customization API was well documented, so I was able to build things like this.
JAVA
public static void main(String[] args) { // 이런 것도 구현했다. System.out.println("여기에 텍스트 입력"); }
- Code block design
- Language label
- Copy button
For the Mac-style design of the code blocks, I referenced a lot of foreign blogs. Whenever I search things on Google, I inevitably end up on foreign sites whether I like it or not, and a lot of them had that kind of design. When I actually tried to build it myself, it didn't come out quite right, so I built something similar from memory.
The copy button was also fairly easy to implement with a bit of simple JS. Prism.js originally has various plugins for this, but they didn't work well when applied within React. In particular, I really wanted line numbers, and while I managed to build everything else myself somehow, that kind of design was a bit more difficult, and since it required dynamically counting and inserting line numbers, I decided not to build it separately for now. Maybe I'll give it a try later if I have the time...?
One more thing: for tables, to get center alignment with scrolling, I had to wrap them in a div, which I was also able to build fairly easily through customization.
| Category | Value |
|---|---|
| Hover your | mouse |
| and the color | changes |
| If it gets | too wide horizontally |
| scrolling also | appears |
In many ways, this was the toughest and most frustrating part of the whole blog overhaul.
While looking into all this, I also found something called MDX, which lets you mix Markdown and React together—kind of neat, but I feel like Markdown is probably better off staying pure Markdown... anyway, that's how it is.
The old blog did have categories in the form of a select dropdown. Tags, on the other hand, had no related pages at all. Back when I first built it, I didn't even fully understand Next.js's features—I didn't even know dynamic routing existed.
🖼️ Category
I built pages showing lists by category and by tag using dynamic routing. For categories, since the old select-dropdown approach didn't look great, I made a simple card-view layout instead, though it still doesn't feel particularly polished. For now, since I don't have a good design in mind, I'll leave it as is.
- /posts/category/{category}/1
- /posts/tags/{tag}/1
The URLs were defined as shown above.
The old URLs were structured like this.
- /posts/?page=1&category=all: page 1 of the post list across all categories
- /posts/brand-new/: the URL of the current post
The post URL itself was fine, but the post list URL was genuinely unpleasant to look at.
Next.js supports dynamic routing. In TypeScript, you create files like [page].tsx or [...page].tsx. The "page" in the name is just the name assigned to the routing variable, so it can be anything. The [...page].tsx form is used when there are multiple dynamic segments.
I'd already made a small change to the post URLs before this overhaul, based on experience from my old Jekyll blog. Jekyll manages post titles in the form yyyy-MM-dd-title.md, meaning the date information lives in the title, which made it convenient to sort posts chronologically. Also, looking at blogs these days, I noticed many structure their URLs like /yyyy/MM/title, and having more than just the bare title in the URL felt nicer, so I decided to structure mine this way too.
The reason I went as far as including the day in the format /yyyy/MM/dd/title is that, given how Next.js works, being able to reconstruct the exact Markdown filename purely from the URL makes routing far more convenient.
The final, updated URLs look like this.
- /posts/1: page 1 of the post list across all categories
- /posts/JAVA/2: page 2 of the post list for the JAVA category
- /posts/2021/07/26/brand-new: the current post
There's a trailing / at the end of the URLs because I enabled the trailing slash option in Next.js. Turning on that option always builds the page as index.html. For example, if there's /pages/posts.tsx, it gets converted as follows depending on the option.
- /posts.html: with the option off
- /posts/index.html: with the option on
When testing the dev server on Tomcat, I always had to append .html, so I'd turned this option on—but GitHub allows omitting the .html extension, so it turned out I didn't actually need to.
Because of the trailing index.html, visiting via /posts/ ended up tangling the URLs registered with Google's search engine... I've since updated everything, but Google's search index update is so slow that traffic will probably be sluggish for a while.
I also made various other big and small UI improvements. The biggest change was removing the Bottom Nav.
On mobile browsers, the address bar at the top and the menu at the bottom appear and disappear, which changes the page's height in the process. Because of this, the Bottom Nav's position kept jumping around, which really hurt the UX.
Instead of the Bottom Nav, I added a slide-out menu, but it doesn't look great yet, so I plan to redesign it properly at some point.
🖼️ Post item
I also made the existing post items look more like a card view and gave images more emphasis. Cluttered elements like tags were also cleaned up neatly into a slide-out menu format.
Over the past few days, I've been so absorbed in this blog overhaul that my ongoing Baekjoon algorithm problem-solving has been on hold. I was working on problem 1020, but I've only written up about half the solution and haven't finished it yet. At this rate I'm going to forget how I was even solving it...
It's not fully complete yet, but since things feel reasonably wrapped up for now, I should get back to the work I paused in the meantime.
