This repository hosts the source for minhdq.dev, a personal blog and portfolio powered by Next.js 15, Tailwind CSS 4, Contentlayer, and the Pliny component suite. The site mixes long-form MDX content, static portfolio pages, and richer motion-driven sections (app/old_home). Use this brief to align any future coding or writing agents on the project context, expectations, and workflows.
- Purpose — Publish technical blog posts, highlight selected work, and provide contact points for Dang Quang Minh.
- Key user flows — Read featured blog posts on the landing page (
app/Main.tsx), browse/blog, filter via/tagssidebar, review/projects. - Rendering model — Next.js
appdirectory with React Server Components. Only interactive areas opt into'use client'(e.g., theme switcher, search, old homepage). - Content source — MDX files under
data/blogand author profiles underdata/authors. Contentlayer turns them into typedallBlogsandallAuthorscollections. - Styling system — Tailwind CSS 4 with the
css/tailwind.csstheme file defining custom palettes, variants, and prose tweaks.
| Layer | Notes |
|---|---|
| Framework | Next.js ^15.5.5 with React 19, App Router, dynamic metadata helpers (app/seo.tsx). |
| Content pipeline | Contentlayer 2 + Pliny MDX plugins (contentlayer.config.ts), remark and rehype tooling, automated tag count and search index generation. |
| UI | Tailwind CSS 4, Headless UI, Motion, lucide-react icons, custom components under components/. |
| Data & metadata | data/siteMetadata.js centralizes analytics (Umami), comments (Utterances), search (kbar), social links, and theme options. |
| Scripts | npm run dev, npm run build (runs next build then scripts/postbuild.mjs → RSS), npm run start, npm run lint. |
| Optional deploy guides | See faq/deploy-with-docker.md for Docker output mode instructions. |
app/— Route handlers, layouts, metadata routes (robots.ts,sitemap.ts), newsletter API proxy, blog archive, dynamic tag routes, standalone pages (projects,about), and the experimentalold_homehero.components/— Shared UI (Link, Tag, Header, Footer, MDXComponents, SearchButton, ThemeSwitch, ScrollTopAndComment, SkillPill, etc.).components/index.tsexposes the CTA-style pieces reused byapp/old_home.layouts/— Blog-specific shells (PostLayout,PostSimple,PostBanner) plus listing layout with tag sidebar (ListLayoutWithTags.tsx).data/— Only source of truth for site content. Includesblog/MDX posts,authors/,featured.tsdata for the animated home, nav links, metadata, social logo SVG, and bibliography references.css/— Tailwind entrypoint with theme tokens, prose overrides, and custom utilities such as.no-scrollbarand.content-header-link.docs/WRITING_GUIDELINE.md— Tone, structure, and evidence expectations for every post. Link to it whenever content work is requested.faq/— How-to notes for customizing search and MDX components plus Docker deployment.scripts/—postbuild.mjsrunsscripts/rss.mjsto emitfeed.xmland tag-specific RSS underpublic/tags/<tag>/feed.xml. Never skip this after adding blog content.public/— Favicons, RSS output, static images, downloadable CV, etc.
npm installonce, thennpm run devto launch on localhost (default port 3000).- Set
INIT_CWDviacross-envfor Contentlayer so file watchers resolve paths consistently. - Dark/light theming is handled via
ThemeProviders(next-themes). Confirm color contrast when editing Tailwind classes.
npm run lintscansapp,components,layouts, andscripts.npm run builddoes more than compile: it triggers Contentlayer, rewritesapp/tag-data.json, and executes the RSS generator.- Environment-driven options:
BASE_PATHto serve from a sub-path (used inside metadata URLs, favicons, search index path).EXPORT=1toggles staticnext exportmode and switches RSS output toout/.UNOPTIMIZED=1tells Next Image not to optimize assets (handy for self-hosted deploys).
- Add or edit MDX files under
data/blog/**. Required front matter (seecontentlayer.config.ts):title,date(ISO string), optionaltags: [],draft,summary,images,authors,layout,bibliography, andcanonicalUrl.
- Reference
docs/WRITING_GUIDELINE.mdfor structure and tone. Include real incidents or before/after stories per section 4 of that document. - Author bios live under
data/authors/**. Updateauthorsarrays in blog front matter to pull the right metadata intoPostLayout. - Tag counts feed
app/tags/and RSS. They are autogenerated; do not editapp/tag-data.json. MDXLayoutRendererpipes through components defined incomponents/MDXComponents.tsx. Add new MDX widgets there (and tocomponents/) if needed.
app/Main.tsxdrives the homepage: shows five newest posts, summary text fromsiteMetadata.description, and optionally the PlinyNewsletterForm.app/blog/page.tsxandapp/blog/page/[page]/page.tsximplement pagination usingListLayoutWithTags. Guardrails: invalid page numbers callnotFound().app/tags/[tag]/page.tsxbuilds canonical URLs and RSS link metadata for each tag slug produced viagithub-slugger.app/projects/page.tsxmirrors the curated work history (update copy here plusdata/featured.tsas needed).app/old_home/page.tsxis a'use client'showcase using Motion, custom scroll interactions, and data fromdata/featured.ts. Any change must keep hooks and DOM refs stable—expect TypeScript to be loose because of direct DOM access.app/api/newsletter/route.tsproxies to the provider defined insiteMetadata.newsletter.provider; keep it in sync if you enable Mailchimp/Buttondown/etc.- Metadata routes
app/robots.tsandapp/sitemap.tsrely onsiteMetadata.siteUrlandallBlogs. UpdatesiteMetadatawhenever deployment URLs change.
- Respect the app-directory conventions: server components by default, add
'use client'only when hooks or browser APIs are needed. - Stick to Tailwind utility classes defined in
css/tailwind.css. Use the provided color tokens (primary,gray) to keep light/dark themes aligned. - For small class name merges, use
lib/utils.tsx→cn()rather than installing another helper. - When touching blog rendering, remember
PostLayoutconsumescontent,authorDetails, andnext/prevposts. KeepallCoreContent(sortPosts(allBlogs))in sync with any filter changes. - Validate interactive changes with at least
npm run lint. For significant UI rewrites, runnpm run buildto exercise Contentlayer and custom scripts.
- Follow
docs/WRITING_GUIDELINE.mdverbatim for structure (Context → Architecture → Walkthrough → Failures → Trade-offs → References) and tone (relaxed yet precise). - Each post must include evidence (logs, benchmarks, docs links) for non-trivial claims, per Section 7 of the writing guide.
- Prefer English unless a topic explicitly targets Vietnamese readers; maintain consistency within a post.
- Use front matter tags that already exist when possible so tag counts stay meaningful.
- Keep
data/siteMetadata.jsupdated when analytics, newsletter, or comment providers change. Some providers need CSP updates innext.config.js. - After editing metadata or nav links, verify
Header(components/Header.tsx) andFootersocial icons still link to valid URLs. - When deploying via Docker, set
output: 'standalone'as described infaq/deploy-with-docker.md. - Remember to regenerate RSS (
npm run buildornode scripts/rss.mjs) and redeploy static assets if you add or rename blog posts.
-
npm run lintpasses without warnings. -
npm run buildsucceeds locally (guarantees Contentlayer, tag counts, RSS, and search documents are fresh). - No manual edits to
app/tag-data.json; verify Git diff only includes source files. - New or edited posts include required front matter plus references per the writing guide.
-
data/siteMetadata.jsvalues reflect any new social or contact links.
- Writing voice and structure —
docs/WRITING_GUIDELINE.md - Search customization —
faq/customize-kbar-search.md - Adding MDX components —
faq/custom-mdx-component.md - Deploying with Docker —
faq/deploy-with-docker.md
This blog has been optimized for AI agent access with the following features:
- BlogPosting schema.org markup: Every blog post includes comprehensive JSON-LD with headline, author, dates, keywords, mainEntityOfPage, and publisher information
- BreadcrumbList schema: Navigation context via structured breadcrumb data
- All schema embedded in
<head>as JSON-LD for easy parsing
- Download Markdown button: Available on every blog post
- API endpoint:
/api/blog/{slug}/markdownserves raw markdown with frontmatter - Content-Type: Served as
text/markdownfor proper MIME type handling - rel="alternate" link: Discoverable via
<link rel="alternate" type="text/markdown">in page metadata - Frontmatter included: Full metadata (title, date, tags, authors, summary) in YAML format
- Full content: RSS feeds include complete post content via
<content:encoded>element - Main feed:
/feed.xmlwith all published posts - Tag-specific feeds:
/tags/{tag}/feed.xmlfor filtered content - Metadata: Each item includes title, link, description, pubDate, author, and categories (tags)
- AI Agents page:
/ai-agentsdocuments all agent-friendly features, endpoints, and usage guidelines - HTML Sitemap:
/sitemap-pageprovides hierarchical view of all content - XML Sitemap:
/sitemap.xmlfor programmatic discovery - Search Index:
/search.jsonin kbar format for client-side search
- Proper use of
<header>,<nav>,<main>,<article>,<section>,<footer> - Navigation wrapped in
<nav aria-label="Main navigation"> - Breadcrumbs use
<nav aria-label="Breadcrumb">with proper ARIA attributes - Exactly one
<h1>per page with logical heading hierarchy - Accessible and screen-reader friendly
- Consistent URL patterns:
/blog/{slug}for all posts - Tag archives:
/tags/{tag}with RSS feeds - Clear link text: Descriptive labels like "Download Markdown" instead of generic "Download"
- robots.txt: Publicly accessible for crawler directives
- OpenGraph & Twitter Card: Rich social metadata on all pages
Documented at /ai-agents page:
- Preferred access methods (RSS, markdown download, HTML)
- Rate limiting recommendations (1-2 second delays)
- Example curl commands for common tasks
- Contact information for agent-related questions
Use this document as the entry point whenever you spin up new agents (coding, content, ops) so they can work autonomously without re-auditing the entire repository.