Lightweight Personal Website Template
A lightweight, content-first personal website template for publishing blogs and projects, with a Markdown workflow and a straightforward path to deployment.
Where the project came from
One Friday evening after work, I decided I wanted a personal website. I already used Notion as a second brain for notes and ideas, and it had become useful in both work and everyday life. A public site felt like a natural place to share the parts that might be useful to someone else.
I spent the night vibe coding the first version. I built the Next.js pages and the Blog and Project content models, connected Obsidian notes to an MDX publishing workflow, and set up GitHub Actions and Vercel. Then I bought a domain, configured DNS and HTTPS, and added search, RSS, a sitemap, and light and dark themes. By the time I stopped, the site was online. I liked the result enough to start extracting the reusable parts into a GitHub template.
What the project is
Lightweight Personal Website Template is the reusable foundation behind this site. It is for developers and creators who want an independent place to publish writing and present their work. A user can replace the profile, branding, and sample content, then write new pages in Markdown. There is no database or admin application to set up first.
The full MyWebsite repository also contains a Spring Boot backend, MySQL, and Redis for engineering practice. The public site does not depend on them. The template keeps the frontend and the content workflow, while the experimental backend and local infrastructure stay outside the core.
Design goals
I started with three constraints:
- Keep the site light and use as few dependencies as practical.
- Make publishing and maintenance simple enough for one person.
- Use a minimal visual style that leaves room for the content instead of decorative animation.
These constraints shaped both the interface and the project structure.
Design details
Keep content in the repository
Blogs and Projects are Markdown files. Each file has frontmatter for its title, summary, dates, tags, cover image, and related content. The body remains ordinary Markdown.
This is less convenient than editing in a hosted CMS, but I prefer owning the files and keeping their history beside the code. Content is also separate from the React components. I can revise the article layout without touching an article, and I can publish an article without editing a page component.
Put reading before interface
The home page introduces the site and shows recent work. Blogs, Projects, About Me, and Search have clear top-level navigation. Article pages use a narrow reading column, and the same design rules work in light mode, dark mode, and on small screens.
The template avoids elaborate animation and large interactive sections. Images, code blocks, and related links support the article. The page itself stays quiet.
Keep the public site small
Published content is read from local MDX during the build. Blogs and Projects do not need a database, and a page request does not call a content API. The deployed site is one Next.js application, so each release and rollback maps to a Git commit.
Comments, authentication, or other dynamic features can use a backend later. The template does not add services before the site needs them.
Technology choices
| Layer | Technology | Why I chose it |
|---|---|---|
| Web framework | Next.js 16 | The App Router covers routing, static generation, metadata, RSS, and the sitemap in one application. It also deploys directly to Vercel. |
| UI | React 19 | Components keep the article layout, navigation, search, and theme controls reusable without turning every page into a client application. |
| Language | TypeScript | Content metadata, component props, and route data have explicit types, which catches missing fields during refactors. |
| Styling | Tailwind CSS | Most styles stay near their components, while shared colors and typography remain in global variables. |
| Content | Markdown and MDX | Markdown works well for writing and version control. MDX adds syntax highlighting and a small set of custom content components. |
| Content pipeline | gray-matter, unified, and remark | Frontmatter parsing, Markdown conversion, internal links, and asset validation all run locally. |
| Code highlighting | Shiki | Highlighting happens during rendering, so readers do not download a browser-based editor. |
| Hosting | Vercel | Git integration produces a Preview for each pull request and deploys production after a merge. |
| Continuous integration | GitHub Actions | Every change checks content synchronization, linting, TypeScript, and the production build. |
Obsidian is an optional writing tool, not a runtime dependency. The source files are plain Markdown and work just as well in VS Code or another editor.
Project structure
The parts that matter to the template live in frontend and obsidian:
MyWebsite/
frontend/
content/ # Generated MDX read by the site
public/ # Cover images and article assets
scripts/content/ # Content synchronization and validation
src/app/ # Next.js routes and pages
src/components/ # Navigation, articles, search, and UI
src/data/ # Profile and site configuration
obsidian/
blog/ # Blog Markdown sources
projects/ # Project Markdown sources
project-posts/ # Child articles for collection projects
assets/ # Source images used by content
templates/ # Starting points for new notesMarkdown under obsidian is the source of truth. The files in frontend/content are generated output and should not be edited by hand.
Deploying a complete site
This workflow keeps the source on GitHub and deploys the public site with Vercel. It requires Node.js 22, Git, a GitHub account, and a Vercel account. A custom domain is optional.
1. Run the template locally
After creating a repository from the template, clone it and install the locked dependencies:
git clone <your-repository-url>
cd <your-repository-name>/frontend
npm ci
npm run devOpen http://localhost:3000. Check the home page, Projects, Blogs, and About Me before making changes.
2. Replace the site information
Start with the name, role, and contact email in frontend/src/data/site.ts. Then update the home page, About Me page, metadata, and site branding. In the current project, the relevant files are:
frontend/src/data/site.ts
frontend/src/app/layout.tsx
frontend/src/app/page.tsx
frontend/src/app/about/page.tsx
frontend/src/components/Header.tsxProfile photos and shared images live in frontend/public/images. Update the import or public path whenever a filename changes.
Run the full local checks after customization:
cd frontend
npm run content:test
npm run content:check
npm run lint
npm run typecheck
npm run build3. Push the customized site
git add .
git commit -m "Customize personal website"
git push origin mainA pull request works better when more than one person maintains the site. GitHub Actions checks the branch, and Vercel can attach a Preview URL before the change reaches main.
4. Create the Vercel project
Import the GitHub repository into Vercel and use these settings:
Framework Preset: Next.js
Root Directory: frontend
Node.js Version: 22.x
Install Command: npm ci
Build Command: npm run buildAfter the first deployment, check the main routes and generated files:
/
/blogs
/projects
/about
/search
/robots.txt
/sitemap.xml5. Set the canonical URL
Once the public domain is known, add this environment variable in Vercel:
NEXT_PUBLIC_SITE_URL=https://example.comThe site uses it for canonical URLs, Open Graph metadata, RSS, and the sitemap. Redeploy after saving the value.
For a custom domain, add both the apex and www hostnames to Vercel, then copy the DNS records Vercel provides to the DNS provider. Choose one hostname as canonical and redirect the other. Finally, verify that HTTPS, redirects, and sitemap.xml all use the same hostname.
Publishing new content
Source content lives under obsidian. The folder can be opened as an Obsidian vault or edited with any text editor.
A Blog starts with frontmatter like this:
---
status: "draft"
title: "My first post"
slug: "my-first-post"
summary: "A short description of the article."
coverImage: ""
publishedAt:
updatedAt: 2026-08-25
category: "Software Engineering"
tags: ["Next.js"]
pinned: false
relatedBlogs: []
relatedProjects: []
---Keep status as draft while writing. When the article is ready, change it to published, add publishedAt, and run:
cd frontend
npm run content:sync
npm run content:checkThe synchronization script reads the Markdown, validates frontmatter, internal links, and images, then generates the MDX used by the website. Commit the source Markdown, generated MDX, and any generated assets together.
Projects use the same workflow and can add techStack, demoUrl, and repositoryUrl. A standard Project renders as one article. A collection Project can hold a set of smaller articles under one project page.
Add an image to an article
Place the source image in obsidian/assets, then reference it with Obsidian image syntax:
![[architecture.png|Architecture overview]]content:sync copies the image into the article's public asset directory and rewrites its path in the generated MDX. The pipeline accepts PNG, JPEG, WebP, GIF, and AVIF files.
Publish to production
After the checks pass, push a branch and inspect the article in the Vercel Preview. Check its images, code blocks, search result, RSS entry, and sitemap entry. Merging the branch into main triggers the production deployment.
Maintenance
The template has no database migrations or separately hosted content to back up. Code, articles, and images are all stored in Git. Most maintenance work is dependency updates and release checks.
I run the following checks after a framework upgrade and use the same list for a periodic dependency review:
cd frontend
npm audit
npm outdated
npm run content:test
npm run content:check
npm run lint
npm run typecheck
npm run buildDo not store API keys, private notes, or unpublished personal attachments under obsidian, because the folder is committed to the public repository. Features that need credentials should read them from Vercel environment variables.
If a release is broken, revert its Git commit or pull request, run content:check, and let the next main deployment restore the previous version. To remove an article without deleting its history, change its status to archived and synchronize the content again.
If a future version adds a dynamic backend, its database and Redis instance should stay on a private network. The local Docker Compose file is not intended for a public server. For a personal site that only publishes Blogs and Projects, the frontend-only deployment is much easier to maintain.
Extracting the template
The template is built from the shared parts of MyWebsite. Personal content and the experimental backend are removed, and site branding will move into one configuration file. Sample Blogs and Projects, content validation, and Vercel deployment settings will remain so a new user can replace the examples and publish a site without rebuilding the system first.