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,5 @@
import { RouteMatcher } from './route-matcher';
import type { AppPageRouteDefinition } from '../route-definitions/app-page-route-definition';
export declare class AppPageRouteMatcher extends RouteMatcher<AppPageRouteDefinition> {
get identity(): string;
}

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "AppPageRouteMatcher", {
enumerable: true,
get: function() {
return AppPageRouteMatcher;
}
});
const _routematcher = require("./route-matcher");
class AppPageRouteMatcher extends _routematcher.RouteMatcher {
get identity() {
return `${this.definition.pathname}?__nextPage=${this.definition.page}`;
}
}
//# sourceMappingURL=app-page-route-matcher.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/server/route-matchers/app-page-route-matcher.ts"],"sourcesContent":["import { RouteMatcher } from './route-matcher'\nimport type { AppPageRouteDefinition } from '../route-definitions/app-page-route-definition'\n\nexport class AppPageRouteMatcher extends RouteMatcher<AppPageRouteDefinition> {\n public get identity(): string {\n return `${this.definition.pathname}?__nextPage=${this.definition.page}`\n }\n}\n"],"names":["AppPageRouteMatcher","RouteMatcher","identity","definition","pathname","page"],"mappings":";;;;+BAGaA;;;eAAAA;;;8BAHgB;AAGtB,MAAMA,4BAA4BC,0BAAY;IACnD,IAAWC,WAAmB;QAC5B,OAAO,GAAG,IAAI,CAACC,UAAU,CAACC,QAAQ,CAAC,YAAY,EAAE,IAAI,CAACD,UAAU,CAACE,IAAI,EAAE;IACzE;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,4 @@
import { RouteMatcher } from './route-matcher';
import type { AppRouteRouteDefinition } from '../route-definitions/app-route-route-definition';
export declare class AppRouteRouteMatcher extends RouteMatcher<AppRouteRouteDefinition> {
}

View File

@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "AppRouteRouteMatcher", {
enumerable: true,
get: function() {
return AppRouteRouteMatcher;
}
});
const _routematcher = require("./route-matcher");
class AppRouteRouteMatcher extends _routematcher.RouteMatcher {
}
//# sourceMappingURL=app-route-route-matcher.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/server/route-matchers/app-route-route-matcher.ts"],"sourcesContent":["import { RouteMatcher } from './route-matcher'\nimport type { AppRouteRouteDefinition } from '../route-definitions/app-route-route-definition'\n\nexport class AppRouteRouteMatcher extends RouteMatcher<AppRouteRouteDefinition> {}\n"],"names":["AppRouteRouteMatcher","RouteMatcher"],"mappings":";;;;+BAGaA;;;eAAAA;;;8BAHgB;AAGtB,MAAMA,6BAA6BC,0BAAY;AAA2B","ignoreList":[0]}

View File

@@ -0,0 +1,40 @@
import type { LocaleAnalysisResult } from '../lib/i18n-provider';
import type { LocaleRouteDefinition } from '../route-definitions/locale-route-definition';
import type { LocaleRouteMatch } from '../route-matches/locale-route-match';
import { RouteMatcher } from './route-matcher';
export type LocaleMatcherMatchOptions = {
/**
* If defined, this indicates to the matcher that the request should be
* treated as locale-aware. If this is undefined, it means that this
* application was not configured for additional locales.
*/
i18n?: LocaleAnalysisResult;
};
export declare class LocaleRouteMatcher<D extends LocaleRouteDefinition = LocaleRouteDefinition> extends RouteMatcher<D> {
/**
* Identity returns the identity part of the matcher. This is used to compare
* a unique matcher to another. This is also used when sorting dynamic routes,
* so it must contain the pathname part as well.
*/
get identity(): string;
/**
* Match will attempt to match the given pathname against this route while
* also taking into account the locale information.
*
* @param pathname The pathname to match against.
* @param options The options to use when matching.
* @returns The match result, or `null` if there was no match.
*/
match(pathname: string, options?: LocaleMatcherMatchOptions): LocaleRouteMatch<D> | null;
/**
* Test will attempt to match the given pathname against this route while
* also taking into account the locale information.
*
* @param pathname The pathname to match against.
* @param options The options to use when matching.
* @returns The match result, or `null` if there was no match.
*/
test(pathname: string, options?: LocaleMatcherMatchOptions): {
params?: import("../request/params").Params;
} | null;
}

View File

