I created this project primarily with Next.js, React and Tailwind to practice and demonstrate my programming skills. Blog posts are written in MDX, allowing me to embed interactive React components in Markdown, a simple, easy-to-read markup language. It really is the best of both worlds.
The posts live on the server (I suppose I use GitHub as a CMS) and are also compiled and bundled server-side using mdx-bundler. Each blog-post page, in fact, is also rendered on the server at build time, and the resulting HTML is served to clients and reused on each request.
I also have some plain Markdown content for items in my portfolio, and that is served in a similar way. Since it's just regular old Markdown though there's no need for compiling and bundling the content, so it simply gets passed in the server as plain text and rendered there with the help of react-markdown.
Those are the basics of what's going on with this blog; below I'll go into more details in case anybody wants learn more about how I have things organized or if they want to build a blog of their own using this code.
- For the most part, every TypeScript file gets its own directory, with exports passed onto the
directory's
index.ts
file. - All exports (default and named) are declared at the end of files.
- Code is formatted according to the rules outlined in
.eslintrc.js
and.prettierrc.js
.
Here we find all the from-scratch components used in the website, except those consumed in blog-post MDX files. The exception to this is the <BlogPlot>
, which contains the large Plotly import so that posts that use it don't have such a large amount of data, since this strains the client.
An important detail is that components used exclusively on the posts/id
page have names beginning with "Blog." The <Layout>
component is also special and worth mentioning since it's a parent of every page.
For things that don't change and are used in multiple parts of the application.
This directory is basically the "database" for the application. Here we have the MDX blog posts, the components embedded into that MDX and also the Markdown files for portfolio items.
If src/content
is the database of the app, then this directory is like the API (in a way, since everything that consumes the data returned from here still gets rendered on the server anyway). Retrieving portfolio items is fairly straightforward, since our use-case is only ever getting all of them, but post retrieval is a bit more involved, since sometimes we only want the frontmatter or a filtered subset of the posts.
If I ever move away from using GitHub as my CMS and instead set up a separate back end to serve data from a database, then this directory will probably hold all the functions for hitting that API for reusability purposes.
The code for managing application state via the Redux ducks pattern. Right now the only application state we're managing is the userPreferences
slice with the useDarkMode
state and toggleUseDarkMode
reducer.
Any TypeScript interfaces that get reused.
This directory implements the Next.js Pages Router. Eventually I want to migrate to the App Router.
Here you'll find the Redux store and root reducer.
Here we have global styling for the whole application (e.g., smooth scrolling and Webkit stuff) and also the syntax-highlighting rules for code snippets in blog posts.
This directory is for custom types, in case some dependency isn't typed or we only want a type declaration for a partial bundle of a dependency, as seen here.
We have here shared tools for formatting and general usage, as well as custom React hooks.
This directory is used by Next.js for serving static
files in a
conveniently-programmed way. In my own implementation, images owned by myself are in the
public/myImages
directory, and images I don't own are in the public/otherImages
directory.
Understanding most of the config files in the root directory is just a matter of reading the
official documentation provided for them to see what they're doing for the application. In
next.config.js
we have some stuff going on to make SVGs render as React components. Also, in
tailwind.config.js
is the palette definition for the application, which may be of particular use
if you're going to use this codebase as a template for your own blog.
First, open a terminal (e.g., Git Bash) in some directory where you want the code to live locally.
Then pick a name for the directory that will store the codebase (e.g., my-blog-app
) and enter the
command
git clone https://github.com/ThomasLatham/tlb-app.git my-blog-app
After that, enter the command cd my-blog-app
to navigate into the project's root directory.
(In both the commands above, you can replace the my-blog-app
part with whatever directory name you
chose.)
From there, go into the package.json
file and change the value of the name
property to something of your
choosing (e.g., "my-blog-app"
).
Make sure you have node
and yarn
installed globally on your machine. You can check this by
entering node -v
and yarn -v
in the terminal. To install dependencies for the application, run
yarn
.
Now you can run the application locally in development mode with the command yarn dev
, after which
you can visit localhost:3000
to see the website.
If you plan to open-source your blog's code and/or release the blog to the public, then please follow these steps to ensure licensing is done correctly:
- Open
LICENSE.md
in the project's root directory. - Remove everything from the top of the file down to (but not including) the
# License Texts
heading. - Remove the
## GPLv3
subheading (and its content) under the## MIT License
subheading. - At the top of the file add the following section:
# Third-Party Software ## Thomas Latham Copyright (c) 2023 Thomas Latham License: MIT
- If you want to open-source your code, then choose a license and
modify the
LICENSE.md
file to reflect your choice.
In the public/myImages
directory, replace logo.svg
with a logo of your own. I used
UXWing to find an attribution-free SVG, and then I used a mix of
GIMP and Inkscape to edit and add onto the SVG to
my liking. I used favicon.io to generate the icons in the public/myImages/favicon
directory, using just the honeycomb symbol portion of my logo.
Start by cleaning out the files in the src/content/posts
directory. Once cleanup is done, to
create a blog post, make a new MDX file in that same directory (e.g., my-first-post.mdx
). The
filename (without the .mdx
extension) will be used as the slug for the post URL, so I
recommend-using-kebab-case-for-that.
Inside that file you just created, at the very top you'll want to have some frontmatter in the following format:
---
title: My First Post!
datePublished: "2024-01-03"
author: Finn Mertins
description: A short description for your first blog post.
tags: ["someTag", "anotherTag"]
---
The above is just a template, so feel free to change up the fields to your liking.
Below that you can add any Markdown or React components you want for your post's content. You don't really have to do anything else besides this to make a new post because the application automatically does the rest for you, from creating a listing and tag-filter buttons in the post-search page to styling the post itself and the creating its table of contents. There's some fine-tuning you can do though, if you want:
- To change the way code snippets are displayed, you'll want to check out and play with the
src/styles/syntaxHighlighting.css
file. Some code-snippet styling is also done intailwind.config.js
in the root directory, but that's just keeping Tailwind from wrapping every snippet in quotes. - To change the depth of the table of contents, you'll want to take a look at the
useHeadingsData()
function insrc/utils/hooks/hooks.ts
. It looks like:
const useHeadingsData = () => {
const [nestedHeadings, setNestedHeadings] = useState<HeadingWithNestedHeadings[]>([]);
useEffect(() => {
const headingElements: HTMLElement[] = Array.from(document.querySelectorAll("h1, h2"));
const newNestedHeadings = getNestedHeadings(headingElements);
setNestedHeadings(newNestedHeadings);
}, []);
return { nestedHeadings };
};
Change the argument of document.querySelectorAll()
to get the desired TOC depth you're after. For
example, if you only want top-level headings to be included in the TOC, then the argument to that
function would just be "h1"
. If you wanted a depth of three, though, then you could pass "h1, h2, h3"
as the argument.
If you don't want a portfolio on your website, follow these steps:
- Delete the
src/pages/portfolio
directory so that Next.js doesn't create a route for the page. - In
src/constants/index.ts
, remove the["Portfolio", "/portfolio"]
element from theNAV_OPTIONS
array so that the "Portfolio" option doesn't appear in the navbar and the footer.
If you do want a portfolio, follow these steps (the example for which follow my arbitrary naming conventions):
-
Delete the directories which currently reside in the
src/content/portfolioItems
directory. -
Create a new directory there for your first portfolio item, (e.g.,
dragonTracker
). -
Inside that directory create a single Markdown file (e.g.,
dt-info.md
) to hold the content of your portfolio item. It's important that there is precisely one Markdown file in the directory. -
Put a PNG in the same directory that will act as the image for the portfolio card (see my portfolio for an example). The image should be roughly square (same height and width dimensions) in order for it to not get distorted. Name it, for example,
dt-image.png
. -
At the top of your Markdown file, create frontmatter according to the following format:
--- id: "dragon-tracker" title: "Dragon Tracker" role: "Creator" description:"An application I made to globally geolocate dragons and estimate the values of their gold hoards." cardImage: "dt-image.png" ---
It's important that the value of the
cardImage
field matches the name and file extension on the card image you placed in the portfolio item's directory. I also recommend for readability keeping the value of theid
field in kebab-case since the portfolio item's URL will display aswww.yourblog.com/porfolio#dragon-tracker
.
After following these steps, you should have a working portfolio page that contains your first portfolio item.
To change the colors for the light and dark themes in your blog, open the tailwind.config.js
file
in the project's root directory and modify the values of the properties in the theme.colors
property. For example, if you wanted to change the primary color of the blog's light theme to white,
then you would set the value of theme.colors["primary-light"]
to #ffffff
. To read more, check
out Tailwind's Theme Configuration documentation.
Create a repo in GitHub to house your blog's code remotely. After that, execute the following commands to point your local repo at your new remote one:
git remote set-url origin <URL of the repo you just created>
Then, to make sure your deployment reflects all the changes you just made, push those changes to remote with the following commands:
git add .
git commit -m "initial commit"
git push
With your GitHub repository set up and up to date with your tailored blog content, you're ready to deploy with Vercel. I could describe this process in detail, but I'd basically just be rewriting what the awesome folks at Vercel have already prepared in their Deploying Your Next.js App documentation. Of course there are other deployment options, but Vercel makes the process very simple (and it's the option I chose). After you follow the steps outlined in that guide, your blog should be live. Congratulations on making a blog!