Blog

Building satanilabs.com with Next.js 16 and a writing hub

How I shipped the SataniLabs portfolio on Next.js 16 App Router—route groups, motion, SEO, the spider backdrop, and a markdown writing hub.

When I set out to rebuild satanilabs.com, I did not want another template portfolio with a hero image and three cards. The site needed to feel like a studio: deliberate typography, a dark editorial mood, motion that earns its place, and a place to publish the same kind of writing I do for clients. That combination pushed me toward Next.js 16 on the App Router, with route groups separating the immersive home experience from the calmer editorial surfaces where blog posts and insights live.

This article walks through the architecture choices, the motion layer, SEO defaults, the spider backdrop on the home route, and how the writing hub loads markdown from the repo without a CMS.

Why App Router and route groups

The App Router is not new, but Next.js 16 tightened the story around layouts, caching, and server components in ways that matter for a mostly-static marketing site with a growing content layer. I split the app into two route groups: an immersive group for the scroll-driven home page and an editorial group for /writing, /blog, and /insights.

Route groups let you mount different layouts without changing URLs. The immersive layout can own full-viewport chapters, a fixed nav, and WebGL-adjacent canvas layers. The editorial layout can switch to a reading width, simpler background, and typography tuned for long-form markdown. Both groups still share root metadata, fonts, and global CSS variables.

// app/(immersive)/layout.tsx — full-bleed chapters, spider layer
export default function ImmersiveLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <SpiderBackdrop />
      <Nav />
      {children}
    </>
  );
}

// app/(editorial)/layout.tsx — writing hub chrome
export default function EditorialLayout({ children }: { children: React.ReactNode }) {
  return <WritingLayout>{children}</WritingLayout>;
}

Keeping the home page in (immersive) and posts in (editorial) avoided the common mistake of bolting a blog onto a landing layout that was never meant for paragraphs longer than two lines.

Motion without stealing focus

Motion on a portfolio is easy to overdo. I wanted chapter reveals and subtle stagger on the writing hub, not a carousel of parallax gimmicks. The motion layer is built from small client components—Reveal wrappers that respect prefers-reduced-motion and only animate opacity and transform on elements that are already in the DOM for screen readers.

Stagger delays are indexed, not random, so tab order and visual order stay aligned. On the writing hub, the featured post card animates in after the section title and lede, which gives hierarchy without blocking first paint. For users who prefer reduced motion, animations collapse to instant visibility; there is no alternate broken layout.

SEO, metadata, and the sitemap

Studio sites often ship with a single title tag and hope Google figures out the rest. Here, every post exports metadata through a shared helper that maps frontmatter title, description, and date into Open Graph and Twitter fields. The root layout sets defaults; per-route generateMetadata overrides them for blog and insight slugs.

A programmatic sitemap.ts merges static routes with all markdown slugs discovered at build time. That matters because posts live in content/blog and content/insights—if a file exists and is not marked draft, it should appear in search indexes the same day it merges. Canonical URLs point at /blog/[slug] and /insights/[slug] so the writing hub acts as discovery, not a duplicate content trap.

The spider backdrop

The home page backdrop is a canvas-driven “spider” graph: nodes drift slowly, edges fade in when nodes are near enough, and the whole field sits behind content at low contrast. It is decorative, not interactive, which keeps pointer events on real UI and avoids trapping keyboard users in a canvas.

Performance-wise, the animation runs on requestAnimationFrame with a capped node count and pauses when the tab is hidden. On low-power devices, frame budget is protected by reducing link distance checks every other frame. The effect reads as technical texture—on-brand for SataniLabs—without becoming a second product to maintain.

The writing hub and markdown pipeline

I did not want a headless CMS for eight to twenty articles. Posts are markdown files with YAML frontmatter parsed by gray-matter at build time. A small posts.ts module lists slugs per kind (blog | insight), sorts by date, and computes reading time from word count.

// Simplified: one featured post drives the writing hub hero
export function getFeaturedPost(): Post | null {
  const featured = getAllPosts().find((p) => p.featured);
  return featured ?? getAllPosts()[0] ?? null;
}

The /writing route shows the featured piece, cross-links to blog and insights indexes, and uses the same PostCard component everywhere for consistency. Related posts are naive today—same kind, exclude current slug, slice three—but that is enough until tag-based ranking is worth the complexity.

What I would do differently next time

I would extract frontmatter validation earlier. Zod schemas for kind, date, and description length catch typos before deploy. I would also snapshot visual regression on the immersive home once the spider parameters stabilize, because canvas tuning is easy to break accidentally.

Shipping satanilabs.com was less about chasing a framework version number and more about drawing a hard line between spectacle and reading. Next.js 16 gave me route groups and metadata APIs that made that line explicit in the filesystem—where the next article, case study, or product note can land without redesigning the whole site.