Compare commits
10 Commits
content/we
...
fix/chunk-
| Author | SHA1 | Date | |
|---|---|---|---|
| ede34ab7ce | |||
| 3fac158185 | |||
| 001c8514b5 | |||
| 00a8e3248f | |||
| 9951a357ac | |||
| 3395e6ebf8 | |||
| 55c25ca203 | |||
| 53808c416a | |||
| 9dad18daa5 | |||
| d4b8c1ce4a |
@@ -167,6 +167,9 @@ jobs:
|
|||||||
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
|
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
|
||||||
|
|
||||||
# Comprehensive Docker purge
|
# Comprehensive Docker purge
|
||||||
|
docker buildx stop || true
|
||||||
|
docker buildx rm -a || true
|
||||||
|
docker rm -fv $(docker ps -aq --filter "ancestor=moby/buildkit") || true
|
||||||
docker system prune -af --volumes
|
docker system prune -af --volumes
|
||||||
docker builder prune -af
|
docker builder prune -af
|
||||||
docker buildx prune -af
|
docker buildx prune -af
|
||||||
@@ -241,8 +244,6 @@ jobs:
|
|||||||
DIRECTUS_URL: ${{ needs.prepare.outputs.directus_url }}
|
DIRECTUS_URL: ${{ needs.prepare.outputs.directus_url }}
|
||||||
DIRECTUS_HOST: cms.${{ needs.prepare.outputs.traefik_host }}
|
DIRECTUS_HOST: cms.${{ needs.prepare.outputs.traefik_host }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Mail
|
# Mail
|
||||||
MAIL_HOST: ${{ secrets.SMTP_HOST || vars.SMTP_HOST }}
|
MAIL_HOST: ${{ secrets.SMTP_HOST || vars.SMTP_HOST }}
|
||||||
MAIL_PORT: ${{ secrets.SMTP_PORT || vars.SMTP_PORT || '587' }}
|
MAIL_PORT: ${{ secrets.SMTP_PORT || vars.SMTP_PORT || '587' }}
|
||||||
|
|||||||
@@ -15,6 +15,22 @@ export default function Error({
|
|||||||
// Log the error to Sentry/GlitchTip
|
// Log the error to Sentry/GlitchTip
|
||||||
Sentry.captureException(error);
|
Sentry.captureException(error);
|
||||||
console.error("Caught in error.tsx:", error);
|
console.error("Caught in error.tsx:", error);
|
||||||
|
|
||||||
|
// Auto-reload on ChunkLoadError or Server Action errors to fetch the new deployment assets
|
||||||
|
const isChunkLoadError =
|
||||||
|
error.message &&
|
||||||
|
(error.message.includes("ChunkLoadError") ||
|
||||||
|
error.message.includes("Loading chunk") ||
|
||||||
|
error.message.includes("Failed to find Server Action"));
|
||||||
|
|
||||||
|
if (isChunkLoadError) {
|
||||||
|
const lastReload = sessionStorage.getItem("last-chunk-reload");
|
||||||
|
const now = Date.now();
|
||||||
|
if (!lastReload || now - parseInt(lastReload) > 10000) {
|
||||||
|
sessionStorage.setItem("last-chunk-reload", now.toString());
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
import Error from "next/error";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Inter, Newsreader } from "next/font/google";
|
import { Inter, Newsreader } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
@@ -23,6 +22,22 @@ export default function GlobalError({
|
|||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Sentry.captureException(error);
|
Sentry.captureException(error);
|
||||||
|
|
||||||
|
// Auto-reload on ChunkLoadError or Server Action errors to fetch the new deployment assets
|
||||||
|
const isChunkLoadError =
|
||||||
|
error.message &&
|
||||||
|
(error.message.includes("ChunkLoadError") ||
|
||||||
|
error.message.includes("Loading chunk") ||
|
||||||
|
error.message.includes("Failed to find Server Action"));
|
||||||
|
|
||||||
|
if (isChunkLoadError) {
|
||||||
|
const lastReload = sessionStorage.getItem("last-chunk-reload");
|
||||||
|
const now = Date.now();
|
||||||
|
if (!lastReload || now - parseInt(lastReload) > 10000) {
|
||||||
|
sessionStorage.setItem("last-chunk-reload", now.toString());
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
30
apps/web/app/api/debug-mdx/route.ts
Normal file
30
apps/web/app/api/debug-mdx/route.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const POSTS_DIR = path.join(process.cwd(), "content/blog");
|
||||||
|
let files = [];
|
||||||
|
if (fs.existsSync(POSTS_DIR)) {
|
||||||
|
files = fs.readdirSync(POSTS_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revalidate the two problematic pages
|
||||||
|
revalidatePath("/blog/website-as-a-service");
|
||||||
|
revalidatePath("/blog/zero-overhead-agencies");
|
||||||
|
revalidatePath("/blog"); // also bust the blog list cache
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
cwd: process.cwd(),
|
||||||
|
postsDir: POSTS_DIR,
|
||||||
|
postsDirExists: fs.existsSync(POSTS_DIR),
|
||||||
|
files: files,
|
||||||
|
message: "Cache revalidated for specific paths",
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
return NextResponse.json({ success: false, error: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
25
apps/web/app/debug-mdx/route.ts
Normal file
25
apps/web/app/debug-mdx/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
|
import { revalidatePath } from "next/cache";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const searchParams = request.nextUrl.searchParams;
|
||||||
|
const pathParam = searchParams.get("path") || "/";
|
||||||
|
const typeParam =
|
||||||
|
(searchParams.get("type") as "page" | "layout") || "layout";
|
||||||
|
|
||||||
|
// Revalidate the requested path (defaults to entire app via layout)
|
||||||
|
revalidatePath(pathParam, typeParam);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
revalidatedPath: pathParam,
|
||||||
|
revalidatedType: typeParam,
|
||||||
|
message: `Cache revalidated for ${pathParam} (${typeParam})`,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
return NextResponse.json({ success: false, error: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,13 +28,7 @@ tags: ["business", "management", "strategy"]
|
|||||||
Dazu kommt das <Marker>Opportunitätsrisiko</Marker>: Was hätte das Unternehmen in diesen fünf Stunden erreichen können? Ein entscheidendes Verkaufsgespräch mehr? Eine strategische Prozessoptimierung? Die Verwaltung von IT-Infrastruktur sollte niemals die Kernaufgabe eines Nicht-IT-Unternehmens sein.
|
Dazu kommt das <Marker>Opportunitätsrisiko</Marker>: Was hätte das Unternehmen in diesen fünf Stunden erreichen können? Ein entscheidendes Verkaufsgespräch mehr? Eine strategische Prozessoptimierung? Die Verwaltung von IT-Infrastruktur sollte niemals die Kernaufgabe eines Nicht-IT-Unternehmens sein.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<ArticleQuote
|
<ArticleQuote quote="The most expensive software is the one that requires your constant attention to keep it running safely." author="Digital Economics" isCompany={true} source="SaaS Industry Report" translated={false} />
|
||||||
quote="The most expensive software is the one that requires your constant attention to keep it running safely."
|
|
||||||
author="Digital Economics"
|
|
||||||
isCompany={true}
|
|
||||||
source="SaaS Industry Report"
|
|
||||||
translated={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<H2>Website as a Service: Das Ende des Overheads</H2>
|
<H2>Website as a Service: Das Ende des Overheads</H2>
|
||||||
|
|
||||||
@@ -72,14 +66,7 @@ tags: ["business", "management", "strategy"]
|
|||||||
In einem WaaS-Modell übernehme ich als Entwickler nicht nur die inhaltlichen Änderungen, sondern baue die Seite von Grund auf auf einer modernen, statischen Architektur (Jamstack / Next.js). Das bedeutet: Die Seite wird im Vorfeld generiert. Es gibt keine Datenbank, die live am Netz hängt und gehackt werden könnte.
|
In einem WaaS-Modell übernehme ich als Entwickler nicht nur die inhaltlichen Änderungen, sondern baue die Seite von Grund auf auf einer modernen, statischen Architektur (Jamstack / Next.js). Das bedeutet: Die Seite wird im Vorfeld generiert. Es gibt keine Datenbank, die live am Netz hängt und gehackt werden könnte.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<ComparisonRow
|
<ComparisonRow description="Wie unterscheidet sich WaaS von klassischem Hosting?" negativeLabel="Klassisches Hosting & CMS" negativeText="Sie sind selbst für Updates, Backups und Plugin-Kompatibilität verantwortlich. Die Seite ist ein Sicherheitsrisiko und veraltet technologisch schnell." positiveLabel="Sorglos-Paket (WaaS)" positiveText="Proaktives Monitoring, unsichtbare Updates und sofortige strukturelle Anpassungen durch einen Experten. Kein Stress, absolute Sicherheit und volle redaktionelle Kontrolle." showShare={true} />
|
||||||
description="Wie unterscheidet sich WaaS von klassischem Hosting?"
|
|
||||||
negativeLabel="Klassisches Hosting & CMS"
|
|
||||||
negativeText="Sie sind selbst für Updates, Backups und Plugin-Kompatibilität verantwortlich. Die Seite ist ein Sicherheitsrisiko und veraltet technologisch schnell."
|
|
||||||
positiveLabel="Sorglos-Paket (WaaS)"
|
|
||||||
positiveText="Proaktives Monitoring, unsichtbare Updates und sofortige strukturelle Anpassungen durch einen Experten. Kein Stress, absolute Sicherheit und volle redaktionelle Kontrolle."
|
|
||||||
showShare={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
Das Ergebnis: Die Seite kann faktisch nicht gehackt werden. [Updates und Wartung](/blog/why-websites-break-after-updates) laufen unsichtbar im Hintergrund ab, und die Ladezeiten sind extrem optimiert, da nur statische Dateien (HTML/CSS/JS) vom Server ausgeliefert werden.
|
Das Ergebnis: Die Seite kann faktisch nicht gehackt werden. [Updates und Wartung](/blog/why-websites-break-after-updates) laufen unsichtbar im Hintergrund ab, und die Ladezeiten sind extrem optimiert, da nur statische Dateien (HTML/CSS/JS) vom Server ausgeliefert werden.
|
||||||
@@ -97,13 +84,7 @@ tags: ["business", "management", "strategy"]
|
|||||||
Diese <Marker>Budgetsicherheit</Marker> ist für B2B-Unternehmen extrem wertvoll. Unvorhergesehene Serverausfälle oder nötige Sicherheits-Patches sind nicht mehr Ihr finanzielles Risiko, sondern mein technisches. Es liegt also in meinem absoluten Eigeninteresse, die Architektur von Tag eins an so robust und fehlerfrei wie möglich zu bauen. Das ist das Prinzip des [strategischen Festpreises](/blog/fixed-price-digital-projects).
|
Diese <Marker>Budgetsicherheit</Marker> ist für B2B-Unternehmen extrem wertvoll. Unvorhergesehene Serverausfälle oder nötige Sicherheits-Patches sind nicht mehr Ihr finanzielles Risiko, sondern mein technisches. Es liegt also in meinem absoluten Eigeninteresse, die Architektur von Tag eins an so robust und fehlerfrei wie möglich zu bauen. Das ist das Prinzip des [strategischen Festpreises](/blog/fixed-price-digital-projects).
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<LeadMagnet
|
<LeadMagnet title="Bereit für Zero Overhead?" description="Lassen Sie uns Ihre aktuelle Website analysieren. Ich zeige Ihnen, wie wir den Aufwand für Sie auf null reduzieren können." buttonText="Sorglos-Paket anfragen" href="/contact" variant="standard" />
|
||||||
title="Bereit für Zero Overhead?"
|
|
||||||
description="Lassen Sie uns Ihre aktuelle Website analysieren. Ich zeige Ihnen, wie wir den Aufwand für Sie auf null reduzieren können."
|
|
||||||
buttonText="Sorglos-Paket anfragen"
|
|
||||||
href="/contact"
|
|
||||||
variant="standard"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<H2>Fazit: Kaufen Sie Zeit, keine Software</H2>
|
<H2>Fazit: Kaufen Sie Zeit, keine Software</H2>
|
||||||
|
|
||||||
|
|||||||
@@ -28,13 +28,7 @@ tags: ["management", "strategy", "efficiency"]
|
|||||||
Das Problem dabei ist nicht nur der aufgeblähte Preis, sondern die **Geschwindigkeit**. Wenn Sie eine einfache inhaltliche oder funktionale Anpassung anfordern, geht die E-Mail an den Projektmanager. Dieser schreibt ein Ticket für den Designer. Der Designer macht einen Entwurf. Der Projektmanager schickt Ihnen den Entwurf. Sie geben Feedback. Das Ticket geht zurück zum Designer, dann zum Entwickler, dann in die QA, dann wieder zu Ihnen.
|
Das Problem dabei ist nicht nur der aufgeblähte Preis, sondern die **Geschwindigkeit**. Wenn Sie eine einfache inhaltliche oder funktionale Anpassung anfordern, geht die E-Mail an den Projektmanager. Dieser schreibt ein Ticket für den Designer. Der Designer macht einen Entwurf. Der Projektmanager schickt Ihnen den Entwurf. Sie geben Feedback. Das Ticket geht zurück zum Designer, dann zum Entwickler, dann in die QA, dann wieder zu Ihnen.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<ArticleQuote
|
<ArticleQuote quote="Every layer of communication halves the precision and doubles the time required to execute a task. In software, middle management is often a bug, not a feature." author="Lean Engineering Principles" isCompany={false} source="The Minimalist Developer" translated={false} />
|
||||||
quote="Every layer of communication halves the precision and doubles the time required to execute a task. In software, middle management is often a bug, not a feature."
|
|
||||||
author="Lean Engineering Principles"
|
|
||||||
isCompany={false}
|
|
||||||
source="The Minimalist Developer"
|
|
||||||
translated={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<H2>Der Lean-Ansatz: Ein Ansprechpartner, volle Verantwortung</H2>
|
<H2>Der Lean-Ansatz: Ein Ansprechpartner, volle Verantwortung</H2>
|
||||||
|
|
||||||
@@ -72,14 +66,7 @@ tags: ["management", "strategy", "efficiency"]
|
|||||||
Wenn man den Overhead eliminiert, kann man auf [hochperformante, moderne Stacks (Jamstack, Next.js)](/blog/digital-longevity-architecture) setzen. Diese wären für ein heterogenes Team oft zu komplex im Setup, können von einem Senior-Entwickler aber in einem Bruchteil der Zeit fehlerfrei und in industrieller Qualität gebaut werden.
|
Wenn man den Overhead eliminiert, kann man auf [hochperformante, moderne Stacks (Jamstack, Next.js)](/blog/digital-longevity-architecture) setzen. Diese wären für ein heterogenes Team oft zu komplex im Setup, können von einem Senior-Entwickler aber in einem Bruchteil der Zeit fehlerfrei und in industrieller Qualität gebaut werden.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<ComparisonRow
|
<ComparisonRow description="Wofür bezahlen Sie wirklich?" negativeLabel="Klassische Agentur" negativeText="Sie finanzieren Meetings, interne Abstimmungen, Projektmanagement-Tools, Büromieten und Junior-Gehälter." positiveLabel="Lean Engineer (Zero Overhead)" positiveText="Sie investieren 100% Ihres Budgets in pure Code-Qualität, High-End Design und technologische Langlebigkeit." showShare={true} />
|
||||||
description="Wofür bezahlen Sie wirklich?"
|
|
||||||
negativeLabel="Klassische Agentur"
|
|
||||||
negativeText="Sie finanzieren Meetings, interne Abstimmungen, Projektmanagement-Tools, Büromieten und Junior-Gehälter."
|
|
||||||
positiveLabel="Lean Engineer (Zero Overhead)"
|
|
||||||
positiveText="Sie investieren 100% Ihres Budgets in pure Code-Qualität, High-End Design und technologische Langlebigkeit."
|
|
||||||
showShare={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StatsGrid stats="60%|Weniger Meetings|durch direkten Kontakt~3x|Schnellere Umsetzung|durch fehlende Feedback-Schleifen~100%|Verantwortung|bei einer einzigen Person" />
|
<StatsGrid stats="60%|Weniger Meetings|durch direkten Kontakt~3x|Schnellere Umsetzung|durch fehlende Feedback-Schleifen~100%|Verantwortung|bei einer einzigen Person" />
|
||||||
|
|
||||||
@@ -103,13 +90,7 @@ tags: ["management", "strategy", "efficiency"]
|
|||||||
Aber für 90% der B2B-Unternehmen, die eine hochprofessionelle, blitzschnelle und zukunftssichere Plattform brauchen, um digitale Dominanz auszustrahlen und Leads zu generieren? Dafür ist eine Agentur meistens Overkill – und ein massiver Rendite-Killer.
|
Aber für 90% der B2B-Unternehmen, die eine hochprofessionelle, blitzschnelle und zukunftssichere Plattform brauchen, um digitale Dominanz auszustrahlen und Leads zu generieren? Dafür ist eine Agentur meistens Overkill – und ein massiver Rendite-Killer.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<LeadMagnet
|
<LeadMagnet title="Machen wir es unkompliziert." description="Lassen Sie uns den administrativen Overhead streichen. In einem kurzen Call klären wir die Fakten, danach erhalten Sie ein glasklares Angebot." buttonText="Jetzt Blueprint-Call anfragen" href="/contact" variant="standard" />
|
||||||
title="Machen wir es unkompliziert."
|
|
||||||
description="Lassen Sie uns den administrativen Overhead streichen. In einem kurzen Call klären wir die Fakten, danach erhalten Sie ein glasklares Angebot."
|
|
||||||
buttonText="Jetzt Blueprint-Call anfragen"
|
|
||||||
href="/contact"
|
|
||||||
variant="standard"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FAQSection>
|
<FAQSection>
|
||||||
<H3>Was passiert, wenn der alleinige Entwickler ausfällt?</H3>
|
<H3>Was passiert, wenn der alleinige Entwickler ausfällt?</H3>
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
// In Standalone mode, Next.js expects the tracing root to be the monorepo root
|
// In Standalone mode, Next.js expects the tracing root to be the monorepo root
|
||||||
outputFileTracingRoot: path.join(dirname, '../../'),
|
outputFileTracingRoot: path.join(dirname, '../../'),
|
||||||
|
outputFileTracingIncludes: {
|
||||||
|
'/(site)/blog/[slug]': ['./content/blog/**/*'],
|
||||||
|
'/blog/[slug]': ['./content/blog/**/*'],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const withMDX = createMDX({});
|
const withMDX = createMDX({});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useState, useMemo, useEffect, useRef } from "react";
|
import { useState, useMemo, useEffect, useRef } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import * as confetti from "canvas-confetti";
|
import confetti from "canvas-confetti";
|
||||||
import {
|
import {
|
||||||
Layers,
|
Layers,
|
||||||
BrainCircuit,
|
BrainCircuit,
|
||||||
|
|||||||
Reference in New Issue
Block a user