blog.itcode.devblog.itcode.dev

Blog 3rd Renewal

After rebuilding my blog with Next.js, I didn't do much maintenance beyond writing new posts. I had my reasons — there was no major problem using it as-is, and honestly it was a hassle. Coming up with and placing the right components was quite a bother too. There were a few problems with my blog, one of which was that the About page had nothing on it. I'd tried to write some kind of blog introduction, but I didn't have a good idea for it. Then, out of nowhere, a decent idea popped into my head: "Wouldn't it be nice to show a commit list on the About page?" It seemed like a decent idea, so I started developing it, but suddenly the ugly parts of the blog started bothering me. Unable to bear seeing those ugly parts all of a sudden, I ended up starting an unplanned blog renewal.

Blog 3rd Renewal

After rebuilding my blog with Next.js, I didn't do much maintenance beyond writing new posts. I had my reasons — there was no major problem using it as-is, and honestly it was a hassle. Coming up with and placing the right components was quite a bother too. There were a few problems with my blog, one of which was that the About page had nothing on it. I'd tried to write some kind of blog introduction, but I didn't have a good idea for it. Then, out of nowhere, a decent idea popped into my head: "Wouldn't it be nice to show a commit list on the About page?" It seemed like a decent idea, so I started developing it, but suddenly the ugly parts of the blog started bothering me. Unable to bear seeing those ugly parts all of a sudden, I ended up starting an unplanned blog renewal.
RWB0104
@RWBwritten at 2022-06-05 14:01:19

After rebuilding my blog with Next.js, I didn't do much maintenance beyond writing new posts. I had my reasons — there was no major problem using it as-is, and honestly it was a hassle. Coming up with and placing the right components was quite a bother too.

There were a few problems with my blog, one of which was that the About page had nothing on it. I'd tried to write some kind of blog introduction, but I didn't have a good idea for it. Then, out of nowhere, a decent idea popped into my head: "Wouldn't it be nice to show a commit list on the About page?" It seemed like a decent idea, so I started developing it, but suddenly the ugly parts of the blog started bothering me.

Unable to bear seeing those ugly parts all of a sudden, I ended up starting an unplanned blog renewal.




The problems I identified with my blog are as follows.

  • Awkward dark theme colors
  • Page transition loading that still hadn't gone away
  • Slow build time
  • Lackluster About page content
  • Sloppy mobile navigation
  • Material-UI being basically useless

Beyond that, I reworked a lot of parts of the blog during the renewal process.




The renewed details are as follows.



The original dark theme used a somewhat blue-black, navy-ish color scheme. It was probably influenced by Upbit or the Material-UI dark theme.

I liked the unique color feel at the time, but at some point I grew to dislike looking at it. Unfortunately, having zero eye for pure design elements like color, after much deliberation I decided to reference the colors of an already well-run dark theme.

The target: a developer's soulmate, GitHub.

I redefined my colors by referencing GitHub's dark theme colors. I also just got rid of the header gradient entirely. It might have been fine if the colors matched well, but since the theme and colors clashed, it looked way too tacky. Again, I have absolutely no sense for color.

  • Expected effect
    • Cleaned up theme


The typical way to use SCSS modules in React is as follows.

SCSS

.root {
	background-color: gainsboro;
}

TSX

import styles from './App.module.scss';

function App(): JSX.Element
{
	return (
		<div className={styles.root}>Lorem ipsum</div>
	);
}

By default, you use it as an object like above. In React, *.module.scss is converted to unique class names during the build process and applied to both SCSS and JSX. This is why the same class name in different SCSS files doesn't affect other components.

Since my blog is split into Light and Dark modes, switching between the two modes required the following kind of code.

SCSS

.root-dark {
	background-color: black;
	color: white;
}

.root-light {
	background-color: gainsboro;
	color: black;
}

TSX

import styles from './App.module.scss';

function App(): JSX.Element
{
	const theme = themeState ? 'dark' : 'light';

	return (
		<div className={styles[`root-${themeState}`]}>Lorem ipsum</div>
	);
}

It works like the above. Compare the theme value, and call the matching code from styles. As you can see, this isn't a very clean approach.

