Hello World - Welcome to shinyaz.com
Table of Contents
Introduction
Welcome to shinyaz.com! This is a minimal, monochrome personal tech blog built with Next.js 16 and MDX.
In this post, I'll walk through the technologies and features that power this blog.
Tech Stack
| Category | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Styling | Tailwind CSS v4 |
| Content | MDX + Velite |
| Code Highlighting | Shiki + rehype-pretty-code |
| Math Rendering | remark-math + rehype-katex |
| Dark Mode | next-themes |
| PWA | Serwist |
| Testing | Vitest + Playwright |
| Deployment | Vercel |
Bilingual Support (Japanese / English)
Every page on this blog is available in both Japanese and English. Instead of relying on an external i18n library, translations are handled by a simple TypeScript dictionary object.
const dictionaries = {
ja: {
site: { name: "shinyaz.com", description: "..." },
nav: { home: "ホーム", blog: "ブログ", ... },
// ...
},
en: {
site: { name: "shinyaz.com", description: "..." },
nav: { home: "Home", blog: "Blog", ... },
// ...
},
};- A language switcher button ("EN / JP") in the header toggles between locales while staying on the same page
- URLs always include a locale prefix (e.g.,
/en/blog/...,/ja/blog/...) - Bare paths automatically redirect based on the browser's
Accept-Languageheader - Content files are organized by locale (
content/posts/en/,content/posts/ja/)
Dark Mode
The blog supports class-based dark mode toggling via next-themes. Theme tokens are defined as CSS custom properties, switching between :root (light) and .dark (dark).
Code blocks also adapt automatically, using Shiki's dual theme (github-light / github-dark) that responds to the active theme.
function greet(name: string): string {
return `Hello, ${name}! Welcome to shinyaz.com.`;
}
console.log(greet("World"));Content Pipeline
Posts are written as MDX files and transformed into static data at build time by Velite.
content/posts/{en,ja}/*.mdx
→ Velite (prebuild)
→ .velite/ (static data)
→ Next.js (App Router)
MDX content is processed through the following plugin chain:
- rehype-slug — Automatically adds IDs to headings for anchor links
- remark-math + rehype-katex — Math rendering
- rehype-pretty-code (Shiki) — Syntax highlighting with dual themes
Code Highlighting
Syntax highlighting supports multiple languages with optional filename display.
def fibonacci(n: int) -> list[int]:
"""Generate Fibonacci sequence up to n terms."""
if n <= 0:
return []
fib = [0, 1]
for _ in range(2, n):
fib.append(fib[-1] + fib[-2])
return fib[:n]
print(fibonacci(10))Inline code works too: const x: number = 42;
Math Rendering
LaTeX-style math expressions are fully supported.
Inline math:
Block math:
The quadratic formula:
Auto-Generated Table of Contents
Each post displays a table of contents automatically generated from its headings (h2, h3). An IntersectionObserver-based scroll spy highlights the section currently in view.
Client-Side Search
The blog includes a fully client-side search engine with no external services.
- Searches across four fields: title, description, categories, and tags
- AND matching with space-separated tokens (only posts matching all tokens are shown)
- Real-time filtering (results update as you type)
- Syncs with URL query parameters (
?q=nextjsfor direct linking)
Fonts
The blog uses the IBM Plex font family throughout.
- IBM Plex Sans — Latin text (weights: 400, 500, 600, 700)
- IBM Plex Mono — Code blocks (weights: 400, 700)
- IBM Plex Sans JP — Japanese text (weights: 400, 500, 700, lazy-loaded)
The Japanese font is loaded with preload: false to optimize initial page load performance, since its file size is significantly larger.
SEO
The blog implements several SEO best practices:
- Structured data (JSON-LD):
WebSiteschema on the layout,BlogPosting+BreadcrumbListschema on each post - Open Graph / Twitter Cards: Meta tags for social sharing
- hreflang / canonical: Proper markup for translation pairs across locales
- Sitemap: Dynamic sitemap covering all locales
- RSS / Atom feeds: Auto-generated RSS 2.0 and Atom 1.0 per locale (latest 20 posts)
PWA & Offline Support
The blog is a Progressive Web App powered by Serwist.
- Precache manifest is automatically injected at build time, precaching static assets
- An offline fallback page is displayed when there's no network connection
- Runtime caching strategies optimally cache Next.js pages and assets
Security
HTTP security headers are applied to all routes:
- Content-Security-Policy — Restricts resource origins
- Strict-Transport-Security — Enforces HTTPS with a 2-year max-age
- X-Content-Type-Options / X-Frame-Options — Prevents MIME sniffing and clickjacking
- Permissions-Policy — Disables unnecessary APIs like camera, microphone, and geolocation
Testing
Quality is maintained through a two-layer testing strategy:
- Unit / component tests (Vitest) — Verify library functions and component behavior
- E2E tests (Playwright) — Integration tests for navigation, blog, search, i18n, offline support, and security headers
Summary
shinyaz.com packs a lot of technical detail behind its minimal design. I'll continue posting articles about technology on this blog. If any of these features interest you, feel free to explore the source code.