@@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "LocaleRouteMatcher", {
enumerable: true,
get: function() {
return LocaleRouteMatcher;
}
});
const _routematcher = require("./route-matcher");
class LocaleRouteMatcher extends _routematcher.RouteMatcher {
/**
* Identity returns the identity part of the matcher. This is used to compare
* a unique matcher to another. This is also used when sorting dynamic routes,
* so it must contain the pathname part as well.
*/ get identity() {
var _this_definition_i18n;
return `${this.definition.pathname}?__nextLocale=${(_this_definition_i18n = this.definition.i18n) == null ? void 0 : _this_definition_i18n.locale}`;
}
/**
* Match will attempt to match the given pathname against this route while
* also taking into account the locale information.
*
* @param pathname The pathname to match against.
* @param options The options to use when matching.
* @returns The match result, or `null` if there was no match.
*/ match(pathname, options) {
var // If the options have a detected locale, then use that, otherwise use
// the route's locale.
_options_i18n, _this_definition_i18n;
// This is like the parent `match` method but instead this injects the
// additional `options` into the
const result = this.test(pathname, options);
if (!result) return null;
return {
definition: this.definition,
params: result.params,
detectedLocale: (options == null ? void 0 : (_options_i18n = options.i18n) == null ? void 0 : _options_i18n.detectedLocale) ?? ((_this_definition_i18n = this.definition.i18n) == null ? void 0 : _this_definition_i18n.locale)
};
}
/**
* Test will attempt to match the given pathname against this route while
* also taking into account the locale information.
*
* @param pathname The pathname to match against.
* @param options The options to use when matching.
* @returns The match result, or `null` if there was no match.
*/ test(pathname, options) {
// If this route has locale information and we have detected a locale, then
// we need to compare the detected locale to the route's locale.
if (this.definition.i18n && (options == null ? void 0 : options.i18n)) {
// If we have detected a locale and it does not match this route's locale,
// then this isn't a match!
if (this.definition.i18n.locale && options.i18n.detectedLocale && this.definition.i18n.locale !== options.i18n.detectedLocale) {
return null;
}
// Perform regular matching against the locale stripped pathname now, the
// locale information matches!
return super.test(options.i18n.pathname);
}
// If we don't have locale information, then we can just perform regular
// matching.
return super.test(pathname);
}
}
//# sourceMappingURL=locale-route-matcher.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/server/route-matchers/locale-route-matcher.ts"],"sourcesContent":["import type { LocaleAnalysisResult } from '../lib/i18n-provider'\nimport type { LocaleRouteDefinition } from '../route-definitions/locale-route-definition'\nimport type { LocaleRouteMatch } from '../route-matches/locale-route-match'\nimport { RouteMatcher } from './route-matcher'\n\nexport type LocaleMatcherMatchOptions = {\n /**\n * If defined, this indicates to the matcher that the request should be\n * treated as locale-aware. If this is undefined, it means that this\n * application was not configured for additional locales.\n */\n i18n?: LocaleAnalysisResult\n}\n\nexport class LocaleRouteMatcher<\n D extends LocaleRouteDefinition = LocaleRouteDefinition,\n> extends RouteMatcher<D> {\n /**\n * Identity returns the identity part of the matcher. This is used to compare\n * a unique matcher to another. This is also used when sorting dynamic routes,\n * so it must contain the pathname part as well.\n */\n public get identity(): string {\n return `${this.definition.pathname}?__nextLocale=${this.definition.i18n?.locale}`\n }\n\n /**\n * Match will attempt to match the given pathname against this route while\n * also taking into account the locale information.\n *\n * @param pathname The pathname to match against.\n * @param options The options to use when matching.\n * @returns The match result, or `null` if there was no match.\n */\n public match(\n pathname: string,\n options?: LocaleMatcherMatchOptions\n ): LocaleRouteMatch<D> | null {\n // This is like the parent `match` method but instead this injects the\n // additional `options` into the\n const result = this.test(pathname, options)\n if (!result) return null\n\n return {\n definition: this.definition,\n params: result.params,\n detectedLocale:\n // If the options have a detected locale, then use that, otherwise use\n // the route's locale.\n options?.i18n?.detectedLocale ?? this.definition.i18n?.locale,\n }\n }\n\n /**\n * Test will attempt to match the given pathname against this route while\n * also taking into account the locale information.\n *\n * @param pathname The pathname to match against.\n * @param options The options to use when matching.\n * @returns The match result, or `null` if there was no match.\n */\n public test(pathname: string, options?: LocaleMatcherMatchOptions) {\n // If this route has locale information and we have detected a locale, then\n // we need to compare the detected locale to the route's locale.\n if (this.definition.i18n && options?.i18n) {\n // If we have detected a locale and it does not match this route's locale,\n // then this isn't a match!\n if (\n this.definition.i18n.locale &&\n options.i18n.detectedLocale &&\n this.definition.i18n.locale !== options.i18n.detectedLocale\n ) {\n return null\n }\n\n // Perform regular matching against the locale stripped pathname now, the\n // locale information matches!\n return super.test(options.i18n.pathname)\n }\n\n // If we don't have locale information, then we can just perform regular\n // matching.\n return super.test(pathname)\n }\n}\n"],"names":["LocaleRouteMatcher","RouteMatcher","identity","definition","pathname","i18n","locale","match","options","result","test","params","detectedLocale"],"mappings":";;;;+BAcaA;;;eAAAA;;;8BAXgB;AAWtB,MAAMA,2BAEHC,0BAAY;IACpB;;;;GAIC,GACD,IAAWC,WAAmB;YACuB;QAAnD,OAAO,GAAG,IAAI,CAACC,UAAU,CAACC,QAAQ,CAAC,cAAc,GAAE,wBAAA,IAAI,CAACD,UAAU,CAACE,IAAI,qBAApB,sBAAsBC,MAAM,EAAE;IACnF;IAEA;;;;;;;GAOC,GACD,AAAOC,MACLH,QAAgB,EAChBI,OAAmC,EACP;YAUxB,sEAAsE;QACtE,sBAAsB;QACtBA,eAAiC;QAXrC,sEAAsE;QACtE,gCAAgC;QAChC,MAAMC,SAAS,IAAI,CAACC,IAAI,CAACN,UAAUI;QACnC,IAAI,CAACC,QAAQ,OAAO;QAEpB,OAAO;YACLN,YAAY,IAAI,CAACA,UAAU;YAC3BQ,QAAQF,OAAOE,MAAM;YACrBC,gBAGEJ,CAAAA,4BAAAA,gBAAAA,QAASH,IAAI,qBAAbG,cAAeI,cAAc,OAAI,wBAAA,IAAI,CAACT,UAAU,CAACE,IAAI,qBAApB,sBAAsBC,MAAM;QACjE;IACF;IAEA;;;;;;;GAOC,GACD,AAAOI,KAAKN,QAAgB,EAAEI,OAAmC,EAAE;QACjE,2EAA2E;QAC3E,gEAAgE;QAChE,IAAI,IAAI,CAACL,UAAU,CAACE,IAAI,KAAIG,2BAAAA,QAASH,IAAI,GAAE;YACzC,0EAA0E;YAC1E,2BAA2B;YAC3B,IACE,IAAI,CAACF,UAAU,CAACE,IAAI,CAACC,MAAM,IAC3BE,QAAQH,IAAI,CAACO,cAAc,IAC3B,IAAI,CAACT,UAAU,CAACE,IAAI,CAACC,MAAM,KAAKE,QAAQH,IAAI,CAACO,cAAc,EAC3D;gBACA,OAAO;YACT;YAEA,yEAAyE;YACzE,8BAA8B;YAC9B,OAAO,KAAK,CAACF,KAAKF,QAAQH,IAAI,CAACD,QAAQ;QACzC;QAEA,wEAAwE;QACxE,YAAY;QACZ,OAAO,KAAK,CAACM,KAAKN;IACpB;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,7 @@
import type { PagesAPIRouteDefinition } from '../route-definitions/pages-api-route-definition';
import { LocaleRouteMatcher } from './locale-route-matcher';
import { RouteMatcher } from './route-matcher';
export declare class PagesAPIRouteMatcher extends RouteMatcher<PagesAPIRouteDefinition> {
}
export declare class PagesAPILocaleRouteMatcher extends LocaleRouteMatcher<PagesAPIRouteDefinition> {
}