However, using classnames can resolve this issue cleanly.

SCSS

.root-dark {
	background-color: black;
	color: white;
}

.root-light {
	background-color: gainsboro;
	color: black;
}

TSX

import classNames from 'classnames/bind';
import styles from './App.module.scss';

const cn = classNames.bind(styles);

function App(): JSX.Element
{
	return (
		<div className={cn('root', themeState)}>Lorem ipsum</div>
	);
}

You can combine class names by passing parameters into the cn method sequentially, as shown above. According to the official documentation, it seems to support various parameter types beyond strings, including numbers and objects. Of course, for my blog, that level is more than enough.

This lets me cleanly call various classes depending on branching conditions.

As a side note, I found out about classnames while poking around a project at work.

  • Expected effect
    • A clean development method for design


Desktop mode doesn't matter much since the screen is big, but in mobile mode the screen is inevitably smaller, so you need to pay much more attention to UI placement and sizing.

On my blog, navigation was placed in the header in desktop mode, so I needed a separate menu when the screen shrank down to mobile size.

So I threw together a rough mobile menu, but it was sloppy. Very sloppy. It wasn't great to look at, but it wasn't like it was broken either, and above all, implementing a sidebar was a hassle. On top of the UI itself, there was the added burden of needing to implement animation too...

In the end, I couldn't keep it as-is, so I built the UI as a typical sidebar and implemented the animation with CSS.

SCSS

@keyframes slide-in {
	from {
		transform: translate(0px, 0px);
	}

	to {
		transform: translate(-200px, 0px);
	}
}

@keyframes slide-out {
	from {
		transform: translate(-200px, 0px);
	}

	to {
		transform: translate(0px, 0px);
	}
}

.header {
	position: fixed;

	top: 0px;
	right: -200px;

	width: 200px;
	height: 100%;

	transition: 0.3s;

	&[data-show=true] {
		@include slide-in-animation();
	}

	&[data-show=false] {
		@include slide-out-animation();
	}
}

I implemented it so the data-show attribute distinguishes whether the menu is toggled on/off. It's set up with width: 200px; and right: 200px; so it stays hidden off-screen, and when data-show=true, it moves to right: 0px; over 0.3 seconds. When data-show=off, it does the reverse.

Roughly, this is the approach. What I made now isn't perfect either, but it's at least much nicer than before.

  • Expected effect
    • More polished mobile navigation


My blog's posts have two kinds of classifications: category and tag. Category specifies the topic of the post, while tags specify the key keywords the post has. The intent was to use these to show posts by category and posts by tag.

Good intentions. It let readers gather posts by various conditions. But reality is that good intentions don't always lead to good results. This structure — whose intent "was" good — ends up doing the tango together with SSG's characteristic of requiring a separate build process for every declared page...


Unlike SSR and CSR, SSG requires a build process for every declared page. Not just per-post-page lists, but per-category and per-tag lists all needed to be built separately. Pages that hardly anyone uses or cares about were causing build time to grow exponentially. The more posts there are, the worse this phenomenon gets.

What's more, the tag system causes way too many pages to be built. For example, if you add content with category A and tags 1, 2, and 3, the following builds occur.

  1. Build category A page
  2. Build tag 1 page
  3. Build tag 2 page
  4. Build tag 3 page

If it's a category or tag that didn't exist before, that's one more build artifact than existed previously. Since one post can have multiple tags, every time a post is added there's a chance of needlessly building a lot of extra pages.

Compared to the exponentially growing build time, the use of per-tag posts is basically nonexistent. Furthermore, since there's already a similar concept called category, and dedicated menus already provide access, the meaning of tags becomes even more diminished.

It's a shame, but I boldly deleted it and kept tags only for SEO keyword purposes.

  • Expected effect
    • Shortened build process


The existing post list used pagination. But while using pagination, several doubts crept in.

  • Is this really the appropriate approach?
  • Do users really click through my blog's pages one by one to browse posts?
  • Wouldn't removing pagination also shorten the build process?

After careful thought, I concluded that pagination's expected value is very low relative to the volume it requires. I decided to switch to infinite scroll, a comparatively trendy approach.

