0xJosee.
HomeAboutProjectsBlogContact
0xJosee.

Full-stack developer specializing in Solana DeFi, blockchain engineering, and Web3 application development.

Navigation

HomeAboutProjectsBlogContact

Connect

© 2026 0xJosee. All rights reserved.
DevelopmentMay 10, 20253 min read

This Site's Tech Stack: Next.js 15, MDX, and Full-Stack TypeScript

A deep dive into the technical decisions behind this portfolio site. From the content pipeline to the API layer, every choice was made with developer experience in mind.

Next.jsMDXTypeScriptTailwind CSSHono

Every developer portfolio site is also a statement about how you think about software. The technologies you choose, the trade-offs you make, and the level of polish you achieve all communicate something about your engineering values. This post walks through the key technical decisions behind this site and explains why I made each choice.

The short version: Next.js 15 with the App Router for the framework, MDX for content authoring, Tailwind CSS for styling, Hono for the API layer, Drizzle ORM for database access, and Vercel for deployment. The longer version involves a fair amount of opinionated reasoning that I think is worth sharing.

Why MDX Over a CMS

I evaluated several content management approaches before settling on MDX files stored directly in the repository. The alternatives included headless CMS platforms like Sanity and Contentful, database-backed content with a custom admin UI, and plain Markdown without the component extensibility.

MDX won because it gives me the best balance of writing ergonomics, version control, and flexibility. Content lives alongside code, so there is a single source of truth with full Git history. I can write in Markdown for straightforward posts and drop in React components when I need something interactive. There is no external service dependency, no API latency for content fetching, and no CMS-specific lock-in.

// Content loading with frontmatter parsing and type safety
import { compileMDX } from 'next-mdx-remote/rsc'
import { mdxComponents } from '@/components/mdx/mdx-components'
 
interface ProjectFrontmatter {
  title: string
  slug: string
  date: string
  description: string
  category: 'DeFi' | 'Trading Bots' | 'Web Apps' | 'Tools'
  tags: string[]
  featured: boolean
  published: boolean
  liveUrl?: string
  githubUrl?: string
}
 
async function renderProject(source: string) {
  const { content, frontmatter } = await compileMDX<ProjectFrontmatter>({
    source,
    components: mdxComponents,
    options: {
      parseFrontmatter: true,
      mdxOptions: {
        remarkPlugins: [remarkGfm],
        rehypePlugins: [rehypePrism, rehypeSlug],
      },
    },
  })
  return { content, frontmatter }
}

The API Layer: Hono Inside Next.js

For the dynamic features -- the contact form, page view tracking, and any future interactive elements -- I use Hono as the API framework, running as a Next.js route handler. Hono gives me express-like ergonomics with much better TypeScript support and a fraction of the bundle size. It also has excellent middleware for validation, CORS, and rate limiting.

import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { rateLimiter } from '@/lib/rate-limit'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
 
const api = new Hono().basePath('/api')
 
api.use('*', cors())
 
api.post(
  '/contact',
  rateLimiter({ windowMs: 60_000, max: 3 }),
  zValidator(
    'json',
    z.object({
      name: z.string().min(1).max(100),
      email: z.string().email(),
      message: z.string().min(10).max(2000),
    })
  ),
  async (c) => {
    const { name, email, message } = c.req.valid('json')
    await db.insert(contacts).values({ name, email, message })
    await sendNotificationEmail({ name, email, message })
    return c.json({ success: true }, 201)
  }
)
 
export const GET = api.fetch
export const POST = api.fetch

The database layer uses Drizzle ORM, which provides full type safety from schema definition through query execution. The schema is defined in TypeScript, and Drizzle generates the SQL migrations. This approach keeps the entire stack in a single language with end-to-end type safety.

Styling: Tailwind CSS with a Design System

I use Tailwind CSS with a set of custom design tokens defined in the config. The color palette supports light and dark modes through CSS custom properties, and the typography scale is tuned for readability of long-form technical content. Code blocks use a carefully chosen syntax theme that works well in both light and dark modes.

The component library is built on top of Radix UI primitives via shadcn/ui, which gives me accessible, unstyled components that I can customize with Tailwind. This approach avoids the common problem with component libraries where you spend more time fighting the library's opinions than building your own design.

Performance and Deployment

The site is deployed on Vercel with automatic builds triggered by Git pushes. Pages that can be statically generated are pre-rendered at build time, while dynamic routes use server-side rendering with caching. The MDX compilation happens at request time using React Server Components, which keeps the client bundle lean since the MDX processing code never ships to the browser.

// next.config.ts - Key configuration for MDX and performance
const config: NextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
  experimental: {
    optimizePackageImports: ['@radix-ui/react-icons', 'lucide-react'],
  },
}

Core Web Vitals are something I actively monitor and optimize for. The combination of static generation, minimal client JavaScript, optimized fonts and images, and Vercel's edge network results in consistently fast load times regardless of the visitor's location. It is not just vanity metrics -- a fast site is a better reading experience, and the technical content on this blog often attracts readers on slower connections from around the world.

On this page

  • Why MDX Over a CMS
  • The API Layer: Hono Inside Next.js
  • Styling: Tailwind CSS with a Design System
  • Performance and Deployment
Share:

Previous

Building Trading Bots in TypeScript: Lessons from Production

Next

When NOT to Rebalance: Regime Detection and EV-Based LP Decisions

Related Posts

Development
Taming Wormhole: Async Generators for Cross-Chain Bridge Transactions

How I integrated Wormhole bridge transactions that yield an unknown number of sequential steps via async generators. Covers the three-phase bridge lifecycle, calldata regeneration, and dual-lock coordination.

July 1, 2025
WormholeCross-ChainTypeScript
Trading Bots
Building Trading Bots in TypeScript: Lessons from Production

Hard-won lessons from building and running automated trading bots on Solana. Covers architecture patterns, error handling, and the operational concerns nobody talks about.

April 20, 2025
TypeScriptTradingSolana
DeFi & Solana
Getting Started with Solana DeFi: A Developer's Perspective

Practical lessons from building DeFi bots on Solana. Covers the account model, transaction patterns, real-time monitoring via WebSocket, and production pitfalls that documentation does not warn you about.

April 1, 2025
SolanaDeFiTypeScript