View File

@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
PagesAPILocaleRouteMatcher: null,
PagesAPIRouteMatcher: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
PagesAPILocaleRouteMatcher: function() {
return PagesAPILocaleRouteMatcher;
},
PagesAPIRouteMatcher: function() {
return PagesAPIRouteMatcher;
}
});
const _localeroutematcher = require("./locale-route-matcher");
const _routematcher = require("./route-matcher");
class PagesAPIRouteMatcher extends _routematcher.RouteMatcher {
}
class PagesAPILocaleRouteMatcher extends _localeroutematcher.LocaleRouteMatcher {
}
//# sourceMappingURL=pages-api-route-matcher.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/server/route-matchers/pages-api-route-matcher.ts"],"sourcesContent":["import type { PagesAPIRouteDefinition } from '../route-definitions/pages-api-route-definition'\nimport { LocaleRouteMatcher } from './locale-route-matcher'\nimport { RouteMatcher } from './route-matcher'\n\nexport class PagesAPIRouteMatcher extends RouteMatcher<PagesAPIRouteDefinition> {}\n\nexport class PagesAPILocaleRouteMatcher extends LocaleRouteMatcher<PagesAPIRouteDefinition> {}\n"],"names":["PagesAPILocaleRouteMatcher","PagesAPIRouteMatcher","RouteMatcher","LocaleRouteMatcher"],"mappings":";;;;;;;;;;;;;;;IAMaA,0BAA0B;eAA1BA;;IAFAC,oBAAoB;eAApBA;;;oCAHsB;8BACN;AAEtB,MAAMA,6BAA6BC,0BAAY;AAA2B;AAE1E,MAAMF,mCAAmCG,sCAAkB;AAA2B","ignoreList":[0]}