The problem, though, is that it completely negates the existing approach. To apply infinite scroll, you need meta information for all posts, which my blog's structure simply wasn't managing to begin with. And I didn't think having a separate data server was a good direction either.


To solve this, I reworked the existing logic. I boldly removed pagination, and during the pre-build step I now save meta information about posts as JSON.

When the post list renders, it analyzes the meta information and splits it into groups of 10 to display. The page is stored in state, and when the scroll reaches the bottom, the next group is added. For a smoother UX, the next group is added once scrolling reaches about 80%. This measure gives users a seamless experience of continuous data loading.

This let me both reduce the build logic and increase convenience. Of course, if the meta information gets too large, that would raise a different problem, but that's probably something to worry about only after quite a lot of time has passed. The post count would need to reach at least several thousand, and in one year I wrote just under 200 posts under posts alone. Even calculated linearly, it would take 10 years to reach 2,000.

By the time it reaches that scale, it would probably be more efficient to build a dedicated search engine server anyway, and I can hold out hope that my own skills will have improved in 10 years... so for now, at the current scale, I decided not to worry about it much.

  • Expected effect
    • Shortened build process
    • Trendy UX


A measure to solve a problem that arises together with #4. The biggest drawback of infinite scroll is that you can't jump directly to the post you want. Since the concept of "page" becomes vague, reaching a post located relatively far down requires endless scrolling.

Also, the more posts you scroll through, the more DOM gets rendered on a single page, increasing the burden on the browser. In other words, the page keeps getting slower.

To prevent this, additional measures are needed so users can directly reach the post they want. I judged that search functionality was the most appropriate measure, so I attached search to the post list, configured so users can search for the post they want and see the related list.

Since I output meta information as a separate JSON in step 4, I could implement search using it.


The approach is as follows.

  1. Keyword search
  2. Analyze meta information
    1. Title
    2. Summary
    3. Keywords
  3. Output a list of posts whose information contains the keyword

That's roughly it. Here, keywords are an array, and since searching an array increases computational complexity, each item is joined with spaces into a single string for use.

TYPESCRIPT

const keyword1 = [ 'keyword1', 'keyword2', 'keyword3' ];
const keyword2 = keyword1.join(' ');

const hasMatch1 = keyword1.filter(item => item.includes(keyword)).length > 0;
const hasMatch2 = keyword2.includes(keyword)

When finding a post whose string contains keyword, this is the difference between the keyword array keyword1 and its stringified form keyword2. keyword2 is clearly much simpler.

This way, search functionality makes it easy to guide users to posts.

  • Expected effect
    • Added convenience
    • Restored a reason for tags to exist


The UI framework I focused on using during the blog renewal was Material-UI. Its wide range of React-based design components satisfied not just aesthetic needs but functional ones as well. However, my experience with Material-UI wasn't entirely happy, for the following reasons.

  1. Excessive bulk, and the resulting increase in rendering work
  2. Increasing irrelevance as my own SCSS skills improved
  3. Unless you use it exactly as provided, a component structure that ultimately requires extra work

If you've used Material-UI, you'll know the usage is a bit complex and the scale looks large too. Maybe because of that, I felt like a lot of internal JS and CSS was being invoked just to use the components I needed. This ultimately translated into rendering cost. I couldn't understand why loading images would show up even for simple page transitions, and I kept suspecting Material-UI was the cause.

Also, thanks to continuously using SCSS since I first encountered it, my SCSS skills improved a lot. That means it works out that simply designing things myself is much better, both in terms of performance and maintainability.

What's more, unless you use Material-UI's components exactly as-is, you inevitably end up writing messy code. Some of Material-UI's CSS code gets assigned to the highest-priority style attribute. To override this with SCSS, you'd have to slap !important on every style property — a disaster. This was also quite inconvenient when writing responsive code.


With various internal and external issues piling up together, I concluded that Material-UI was no longer necessary. I boldly removed Material-UI, which was in use across nearly every component, and rewrote the components using plain HTML tags and SCSS.

There were also many other changes happening simultaneously, so I'm not entirely sure how much of an impact this had on rendering speed. But one thing is certain: the loading time that briefly appeared during page transitions disappeared entirely.

  • Expected effect
    • Shortened build time (probably?)
    • Elimination of rendering wait time


