Ok so you know how I said in Blog Init that I wanted to finish quickly so I could start writing articles? I fell down a bit of a Next.js rabbit hole.
What's new?
- Its in Next.js now
- Completely statically rendered
- Supports the entire Obsidian markdown superset see: Obsidian Cheatsheet and Callout test
- Supports MDX see: Vintage Story Mod Launcher for a carousel example :)
- Added MathJax support cause it comes with Obsidian
- Uses front matter instead of the homebrew solution I tried in Blog Init (I didn't realise it was a thing)
- A lot of CSS upgrades
- Kind of does article groups, but I'm still trying to work out the styles and UX for this
- Leveraged Next.js to generate site metadata and optimise SEO
- Estimated time to read
How'd I do it?
Next.js:
Next advertises their SSR a lot, their speciality is mixing SSR and client components to allow people to create highly optimised sites with the best of both worlds. What I didn't realise is that you can change a single parameter in the next.config.ts file and get it to run all the SSR at build time and spit out a static site.
typescriptconst nextConfig: NextConfig = {
output: 'export',
}
The other function you need to know is the generateStaticParams, this allows you to pre-dictate the URI parameters that will be passed to a dynamic URL.
When the site is built it will generate an HTML page for each of these parameters:
typescriptexport async function generateStaticParams() {
// collect all my blog pages
const paths = (await globby("src/content/*.md*")) //*.md* so i pick up mdx
.map(toFileName);
// return the list of slugs
return paths.map((path) => {
return { slug: path }
});
}
For the index page I basically do the same thing by collecting all the paths using globby, extract each files metadata and then render each in a list.
The List is actually all client side rendered though, and it has to be wrapped in a dynamic function set to ssr: false otherwise I can't access local storage which I'd like for caching user preferences (sorting, maybe which articles you've read idk the sky's the limit)
When to static, when to not
I had this idea running around in my head, I've already gone so far with static rendering maybe I can pre-render all my stuff including tag filters and what not I.e. someone wants to filter by the godot tag, that would take them to the /tags/godot page which would only show
The MD parser
This is arguably the most important part of the site, I've wrapped my MD parser in a nice component I can call like so:
typescript<MDRenderer params={{mdText: rawMdString}}></MDRenderer>
Under the hood it does the following:
- Run a pre-processor step to transform low-hanging fruit to MD (e.g. Obsidian wiki links etc) using regex
- Return the MDXRemote component with my settings and TSX components as parameters.
The options are where I define a stack of plugins to get the raw MD parser used by MDXRemote to parse the Obsidian superset. Notably:
- Code highlight
- MathJax
Below is an example of a pre-processor step, note that there are some other significantly more complicated steps:
typescript// Obsidian highlight
text = text.replaceAll(/==([^=]+)==/gi, "<mark>$1</mark>")
Result:
I actually spent some time looking into SEO this time around, Google Chrome has its Lighthouse feature which is really cool, with the static pages I'm getting significantly better scores than earlier
The major improvements are:
- Proper indexing I'm using a proper router now.
- SEO, I'm dumping each article's front matter into the page metadata using the
generateMetadatafunction. - Performance: I'm no longer getting the massive pop-in and slow first contentful paint I was getting when I was processing the MD each time a page was opened. I can also use Next.JS' link pre-rendering function now.
There are a few things left to iron out, it doesn't particularly like my image file sizes, so I'm intending to write a script which turns them all into WebP and resize them for my max article width (780px).
Notes on metadata
I never really thought much about metadata tags because I never really did anything I specifically wanted people to see unless I linked them to it, I guess that's changed now. I'm doing some automatic stuff with metadata generation.
One cool thing I like is that if I include the "hidden" tag in the properties of my obsidian note it will set <meta name="robots" content="noindex, nofollow"> so hopefully it won't show up in web results. This is helpful for me because sometimes I like to show select people my stuff before publishing it so I'll be able to ghost publish it without it being indexable which I think is neat.
Roadmap:
-
Precompile all MD files into HTML-
Using plugins to precompile highlights etc
-
- Add GDScript extension to highlight plugin
-
Use front matter to collect metadata-
automatically mark files with last updated and created at
-
-
optimise files at compile time
- shrink so width is max 780 (put this as env var)
- convert to WebP
- lazy load
- check that all asset references exist. Throw build error otherwise
-
carousel JSX component-
Add 1 to index and remove subtract 1 from length in page number -
tap to enlarge image
-
-
add icons back to out links -
add language hint to code (need to add as post-process step) -
add line breaks back toHTMLl output -
fix css-
images not centred -
title not centred -
Blockquotes don't work -
Style check boxes-
remove bullet from in front of checkbox
-
-
out link icons push text around -
date / read time flex-shrink 0 - make tags wrap -
tags on new line after info
-
-
support external videos with IFrame -
support alt text for images and videos[link.png|alt text]-
alt text is displayed alongside in carousels
-
-
calculate time to read - update loading screen
- update error screen
- ensure obsidian markdown tabs are translated properly
- ensure lists are indented properly
- turn list view from client view back to server component
- make custom check boxes fallback to regular check boxes when rss
Block quote issues: -
transform > lines to Blockquote tags with multiline regex. If call-out then set class name and header (easier to split on new line before we parse the MD) (this will allow md in call-out head) -
fix lists in block quotes
Groups:
-
all posts / grouped posts view (sorted by most recently posted in group) -
hidden tags does this -
Go back to date post sorting-
allow clicking on group to filter by group -
Can click on tag to index by tag - Add Subtitles for each month (i.e aug / sep)
- paginate by year
-
-
rss feed -
Next and last pages based on folder grouping not full collection