View File

@@ -0,0 +1,7 @@
import type { PagesRouteDefinition } from '../route-definitions/pages-route-definition';
import { LocaleRouteMatcher } from './locale-route-matcher';
import { RouteMatcher } from './route-matcher';
export declare class PagesRouteMatcher extends RouteMatcher<PagesRouteDefinition> {
}
export declare class PagesLocaleRouteMatcher extends LocaleRouteMatcher<PagesRouteDefinition> {
}

View File

@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
PagesLocaleRouteMatcher: null,
PagesRouteMatcher: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
PagesLocaleRouteMatcher: function() {
return PagesLocaleRouteMatcher;
},
PagesRouteMatcher: function() {
return PagesRouteMatcher;
}
});
const _localeroutematcher = require("./locale-route-matcher");
const _routematcher = require("./route-matcher");
class PagesRouteMatcher extends _routematcher.RouteMatcher {
}
class PagesLocaleRouteMatcher extends _localeroutematcher.LocaleRouteMatcher {
}
//# sourceMappingURL=pages-route-matcher.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/server/route-matchers/pages-route-matcher.ts"],"sourcesContent":["import type { PagesRouteDefinition } from '../route-definitions/pages-route-definition'\nimport { LocaleRouteMatcher } from './locale-route-matcher'\nimport { RouteMatcher } from './route-matcher'\n\nexport class PagesRouteMatcher extends RouteMatcher<PagesRouteDefinition> {}\n\nexport class PagesLocaleRouteMatcher extends LocaleRouteMatcher<PagesRouteDefinition> {}\n"],"names":["PagesLocaleRouteMatcher","PagesRouteMatcher","RouteMatcher","LocaleRouteMatcher"],"mappings":";;;;;;;;;;;;;;;IAMaA,uBAAuB;eAAvBA;;IAFAC,iBAAiB;eAAjBA;;;oCAHsB;8BACN;AAEtB,MAAMA,0BAA0BC,0BAAY;AAAwB;AAEpE,MAAMF,gCAAgCG,sCAAkB;AAAwB","ignoreList":[0]}

View File

@@ -0,0 +1,27 @@
import type { RouteMatch } from '../route-matches/route-match';
import type { RouteDefinition } from '../route-definitions/route-definition';
import type { Params } from '../request/params';
type RouteMatchResult = {
params?: Params;
};
export declare class RouteMatcher<D extends RouteDefinition = RouteDefinition> {
readonly definition: D;
private readonly dynamic?;
/**
* When set, this is an array of all the other matchers that are duplicates of
* this one. This is used by the managers to warn the users about possible
* duplicate matches on routes.
*/
duplicated?: Array<RouteMatcher>;
constructor(definition: D);
/**
* Identity returns the identity part of the matcher. This is used to compare
* a unique matcher to another. This is also used when sorting dynamic routes,
* so it must contain the pathname part.
*/
get identity(): string;
get isDynamic(): boolean;
match(pathname: string): RouteMatch<D> | null;
test(pathname: string): RouteMatchResult | null;
}
export {};

View File

