feat(blog): add file-based blog with dynamic slugs, MDX content and layout shell

- Introduced blog routing using Next.js App Router
- Implemented dynamic [slug] pages for blog posts
- Added MDX-based content loading via lib/posts
- Integrated shared TopBar layout with navigation
- Established clear content, lib and component separation
This commit is contained in:
PascalSchattenburg
2026-01-22 14:14:15 +01:00
parent b717952234
commit d147843c76
10412 changed files with 2475583 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
/** An error that should be thrown when we want to bail out to client-side rendering. */
export declare class BailoutToCSRError extends Error {
readonly reason: string;
readonly digest = "BAILOUT_TO_CLIENT_SIDE_RENDERING";
constructor(reason: string);
}
/** Checks if a passed argument is an error that is thrown if we want to bail out to client-side rendering. */
export declare function isBailoutToCSRError(err: unknown): err is BailoutToCSRError;

View File

@@ -0,0 +1,37 @@
// This has to be a shared module which is shared between client component error boundary and dynamic component
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
BailoutToCSRError: null,
isBailoutToCSRError: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
BailoutToCSRError: function() {
return BailoutToCSRError;
},
isBailoutToCSRError: function() {
return isBailoutToCSRError;
}
});
const BAILOUT_TO_CSR = 'BAILOUT_TO_CLIENT_SIDE_RENDERING';
class BailoutToCSRError extends Error {
constructor(reason){
super(`Bail out to client-side rendering: ${reason}`), this.reason = reason, this.digest = BAILOUT_TO_CSR;
}
}
function isBailoutToCSRError(err) {
if (typeof err !== 'object' || err === null || !('digest' in err)) {
return false;
}
return err.digest === BAILOUT_TO_CSR;
}
//# sourceMappingURL=bailout-to-csr.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/shared/lib/lazy-dynamic/bailout-to-csr.ts"],"sourcesContent":["// This has to be a shared module which is shared between client component error boundary and dynamic component\nconst BAILOUT_TO_CSR = 'BAILOUT_TO_CLIENT_SIDE_RENDERING'\n\n/** An error that should be thrown when we want to bail out to client-side rendering. */\nexport class BailoutToCSRError extends Error {\n public readonly digest = BAILOUT_TO_CSR\n\n constructor(public readonly reason: string) {\n super(`Bail out to client-side rendering: ${reason}`)\n }\n}\n\n/** Checks if a passed argument is an error that is thrown if we want to bail out to client-side rendering. */\nexport function isBailoutToCSRError(err: unknown): err is BailoutToCSRError {\n if (typeof err !== 'object' || err === null || !('digest' in err)) {\n return false\n }\n\n return err.digest === BAILOUT_TO_CSR\n}\n"],"names":["BailoutToCSRError","isBailoutToCSRError","BAILOUT_TO_CSR","Error","constructor","reason","digest","err"],"mappings":"AAAA,+GAA+G;;;;;;;;;;;;;;;;IAIlGA,iBAAiB;eAAjBA;;IASGC,mBAAmB;eAAnBA;;;AAZhB,MAAMC,iBAAiB;AAGhB,MAAMF,0BAA0BG;IAGrCC,YAAY,AAAgBC,MAAc,CAAE;QAC1C,KAAK,CAAC,CAAC,mCAAmC,EAAEA,QAAQ,QAD1BA,SAAAA,aAFZC,SAASJ;IAIzB;AACF;AAGO,SAASD,oBAAoBM,GAAY;IAC9C,IAAI,OAAOA,QAAQ,YAAYA,QAAQ,QAAQ,CAAE,CAAA,YAAYA,GAAE,GAAI;QACjE,OAAO;IACT;IAEA,OAAOA,IAAID,MAAM,KAAKJ;AACxB","ignoreList":[0]}

View File

