This starter is intentionally small, so most personalization happens in a handful of files. Work through this guide after cloning the repository to replace the example identity, add your projects and work experience, publish your own writing, and verify the production setup.
1. Install and run the project
The project requires Node.js 20.9 or newer and pnpm 11.
git clone https://github.com/qiyangdev/notes-site-starter.git
cd notes-site-starter
pnpm install
pnpm devOpen http://localhost:3000 (opens in a new tab) and keep the development server running while you make changes.
2. Replace the public site URL first
Update baseUrl in app/sitemap.ts with the final production origin of your site:
export const baseUrl = 'https://YOUR_DOMAIN'Use the origin only, without a trailing slash. This value is shared by page metadata, canonical URLs, article sharing images, JSON-LD, sitemap.xml, robots.txt, the RSS feed, and llms.txt. Setting it early prevents example URLs from leaking into search results, feeds, social previews, or machine-readable indexes.
If you do not have a custom domain yet, use the production URL assigned by your hosting provider and update it later.
3. Define your site identity
Edit app/layout.tsx and replace the starter metadata with your own:
- Change the default title and title template.
- Write a concise description of who you are and what you do.
- Update the Open Graph title, description, and site name.
- Change
<html lang="en">if your primary language is different.
For example:
export const metadata: Metadata = {
metadataBase: new URL(baseUrl),
title: {
default: 'YOUR_NAME — Designer and Developer',
template: '%s | YOUR_NAME',
},
description: 'YOUR_SHORT_BIO',
openGraph: {
title: 'YOUR_NAME',
description: 'YOUR_SHORT_BIO',
url: baseUrl,
siteName: 'YOUR_NAME',
locale: 'en_US',
type: 'website',
},
}Keep the existing robots configuration unless you intentionally want to prevent indexing.
4. Rewrite the homepage, portfolio sections, and navigation
The homepage lives in app/page.tsx. Replace the heading and introduction with a short explanation of your work, then arrange the three provided sections in the order you want:
app/components/writing.tsxwraps the published article list.app/components/projects.tsxrenders project names, descriptions, years, and optional links.app/components/work-experience.tsxrenders companies, roles, and date ranges.
Projects and work experience share one data source in app/data/portfolio.ts. Replace the starter entries there instead of hard-coding content inside the components:
export const projects = [
{
name: 'My Project',
description: 'A short explanation of what I built',
year: '2026',
href: 'https://example.com',
},
]
export const workExperience = [
{
company: 'My Company',
role: 'Design Engineer',
period: '2024 — Present',
},
]Project links are optional. When href is present, the project name is clickable; when it is omitted, the component renders the name as plain text. Edit the corresponding component when you need different fields or presentation, and keep dates short enough to remain readable on narrow screens.
Content Collections indexes every .mdx file in app/blog/posts, and BlogPosts automatically supplies the Writing section with published entries, so no manual post index is required.
Navigation links are defined in app/components/nav.tsx. Add the routes that match your portfolio, remove links you do not need, and replace the starter deployment URL. A simple configuration might look like this:
const navItems = {
'/': { name: 'home' },
'/blog': { name: 'blog' },
'/about': { name: 'about' },
}Internal paths are rendered with Next.js navigation. Full URLs can also be used for external destinations.
5. Update the footer and author details
Edit app/components/footer.tsx to replace:
- The source repository URL
- Any social or contact links
- The copyright owner
- Links that do not belong on your site
The page metadata and JSON-LD author are configured together in the articleAuthor object in app/blog/[slug]/page.tsx. Replace Qiyang and the GitHub profile URL with your own identity. If the publisher is an organization rather than a person, update the JSON-LD type accordingly.
The RSS channel title and description live in app/rss/route.ts. Update both so feed readers display your identity instead of the starter text.
Keep the MIT license and original copyright notice when redistributing the project. You may adapt the visible upstream link to your design, but preserve the attribution required by the repository license.
6. Publish your own posts
Create one .mdx file per article in app/blog/posts. The filename becomes the URL slug, so building-my-site.mdx is published at /blog/building-my-site.
Every post needs this frontmatter:
---
title: 'Building My Site'
publishedAt: '2026-08-09'
updatedAt: '2026-08-12'
summary: 'A short description used in metadata, feeds, and previews.'
draft: false
---The optional updatedAt field records the date of a meaningful content update and must not be earlier than publishedAt. Omit it until the article changes; builds read this value but never rewrite the source file.
The draft field is optional and defaults to false. Set it to true to keep an unfinished post in the repository while excluding it from post lists, direct article routes, RSS, and the sitemap.
The collection schema and MDX plugins live in content-collections.ts. Update that file when you add frontmatter fields such as tags or categories; the development server and production build validate every article and generate the corresponding TypeScript types.
You can then write with Markdown or MDX. The renderer supports GitHub Flavored Markdown, syntax-highlighted code blocks with a copy button, tables, task lists, footnotes, and KaTeX math.
For standard Markdown images, place the files under public/images/blog and reference them from the site root. The shared MDX image component reads matching files at build time, so Next.js can infer their dimensions and optimize them without maintaining a manual image map:
The same automatic dimension lookup applies when you use the custom Image component with a root-relative public path, so dimensions are optional:
<Image
alt="A descriptive alternative text"
src="/images/my-project.png"
/>For remote images, allow the host in next.config.ts and provide explicit width and height values.
To use a dedicated social image for a post, add an image field to its frontmatter:
image: '/images/my-project-social.png'Remove app/blog/posts/gfm-rendering-test.mdx when you no longer need the rendering fixture. You can also remove this guide after publishing your own content.
7. Customize social previews
When a post does not define an image, the project generates a title-based Open Graph image through app/og/route.tsx. Adjust that route to match your visual identity:
- Change the fallback title.
- Add your name, logo, or role.
- Update background, text colors, spacing, and typography.
Preview the result locally at /og?title=Preview%20Title before sharing a post.
8. Adjust typography and colors
Global typography is configured in app/layout.tsx with Geist and Geist Mono. Replace those next/font imports if you prefer another font, then update the corresponding variables in app/global.css.
The same stylesheet controls the page colors, prose elements, code highlighting, tables, blockquotes, and dark mode. Start with these areas:
::selectionfor text selection colors:rootfor syntax-highlighting tokens@media (prefers-color-scheme: dark)for dark-mode overrides.proserules for article typography
Make small changes while viewing both light and dark mode, and check narrow screens whenever you change spacing or table styles.
9. Choose whether to keep analytics
Vercel Analytics and Speed Insights are mounted in app/layout.tsx. They require no extra configuration when the site is deployed to a supported Vercel project.
If you do not want them, remove the two imports and components from the layout, then uninstall their packages:
pnpm remove @vercel/analytics @vercel/speed-insightsYou can deploy the site with another provider; the application itself does not depend on Vercel-specific hosting.
10. Update repository and machine-readable details
Before publishing your fork, update the project name in package.json and rewrite README.md with your own description, screenshots, repository URL, and deployment instructions. The README preview images live in .github/assets; keep their paths unchanged or update the matching <img> references. Keep the license file and required upstream copyright notice intact.
The root /llms.txt endpoint is generated by app/llms.txt/route.ts. Update its heading, summary, and page descriptions for your identity. Published writing is read from Content Collections, while Projects and Experience reuse app/data/portfolio.ts, so those lists stay synchronized with the homepage automatically. If you remove an entire homepage section, also remove its heading and mapping from the route rather than leaving an empty group.
Keep the root endpoint concise: it should point readers and tools to the canonical pages instead of duplicating complete article content.
Run a final search for starter-specific values:
rg -n "Notes Site Starter|Portfolio Starter Kit|My Portfolio|Qiyang|psk.qiyang.dev|qiyangdev" app README.md package.jsonReview every match and decide whether it is site content to replace or attribution to preserve.
11. Verify before deployment
Run the same checks expected by the repository:
pnpm lint
pnpm typecheck
pnpm buildAfter the production build succeeds, inspect these routes:
/and/blog- At least one article page
/rss/sitemap.xml/robots.txt/llms.txt/og?title=Preview%20Title
Finally, deploy the site, view its page source or metadata with your browser tools, and confirm that every canonical URL and sharing image points to your production domain.
Once those checks pass, the starter is no longer a demo—it is your portfolio.