@@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "RouteMatcher", {
enumerable: true,
get: function() {
return RouteMatcher;
}
});
const _utils = require("../../shared/lib/router/utils");
const _routematcher = require("../../shared/lib/router/utils/route-matcher");
const _routeregex = require("../../shared/lib/router/utils/route-regex");
class RouteMatcher {
constructor(definition){
this.definition = definition;
if ((0, _utils.isDynamicRoute)(definition.pathname)) {
this.dynamic = (0, _routematcher.getRouteMatcher)((0, _routeregex.getRouteRegex)(definition.pathname));
}
}
/**
* Identity returns the identity part of the matcher. This is used to compare
* a unique matcher to another. This is also used when sorting dynamic routes,
* so it must contain the pathname part.
*/ get identity() {
return this.definition.pathname;
}
get isDynamic() {
return this.dynamic !== undefined;
}
match(pathname) {
const result = this.test(pathname);
if (!result) return null;
return {
definition: this.definition,
params: result.params
};
}
test(pathname) {
if (this.dynamic) {
const params = this.dynamic(pathname);
if (!params) return null;
return {
params
};
}
if (pathname === this.definition.pathname) {
return {};
}
return null;
}
}
//# sourceMappingURL=route-matcher.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/server/route-matchers/route-matcher.ts"],"sourcesContent":["import type { RouteMatch } from '../route-matches/route-match'\nimport type { RouteDefinition } from '../route-definitions/route-definition'\nimport type { Params } from '../request/params'\n\nimport { isDynamicRoute } from '../../shared/lib/router/utils'\nimport {\n getRouteMatcher,\n type RouteMatchFn,\n} from '../../shared/lib/router/utils/route-matcher'\nimport { getRouteRegex } from '../../shared/lib/router/utils/route-regex'\n\ntype RouteMatchResult = {\n params?: Params\n}\n\nexport class RouteMatcher<D extends RouteDefinition = RouteDefinition> {\n private readonly dynamic?: RouteMatchFn\n\n /**\n * When set, this is an array of all the other matchers that are duplicates of\n * this one. This is used by the managers to warn the users about possible\n * duplicate matches on routes.\n */\n public duplicated?: Array<RouteMatcher>\n\n constructor(public readonly definition: D) {\n if (isDynamicRoute(definition.pathname)) {\n this.dynamic = getRouteMatcher(getRouteRegex(definition.pathname))\n }\n }\n\n /**\n * Identity returns the identity part of the matcher. This is used to compare\n * a unique matcher to another. This is also used when sorting dynamic routes,\n * so it must contain the pathname part.\n */\n public get identity(): string {\n return this.definition.pathname\n }\n\n public get isDynamic() {\n return this.dynamic !== undefined\n }\n\n public match(pathname: string): RouteMatch<D> | null {\n const result = this.test(pathname)\n if (!result) return null\n\n return { definition: this.definition, params: result.params }\n }\n\n public test(pathname: string): RouteMatchResult | null {\n if (this.dynamic) {\n const params = this.dynamic(pathname)\n if (!params) return null\n\n return { params }\n }\n\n if (pathname === this.definition.pathname) {\n return {}\n }\n\n return null\n }\n}\n"],"names":["RouteMatcher","constructor","definition","isDynamicRoute","pathname","dynamic","getRouteMatcher","getRouteRegex","identity","isDynamic","undefined","match","result","test","params"],"mappings":";;;;+BAeaA;;;eAAAA;;;uBAXkB;8BAIxB;4BACuB;AAMvB,MAAMA;IAUXC,YAAY,AAAgBC,UAAa,CAAE;aAAfA,aAAAA;QAC1B,IAAIC,IAAAA,qBAAc,EAACD,WAAWE,QAAQ,GAAG;YACvC,IAAI,CAACC,OAAO,GAAGC,IAAAA,6BAAe,EAACC,IAAAA,yBAAa,EAACL,WAAWE,QAAQ;QAClE;IACF;IAEA;;;;GAIC,GACD,IAAWI,WAAmB;QAC5B,OAAO,IAAI,CAACN,UAAU,CAACE,QAAQ;IACjC;IAEA,IAAWK,YAAY;QACrB,OAAO,IAAI,CAACJ,OAAO,KAAKK;IAC1B;IAEOC,MAAMP,QAAgB,EAAwB;QACnD,MAAMQ,SAAS,IAAI,CAACC,IAAI,CAACT;QACzB,IAAI,CAACQ,QAAQ,OAAO;QAEpB,OAAO;YAAEV,YAAY,IAAI,CAACA,UAAU;YAAEY,QAAQF,OAAOE,MAAM;QAAC;IAC9D;IAEOD,KAAKT,QAAgB,EAA2B;QACrD,IAAI,IAAI,CAACC,OAAO,EAAE;YAChB,MAAMS,SAAS,IAAI,CAACT,OAAO,CAACD;YAC5B,IAAI,CAACU,QAAQ,OAAO;YAEpB,OAAO;gBAAEA;YAAO;QAClB;QAEA,IAAIV,aAAa,IAAI,CAACF,UAAU,CAACE,QAAQ,EAAE;YACzC,OAAO,CAAC;QACV;QAEA,OAAO;IACT;AACF","ignoreList":[0]}