With the release of React 18, I upgraded the React version along with Next.js. The biggest reason for moving to React 18 was the increase in build speed.

According to React's official documentation, switching the compiler engine to Rust is expected to bring more than a 5x improvement in build speed. I wasn't sure exactly how much of an impact this figure would have on my blog, but I judged there was no reason not to switch, so I performed the upgrade.

BASH

yarn upgrade react@latest @types/react@latest react-dom@latest next@latest

Fortunately, there were no particular issues with behavior or the build.

  • Expected effect
    • Shortened build time (probably?)
    • Use of the latest version


The existing blog used button tags rather than a tags for most of its link components.

The reason was that SPA routing needed to be applied, and the a tag just performs a plain page navigation.

After later learning about Next.js's Link component, I decided there was no more reason to use button.

TSX

function App(): JSX.Element
{
	return (
		<Link href='/{page}'>
			<a>Page post</a>
		</Link>
	)
}

On top of that, since SEO engines can read and analyze a tags, I concluded that using a tags for links was the wiser choice from an SEO standpoint as well, and replaced all link tags with Link.

  • Expected effect
    • A more SEO-friendly site


This part is a little funny... Back when I did a previous renewal, I made this remark.

After that, I actually overhauled the URL policy entirely, switching from a query-based scheme to a path-based scheme.

For a while after that, it worked well...

And ta-da~ turns out there's no such thing as "never"!

With the adoption of infinite scroll, I had to revert from the current path-based scheme back to a query-based scheme.

  • Before
    • /posts/1
    • /posts/TypeScript/3
  • Now
    • /posts?page=1
    • /posts?page=3&category=TypeScript

With infinite scroll applied, there's no longer a reason to build per page. Instead, page and category are shown by comparing the query string values. When page is 3, a total of 30 posts get rendered in the list.

  • Expected effect
    • A more SEO-friendly site


There was also a policy change regarding categories. Previously, you could only select one category at a time. That was because each category's list had to be built separately, so allowing multiple categories would cause the number of pages to build to grow exponentially.

But once infinite scroll was applied, the list build process disappeared entirely, and since URLs also switched to a query-based scheme, I changed it so multiple categories can now be selected.

  • Expected effect
    • Added convenience


I made the post group UI a bit more intuitive and concise. There were signs that the previous version tried to show too much information, which made it hard to get a clear overview of the entire post group list at a glance.

I changed the UI to a list-based layout.



There were also various other small changes elsewhere.

  • Adjusted desktop mode header height
  • Added content to the About page
  • Added a new Comments page
  • Added an ArtBox refresh feature
  • Changed the font loading method to CDN
  • Various other miscellaneous things

Thanks to the renewal, I was able to improve the unstable elements that had accumulated over time.




  • Shortened build time
    • On my home computer, build speed that used to take around 70s dropped to the low-to-mid 20s range
    • Excluding the post list page seems to have been a big factor
  • Eliminated rendering delay
    • The loading image that briefly showed up during page transitions due to rendering is now fast enough to be essentially invisible
    • I think excluding Material-UI was a big factor (not entirely certain)
  • Applied infinite scroll
    • Removed meaningless pages
    • Adopted a trendy approach
  • Removed tags
    • Removed meaningless pages
  • Multi-category selection
    • Provides a wider range of selection options
  • Added search functionality
    • Search for desired keywords
  • Strengthened SEO
    • Built a site that is more search-engine friendly than before
  • Improved development convenience
    • Removed unused code
    • Improved outdated logic
  • Strengthened blog design
    • Improved some outdated design elements
    • Added missing components



While working on this renewal, I used trendy techniques like infinite scroll and made a lot of improvements to the overall design logic and internal behavior.

Even though not much time has passed since changing jobs, in that short time I was able to get exposed to various technologies and libraries, and I tried to incorporate a lot of that into this renewal.

Doing this, I think I went to bed around 4am for nearly 4 days straight, weekday or weekend. It's been a while since I felt this focused on something.

What next...

# React# Next.js# Dev Blog
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08