276 lines
10 KiB
Markdown
276 lines
10 KiB
Markdown
---
|
|
title: "Building a Zero-Backend Privacy-First Micro-SaaS Decision Suite with Next.js 15, App Router & Web Workers"
|
|
description: "How to build a zero-server privacy-first micro-SaaS suite using Next.js 15 SSG, Web Workers, and automated Open Graph dynamic metadata."
|
|
tags: ["nextjs", "react", "typescript", "webdev"]
|
|
canonical_url: "https://entscheidomat.com/ratgeber/zufallsgenerator-richtig-nutzen"
|
|
target_keywords: ["entscheidungshilfe online", "entscheidungsfinder", "zufallsgenerator online", "nextjs 15 app router", "web workers react"]
|
|
---
|
|
|
|
# Building a Zero-Backend Privacy-First Micro-SaaS Decision Suite with Next.js 15, App Router & Web Workers
|
|
|
|
Building and scaling modern web applications often involves managing complex server infrastructure: database connections, user authentication, server-side API rate limiting, and monthly hosting bills.
|
|
|
|
However, for utilities like decision tools, random generators, or productivity suites (such as [Entscheidomat](https://entscheidomat.com)), a **Zero-Backend Client-First Architecture** offers immense benefits:
|
|
1. **$0 Hosting Infrastructure Costs:** The application compiles to static HTML/JS/CSS assets deployed to global CDNs (Vercel, Cloudflare Pages, Netlify).
|
|
2. **100% GDPR & Privacy Compliance:** User data (lists, names, decision options) never leaves the browser. Zero data server transmission.
|
|
3. **Instant Performance:** Near 100/100 Google Lighthouse scores with sub-second page loads.
|
|
|
|
In this article, we will examine how to architect a privacy-first Micro-SaaS decision suite using **Next.js 15 App Router**, **Static Site Generation (SSG)**, **Web Workers** for heavy computation, and **Dynamic Open Graph image generation**.
|
|
|
|
---
|
|
|
|
## 1. Architecture Overview: Zero-Backend Client Suite
|
|
|
|
In a zero-backend architecture, the browser handles 100% of data persistence (via `localStorage` and `IndexedDB`) and computation.
|
|
|
|
```text
|
|
┌────────────────────────────────────────────────────────────────────────┐
|
|
│ Next.js 15 App Router (SSG) │
|
|
├─────────────────────────┬───────────────────┬──────────────────────────┤
|
|
│ Static Page Engine │ Web Workers Engine│ Dynamic Open Graph (@og) │
|
|
│ (Next.js HTML/CSS) │ (Heavy PRNG Tasks)│ (Social Share Previews) │
|
|
└────────────┬────────────┴─────────┬─────────┴─────────────┬────────────┘
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────────┐ ┌──────────────────┐ ┌────────────────────┐
|
|
│ Browser Storage │ │ Web Crypto API │ │ Social Networks │
|
|
│ (localStorage) │ │ (PRNG Engine) │ │ (Twitter/LinkedIn) │
|
|
└──────────────────┘ └──────────────────┘ └────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Offloading Heavy Computation to Web Workers in React
|
|
|
|
If a user imports a list of 50,000 items to shuffle or run Monte Carlo simulations on, executing the calculation on React's main thread will lock the UI, dropping frames and causing unresponsive UI lag.
|
|
|
|
We solve this by offloading computation to a **Web Worker**:
|
|
|
|
### `worker/shuffle.worker.ts`
|
|
```typescript
|
|
// Web Worker for background array shuffling and simulations
|
|
ctx.addEventListener("message", (event: MessageEvent<{ items: string[] }>) => {
|
|
const { items } = event.data;
|
|
const result = [...items];
|
|
|
|
// Fisher-Yates Shuffle inside Web Worker
|
|
const buffer = new Uint32Array(1);
|
|
for (let i = result.length - 1; i > 0; i--) {
|
|
crypto.getRandomValues(buffer);
|
|
const j = Math.floor((buffer[0] / (0xFFFFFFFF + 1)) * (i + 1));
|
|
const temp = result[i];
|
|
result[i] = result[j];
|
|
result[j] = temp;
|
|
}
|
|
|
|
// Send shuffled result back to main thread
|
|
ctx.postMessage({ shuffled: result });
|
|
});
|
|
|
|
export {};
|
|
```
|
|
|
|
### React Custom Hook: `useShuffleWorker.ts`
|
|
```typescript
|
|
import { useState, useEffect, useCallback } from "react";
|
|
|
|
export function useShuffleWorker() {
|
|
const [worker, setWorker] = useState<Worker | null>(null);
|
|
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
|
|
|
useEffect(() => {
|
|
// Instantiate Web Worker on component mount
|
|
const w = new Worker(new URL("../worker/shuffle.worker.ts", import.meta.url));
|
|
setWorker(w);
|
|
|
|
return () => {
|
|
w.terminate();
|
|
};
|
|
}, []);
|
|
|
|
const processShuffle = useCallback((items: string[]): Promise<string[]> => {
|
|
return new Promise((resolve) => {
|
|
if (!worker) return resolve(items);
|
|
|
|
setIsProcessing(true);
|
|
|
|
const handleMessage = (e: MessageEvent<{ shuffled: string[] }>) => {
|
|
setIsProcessing(false);
|
|
worker.removeEventListener("message", handleMessage);
|
|
resolve(e.data.shuffled);
|
|
};
|
|
|
|
worker.addEventListener("message", handleMessage);
|
|
worker.postMessage({ items });
|
|
});
|
|
}, [worker]);
|
|
|
|
return { processShuffle, isProcessing };
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Automated Open Graph Image Generation in Next.js 15
|
|
|
|
To drive organic viral traffic from social media shares (Twitter, LinkedIn, WhatsApp), every page must output custom dynamic Open Graph banner images containing the page title and tool parameters.
|
|
|
|
Using Next.js 15 `@vercel/og` (`ImageResponse` API), we generate dynamic social cards on the fly:
|
|
|
|
### `app/ratgeber/[slug]/opengraph-image.tsx`
|
|
```typescript
|
|
import { ImageResponse } from "next/og";
|
|
import { getGuide } from "@/lib/guides";
|
|
|
|
export const runtime = "edge";
|
|
export const alt = "Entscheidomat Ratgeber";
|
|
export const size = { width: 1200, height: 630 };
|
|
export const contentType = "image/png";
|
|
|
|
export default async function Image({ params }: { params: { slug: string } }) {
|
|
const guide = getGuide(params.slug);
|
|
const title = guide ? guide.title : "Entscheidomat Ratgeber";
|
|
|
|
return new ImageResponse(
|
|
(
|
|
<div
|
|
style={{
|
|
background: "linear-gradient(135deg, #101114 0%, #1c2237 100%)",
|
|
width: "100%",
|
|
height: "100%",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "flex-start",
|
|
justifyContent: "center",
|
|
padding: "60px 80px",
|
|
color: "#ffffff",
|
|
fontFamily: "sans-serif",
|
|
}}
|
|
>
|
|
{/* Brand Tag */}
|
|
<div
|
|
style={{
|
|
fontSize: 20,
|
|
fontWeight: 700,
|
|
letterSpacing: "0.15em",
|
|
color: "#7d97ff",
|
|
textTransform: "uppercase",
|
|
marginBottom: 24,
|
|
}}
|
|
>
|
|
ENTSCHEIDOMAT · RATGEBER
|
|
</div>
|
|
|
|
{/* Dynamic Title */}
|
|
<div
|
|
style={{
|
|
fontSize: 48,
|
|
fontWeight: 800,
|
|
lineHeight: 1.25,
|
|
maxWidth: "900px",
|
|
color: "#eceef2",
|
|
}}
|
|
>
|
|
{title}
|
|
</div>
|
|
|
|
{/* Footer Domain Badge */}
|
|
<div
|
|
style={{
|
|
marginTop: 40,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "12px",
|
|
fontSize: 22,
|
|
color: "#7b818c",
|
|
}}
|
|
>
|
|
<span>entscheidomat.com</span>
|
|
</div>
|
|
</div>
|
|
),
|
|
{ ...size }
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Next.js 15 Metadata & Canonical URL Configuration
|
|
|
|
To ensure maximum organic search engine rankings, every route must export proper SEO metadata tags with cross-domain canonical URLs:
|
|
|
|
```typescript
|
|
import type { Metadata } from "next";
|
|
|
|
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
|
|
const guide = getGuide(params.slug);
|
|
|
|
if (!guide) {
|
|
return { title: "Not Found" };
|
|
}
|
|
|
|
const canonicalUrl = `https://entscheidomat.com/ratgeber/${guide.slug}`;
|
|
|
|
return {
|
|
title: `${guide.title} | Entscheidomat`,
|
|
description: guide.description,
|
|
alternates: {
|
|
canonical: canonicalUrl,
|
|
},
|
|
openGraph: {
|
|
title: guide.title,
|
|
description: guide.description,
|
|
url: canonicalUrl,
|
|
siteName: "Entscheidomat",
|
|
locale: "de_DE",
|
|
type: "article",
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: guide.title,
|
|
description: guide.description,
|
|
},
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Summary & Key Takeaways
|
|
|
|
1. **Zero-Backend Architecture:** Compiling to static HTML (SSG) delivers $0$ infrastructure cost, 100% GDPR compliance, and sub-second page loading speeds.
|
|
2. **Web Workers:** Keep React main threads smooth at 60 FPS by executing computational tasks (array shuffles, Monte Carlo simulations) in Web Workers.
|
|
3. **Dynamic Open Graph Images:** Use `@vercel/og` in Next.js 15 to automatically synthesize 1200x630 social preview banners.
|
|
|
|
Explore a zero-backend decision suite live on [Entscheidomat](https://entscheidomat.com).
|
|
|
|
---
|
|
|
|
## FAQ (Schema Structured Data)
|
|
|
|
```json
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@type": "FAQPage",
|
|
"mainEntity": [
|
|
{
|
|
"@type": "Question",
|
|
"name": "What is a Zero-Backend Micro-SaaS architecture?",
|
|
"acceptedAnswer": {
|
|
"@type": "Answer",
|
|
"text": "A zero-backend web architecture compiles the app into static client assets (SSG) where 100% of state and logic executes inside the user's browser, eliminating server hosting costs and GDPR risks."
|
|
}
|
|
},
|
|
{
|
|
"@type": "Question",
|
|
"name": "Why use Web Workers in Next.js applications?",
|
|
"acceptedAnswer": {
|
|
"@type": "Answer",
|
|
"text": "Web Workers offload heavy computations to background browser threads, preventing UI freeze and maintaining smooth 60 FPS user interaction."
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|