shinyaz.com

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

CategoryTechnology
FrameworkNext.js 16 (App Router)
StylingTailwind CSS v4
ContentMDX + Velite
Code HighlightingShiki + rehype-pretty-code
Math Renderingremark-math + rehype-katex
Dark Modenext-themes
PWASerwist
TestingVitest + Playwright
DeploymentVercel

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-Language header
  • 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.

example.ts
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:

  1. rehype-slug — Automatically adds IDs to headings for anchor links
  2. remark-math + rehype-katex — Math rendering
  3. rehype-pretty-code (Shiki) — Syntax highlighting with dual themes

Code Highlighting

Syntax highlighting supports multiple languages with optional filename display.

fibonacci.py
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: E=mc2E = mc^2

Block math:

ex2dx=π\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}

The quadratic formula:

x=b±b24ac2ax = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}

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.

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=nextjs for 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): WebSite schema on the layout, BlogPosting + BreadcrumbList schema 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.

Share this post