@@ -0,0 +1,11 @@
import type { ReactElement } from 'react';
interface BailoutToCSRProps {
reason: string;
children: ReactElement;
}
/**
* If rendered on the server, this component throws an error
* to signal Next.js that it should bail out to client-side rendering instead.
*/
export declare function BailoutToCSR({ reason, children }: BailoutToCSRProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
export {};

View File

@@ -0,0 +1,24 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "BailoutToCSR", {
enumerable: true,
get: function() {
return BailoutToCSR;
}
});
const _bailouttocsr = require("./bailout-to-csr");
function BailoutToCSR({ reason, children }) {
if (typeof window === 'undefined') {
throw Object.defineProperty(new _bailouttocsr.BailoutToCSRError(reason), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
}
return children;
}
//# sourceMappingURL=dynamic-bailout-to-csr.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactElement } from 'react'\nimport { BailoutToCSRError } from './bailout-to-csr'\n\ninterface BailoutToCSRProps {\n reason: string\n children: ReactElement\n}\n\n/**\n * If rendered on the server, this component throws an error\n * to signal Next.js that it should bail out to client-side rendering instead.\n */\nexport function BailoutToCSR({ reason, children }: BailoutToCSRProps) {\n if (typeof window === 'undefined') {\n throw new BailoutToCSRError(reason)\n }\n\n return children\n}\n"],"names":["BailoutToCSR","reason","children","window","BailoutToCSRError"],"mappings":"AAAA;;;;;+BAcgBA;;;eAAAA;;;8BAXkB;AAW3B,SAASA,aAAa,EAAEC,MAAM,EAAEC,QAAQ,EAAqB;IAClE,IAAI,OAAOC,WAAW,aAAa;QACjC,MAAM,qBAA6B,CAA7B,IAAIC,+BAAiB,CAACH,SAAtB,qBAAA;mBAAA;wBAAA;0BAAA;QAA4B;IACpC;IAEA,OAAOC;AACT","ignoreList":[0]}

View File

@@ -0,0 +1,12 @@
import type { ComponentModule } from './types';
interface LoadableOptions {
loader?: () => Promise<React.ComponentType<any> | ComponentModule<any>>;
loading?: React.ComponentType<any> | null;
ssr?: boolean;
modules?: string[];
}
declare function Loadable(options: LoadableOptions): {
(props: any): import("react/jsx-runtime").JSX.Element;
displayName: string;
};
export default Loadable;

View File

@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return _default;
}
});
const _jsxruntime = require("react/jsx-runtime");
const _react = require("react");
const _dynamicbailouttocsr = require("./dynamic-bailout-to-csr");
const _preloadchunks = require("./preload-chunks");
// Normalize loader to return the module as form { default: Component } for `React.lazy`.
// Also for backward compatible since next/dynamic allows to resolve a component directly with loader
// Client component reference proxy need to be converted to a module.
function convertModule(mod) {
// Check "default" prop before accessing it, as it could be client reference proxy that could break it reference.
// Cases:
// mod: { default: Component }
// mod: Component
// mod: { default: proxy(Component) }
// mod: proxy(Component)
const hasDefault = mod && 'default' in mod;
return {
default: hasDefault ? mod.default : mod
};
}
const defaultOptions = {
loader: ()=>Promise.resolve(convertModule(()=>null)),
loading: null,
ssr: true
};
function Loadable(options) {
const opts = {
...defaultOptions,
...options
};
const Lazy = /*#__PURE__*/ (0, _react.lazy)(()=>opts.loader().then(convertModule));
const Loading = opts.loading;
function LoadableComponent(props) {
const fallbackElement = Loading ? /*#__PURE__*/ (0, _jsxruntime.jsx)(Loading, {
isLoading: true,
pastDelay: true,
error: null
}) : null;
// If it's non-SSR or provided a loading component, wrap it in a suspense boundary
const hasSuspenseBoundary = !opts.ssr || !!opts.loading;
const Wrap = hasSuspenseBoundary ? _react.Suspense : _react.Fragment;
const wrapProps = hasSuspenseBoundary ? {
fallback: fallbackElement
} : {};
const children = opts.ssr ? /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
children: [
typeof window === 'undefined' ? /*#__PURE__*/ (0, _jsxruntime.jsx)(_preloadchunks.PreloadChunks, {
moduleIds: opts.modules
}) : null,
/*#__PURE__*/ (0, _jsxruntime.jsx)(Lazy, {
...props
})
]
}) : /*#__PURE__*/ (0, _jsxruntime.jsx)(_dynamicbailouttocsr.BailoutToCSR, {
reason: "next/dynamic",
children: /*#__PURE__*/ (0, _jsxruntime.jsx)(Lazy, {
...props
})
});
return /*#__PURE__*/ (0, _jsxruntime.jsx)(Wrap, {
...wrapProps,
children: children
});
}
LoadableComponent.displayName = 'LoadableComponent';
return LoadableComponent;
}
const _default = Loadable;
//# sourceMappingURL=loadable.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/shared/lib/lazy-dynamic/loadable.tsx"],"sourcesContent":["import { Suspense, Fragment, lazy } from 'react'\nimport { BailoutToCSR } from './dynamic-bailout-to-csr'\nimport type { ComponentModule } from './types'\nimport { PreloadChunks } from './preload-chunks'\n\n// Normalize loader to return the module as form { default: Component } for `React.lazy`.\n// Also for backward compatible since next/dynamic allows to resolve a component directly with loader\n// Client component reference proxy need to be converted to a module.\nfunction convertModule<P>(\n mod: React.ComponentType<P> | ComponentModule<P> | undefined\n): {\n default: React.ComponentType<P>\n} {\n // Check \"default\" prop before accessing it, as it could be client reference proxy that could break it reference.\n // Cases:\n // mod: { default: Component }\n // mod: Component\n // mod: { default: proxy(Component) }\n // mod: proxy(Component)\n const hasDefault = mod && 'default' in mod\n return {\n default: hasDefault\n ? (mod as ComponentModule<P>).default\n : (mod as React.ComponentType<P>),\n }\n}\n\nconst defaultOptions = {\n loader: () => Promise.resolve(convertModule(() => null)),\n loading: null,\n ssr: true,\n}\n\ninterface LoadableOptions {\n loader?: () => Promise<React.ComponentType<any> | ComponentModule<any>>\n loading?: React.ComponentType<any> | null\n ssr?: boolean\n modules?: string[]\n}\n\nfunction Loadable(options: LoadableOptions) {\n const opts = { ...defaultOptions, ...options }\n const Lazy = lazy(() => opts.loader().then(convertModule))\n const Loading = opts.loading\n\n function LoadableComponent(props: any) {\n const fallbackElement = Loading ? (\n <Loading isLoading={true} pastDelay={true} error={null} />\n ) : null\n\n // If it's non-SSR or provided a loading component, wrap it in a suspense boundary\n const hasSuspenseBoundary = !opts.ssr || !!opts.loading\n const Wrap = hasSuspenseBoundary ? Suspense : Fragment\n const wrapProps = hasSuspenseBoundary ? { fallback: fallbackElement } : {}\n const children = opts.ssr ? (\n <>\n {/* During SSR, we need to preload the CSS from the dynamic component to avoid flash of unstyled content */}\n {typeof window === 'undefined' ? (\n <PreloadChunks moduleIds={opts.modules} />\n ) : null}\n <Lazy {...props} />\n </>\n ) : (\n <BailoutToCSR reason=\"next/dynamic\">\n <Lazy {...props} />\n </BailoutToCSR>\n )\n\n return <Wrap {...wrapProps}>{children}</Wrap>\n }\n\n LoadableComponent.displayName = 'LoadableComponent'\n\n return LoadableComponent\n}\n\nexport default Loadable\n"],"names":["convertModule","mod","hasDefault","default","defaultOptions","loader","Promise","resolve","loading","ssr","Loadable","options","opts","Lazy","lazy","then","Loading","LoadableComponent","props","fallbackElement","isLoading","pastDelay","error","hasSuspenseBoundary","Wrap","Suspense","Fragment","wrapProps","fallback","children","window","PreloadChunks","moduleIds","modules","BailoutToCSR","reason","displayName"],"mappings":";;;;+BA4EA;;;eAAA;;;;uBA5EyC;qCACZ;+BAEC;AAE9B,yFAAyF;AACzF,qGAAqG;AACrG,qEAAqE;AACrE,SAASA,cACPC,GAA4D;IAI5D,iHAAiH;IACjH,SAAS;IACT,8BAA8B;IAC9B,iBAAiB;IACjB,qCAAqC;IACrC,wBAAwB;IACxB,MAAMC,aAAaD,OAAO,aAAaA;IACvC,OAAO;QACLE,SAASD,aACL,AAACD,IAA2BE,OAAO,GAClCF;IACP;AACF;AAEA,MAAMG,iBAAiB;IACrBC,QAAQ,IAAMC,QAAQC,OAAO,CAACP,cAAc,IAAM;IAClDQ,SAAS;IACTC,KAAK;AACP;AASA,SAASC,SAASC,OAAwB;IACxC,MAAMC,OAAO;QAAE,GAAGR,cAAc;QAAE,GAAGO,OAAO;IAAC;IAC7C,MAAME,qBAAOC,IAAAA,WAAI,EAAC,IAAMF,KAAKP,MAAM,GAAGU,IAAI,CAACf;IAC3C,MAAMgB,UAAUJ,KAAKJ,OAAO;IAE5B,SAASS,kBAAkBC,KAAU;QACnC,MAAMC,kBAAkBH,wBACtB,qBAACA;YAAQI,WAAW;YAAMC,WAAW;YAAMC,OAAO;aAChD;QAEJ,kFAAkF;QAClF,MAAMC,sBAAsB,CAACX,KAAKH,GAAG,IAAI,CAAC,CAACG,KAAKJ,OAAO;QACvD,MAAMgB,OAAOD,sBAAsBE,eAAQ,GAAGC,eAAQ;QACtD,MAAMC,YAAYJ,sBAAsB;YAAEK,UAAUT;QAAgB,IAAI,CAAC;QACzE,MAAMU,WAAWjB,KAAKH,GAAG,iBACvB;;gBAEG,OAAOqB,WAAW,4BACjB,qBAACC,4BAAa;oBAACC,WAAWpB,KAAKqB,OAAO;qBACpC;8BACJ,qBAACpB;oBAAM,GAAGK,KAAK;;;2BAGjB,qBAACgB,iCAAY;YAACC,QAAO;sBACnB,cAAA,qBAACtB;gBAAM,GAAGK,KAAK;;;QAInB,qBAAO,qBAACM;YAAM,GAAGG,SAAS;sBAAGE;;IAC/B;IAEAZ,kBAAkBmB,WAAW,GAAG;IAEhC,OAAOnB;AACT;MAEA,WAAeP","ignoreList":[0]}

View File

@@ -0,0 +1,3 @@
export declare function PreloadChunks({ moduleIds, }: {
moduleIds: string[] | undefined;
}): import("react/jsx-runtime").JSX.Element | null;

View File

@@ -0,0 +1,70 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "PreloadChunks", {
enumerable: true,
get: function() {
return PreloadChunks;
}
});
const _jsxruntime = require("react/jsx-runtime");
const _reactdom = require("react-dom");
const _workasyncstorageexternal = require("../../../server/app-render/work-async-storage.external");
const _encodeuripath = require("../encode-uri-path");
const _deploymentid = require("../deployment-id");
function PreloadChunks({ moduleIds }) {
// Early return in client compilation and only load requestStore on server side
if (typeof window !== 'undefined') {
return null;
}
const workStore = _workasyncstorageexternal.workAsyncStorage.getStore();
if (workStore === undefined) {
return null;
}
const allFiles = [];
// Search the current dynamic call unique key id in react loadable manifest,
// and find the corresponding CSS files to preload
if (workStore.reactLoadableManifest && moduleIds) {
const manifest = workStore.reactLoadableManifest;
for (const key of moduleIds){
if (!manifest[key]) continue;
const chunks = manifest[key].files;
allFiles.push(...chunks);
}
}
if (allFiles.length === 0) {
return null;
}
const dplId = (0, _deploymentid.getDeploymentIdQueryOrEmptyString)();
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_jsxruntime.Fragment, {
children: allFiles.map((chunk)=>{
const href = `${workStore.assetPrefix}/_next/${(0, _encodeuripath.encodeURIPath)(chunk)}${dplId}`;
const isCss = chunk.endsWith('.css');
// If it's stylesheet we use `precedence` o help hoist with React Float.
// For stylesheets we actually need to render the CSS because nothing else is going to do it so it needs to be part of the component tree.
// The `preload` for stylesheet is not optional.
if (isCss) {
return /*#__PURE__*/ (0, _jsxruntime.jsx)("link", {
// @ts-ignore
precedence: "dynamic",
href: href,
rel: "stylesheet",
as: "style",
nonce: workStore.nonce
}, chunk);
} else {
// If it's script we use ReactDOM.preload to preload the resources
(0, _reactdom.preload)(href, {
as: 'script',
fetchPriority: 'low',
nonce: workStore.nonce
});
return null;
}
})
});
}
//# sourceMappingURL=preload-chunks.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/shared/lib/lazy-dynamic/preload-chunks.tsx"],"sourcesContent":["'use client'\n\nimport { preload } from 'react-dom'\n\nimport { workAsyncStorage } from '../../../server/app-render/work-async-storage.external'\nimport { encodeURIPath } from '../encode-uri-path'\nimport { getDeploymentIdQueryOrEmptyString } from '../deployment-id'\n\nexport function PreloadChunks({\n moduleIds,\n}: {\n moduleIds: string[] | undefined\n}) {\n // Early return in client compilation and only load requestStore on server side\n if (typeof window !== 'undefined') {\n return null\n }\n\n const workStore = workAsyncStorage.getStore()\n if (workStore === undefined) {\n return null\n }\n\n const allFiles = []\n\n // Search the current dynamic call unique key id in react loadable manifest,\n // and find the corresponding CSS files to preload\n if (workStore.reactLoadableManifest && moduleIds) {\n const manifest = workStore.reactLoadableManifest\n for (const key of moduleIds) {\n if (!manifest[key]) continue\n const chunks = manifest[key].files\n allFiles.push(...chunks)\n }\n }\n\n if (allFiles.length === 0) {\n return null\n }\n\n const dplId = getDeploymentIdQueryOrEmptyString()\n\n return (\n <>\n {allFiles.map((chunk) => {\n const href = `${workStore.assetPrefix}/_next/${encodeURIPath(chunk)}${dplId}`\n const isCss = chunk.endsWith('.css')\n // If it's stylesheet we use `precedence` o help hoist with React Float.\n // For stylesheets we actually need to render the CSS because nothing else is going to do it so it needs to be part of the component tree.\n // The `preload` for stylesheet is not optional.\n if (isCss) {\n return (\n <link\n key={chunk}\n // @ts-ignore\n precedence=\"dynamic\"\n href={href}\n rel=\"stylesheet\"\n as=\"style\"\n nonce={workStore.nonce}\n />\n )\n } else {\n // If it's script we use ReactDOM.preload to preload the resources\n preload(href, {\n as: 'script',\n fetchPriority: 'low',\n nonce: workStore.nonce,\n })\n return null\n }\n })}\n </>\n )\n}\n"],"names":["PreloadChunks","moduleIds","window","workStore","workAsyncStorage","getStore","undefined","allFiles","reactLoadableManifest","manifest","key","chunks","files","push","length","dplId","getDeploymentIdQueryOrEmptyString","map","chunk","href","assetPrefix","encodeURIPath","isCss","endsWith","link","precedence","rel","as","nonce","preload","fetchPriority"],"mappings":"AAAA;;;;;+BAQgBA;;;eAAAA;;;;0BANQ;0CAES;+BACH;8BACoB;AAE3C,SAASA,cAAc,EAC5BC,SAAS,EAGV;IACC,+EAA+E;IAC/E,IAAI,OAAOC,WAAW,aAAa;QACjC,OAAO;IACT;IAEA,MAAMC,YAAYC,0CAAgB,CAACC,QAAQ;IAC3C,IAAIF,cAAcG,WAAW;QAC3B,OAAO;IACT;IAEA,MAAMC,WAAW,EAAE;IAEnB,4EAA4E;IAC5E,kDAAkD;IAClD,IAAIJ,UAAUK,qBAAqB,IAAIP,WAAW;QAChD,MAAMQ,WAAWN,UAAUK,qBAAqB;QAChD,KAAK,MAAME,OAAOT,UAAW;YAC3B,IAAI,CAACQ,QAAQ,CAACC,IAAI,EAAE;YACpB,MAAMC,SAASF,QAAQ,CAACC,IAAI,CAACE,KAAK;YAClCL,SAASM,IAAI,IAAIF;QACnB;IACF;IAEA,IAAIJ,SAASO,MAAM,KAAK,GAAG;QACzB,OAAO;IACT;IAEA,MAAMC,QAAQC,IAAAA,+CAAiC;IAE/C,qBACE;kBACGT,SAASU,GAAG,CAAC,CAACC;YACb,MAAMC,OAAO,GAAGhB,UAAUiB,WAAW,CAAC,OAAO,EAAEC,IAAAA,4BAAa,EAACH,SAASH,OAAO;YAC7E,MAAMO,QAAQJ,MAAMK,QAAQ,CAAC;YAC7B,wEAAwE;YACxE,0IAA0I;YAC1I,gDAAgD;YAChD,IAAID,OAAO;gBACT,qBACE,qBAACE;oBAEC,aAAa;oBACbC,YAAW;oBACXN,MAAMA;oBACNO,KAAI;oBACJC,IAAG;oBACHC,OAAOzB,UAAUyB,KAAK;mBANjBV;YASX,OAAO;gBACL,kEAAkE;gBAClEW,IAAAA,iBAAO,EAACV,MAAM;oBACZQ,IAAI;oBACJG,eAAe;oBACfF,OAAOzB,UAAUyB,KAAK;gBACxB;gBACA,OAAO;YACT;QACF;;AAGN","ignoreList":[0]}

View File

@@ -0,0 +1,15 @@
export type ComponentModule<P = {}> = {
default: React.ComponentType<P>;
};
export declare type LoaderComponent<P = {}> = Promise<React.ComponentType<P> | ComponentModule<P>>;
export declare type Loader<P = {}> = () => LoaderComponent<P>;
export type LoadableGeneratedOptions = {
modules?: string[];
};
export type DynamicOptionsLoadingProps = {
error?: Error | null;
isLoading?: boolean;
pastDelay?: boolean;
retry?: () => void;
timedOut?: boolean;
};

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","ignoreList":[]}