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,31 @@
import type { IncomingHttpHeaders } from 'node:http';
import type { NextConfig } from '../../../server/config-shared';
import { NextResponse } from '../../../server/web/exports';
/**
* Tests the logic of `headers`, `redirects`, and `rewrites` in `next.config.js`.
* Given the provided next config, this function will return a `NextResponse`
* with the result of running the request through the custom routes.
*
* @example Test whether a given URL results in a redirect.
* ```
* import { unstable_getResponseFromNextConfig, getRedirectUrl } from 'next/server/testing'
* const response = await unstable_getResponseFromNextConfig({
* url: 'https://nextjs.org/test',
* nextConfig: {
* async redirects() {
* return [
* { source: '/test', destination: '/test2', permanent: false },
* ]
* },
* }
* });
* expect(response.status).toEqual(307);
* expect(getRedirectUrl(response)).toEqual('https://nextjs.org/test2');
* ```
*/
export declare function unstable_getResponseFromNextConfig({ url, nextConfig, headers, cookies, }: {
url: string;
nextConfig: NextConfig;
headers?: IncomingHttpHeaders;
cookies?: Record<string, string>;
}): Promise<NextResponse>;

View File

@@ -0,0 +1,118 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "unstable_getResponseFromNextConfig", {
enumerable: true,
get: function() {
return unstable_getResponseFromNextConfig;
}
});
const _nodeurl = require("node:url");
const _pathtoregexp = require("next/dist/compiled/path-to-regexp");
const _preparedestination = require("../../../shared/lib/router/utils/prepare-destination");
const _buildcustomroute = require("../../../lib/build-custom-route");
const _loadcustomroutes = /*#__PURE__*/ _interop_require_default(require("../../../lib/load-custom-routes"));
const _exports = require("../../../server/web/exports");
const _redirectstatus = require("../../../lib/redirect-status");
const _utils = require("./utils");
const _parsedurlquerytoparams = require("../../../server/route-modules/app-route/helpers/parsed-url-query-to-params");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
/**
* Tries to match the current request against the provided route. If there is
* a match, it returns the params extracted from the path. If not, it returns
* undefined.
*/ function matchRoute(route, request, parsedUrl) {
const pathname = parsedUrl.pathname;
if (!pathname) {
return;
}
const regexMatches = pathname == null ? void 0 : pathname.match(route.regex);
if (regexMatches) {
const pathMatch = (0, _pathtoregexp.match)(route.source)(pathname);
if (!pathMatch) {
throw Object.defineProperty(new Error('Unexpected error: extracting params from path failed but the regular expression matched'), "__NEXT_ERROR_CODE", {
value: "E289",
enumerable: false,
configurable: true
});
}
if (route.has || route.missing) {
if (!(0, _preparedestination.matchHas)(request, parsedUrl.query, route.has, route.missing)) {
return;
}
}
return pathMatch.params;
}
}
async function unstable_getResponseFromNextConfig({ url, nextConfig, headers = {}, cookies = {} }) {
const parsedUrl = (0, _nodeurl.parse)(url, true);
const request = (0, _utils.constructRequest)({
url,
headers,
cookies
});
const routes = await (0, _loadcustomroutes.default)(nextConfig);
const headerRoutes = routes.headers.map((route)=>(0, _buildcustomroute.buildCustomRoute)('header', route));
const redirectRoutes = routes.redirects.map((route)=>(0, _buildcustomroute.buildCustomRoute)('redirect', route, [
'/_next/'
]));
const rewriteRoutes = [
...routes.rewrites.beforeFiles,
...routes.rewrites.afterFiles,
...routes.rewrites.fallback
].map((route)=>(0, _buildcustomroute.buildCustomRoute)('rewrite', route));
const respHeaders = {};
for (const route of headerRoutes){
const matched = matchRoute(route, request, parsedUrl);
if (matched) {
for (const header of route.headers){
respHeaders[header.key] = header.value;
}
}
}
function matchRouteAndGetDestination(route) {
const params = matchRoute(route, request, parsedUrl);
if (!params) {
return;
}
const { newUrl, parsedDestination } = (0, _preparedestination.prepareDestination)({
appendParamsToQuery: false,
destination: route.destination,
params,
query: parsedUrl.query
});
const searchParams = new URLSearchParams((0, _parsedurlquerytoparams.parsedUrlQueryToParams)(parsedDestination.query));
return new URL(searchParams.size > 0 ? `${newUrl}?${searchParams.toString()}` : newUrl, parsedDestination.hostname ? `${parsedDestination.protocol}//${parsedDestination.hostname}` : parsedUrl.host ? `${parsedUrl.protocol}//${parsedUrl.host}` : 'https://example.com');
}
for (const route of redirectRoutes){
const redirectUrl = matchRouteAndGetDestination(route);
if (!redirectUrl) {
continue;
}
const statusCode = (0, _redirectstatus.getRedirectStatus)(route);
return _exports.NextResponse.redirect(redirectUrl, {
status: statusCode,
headers: respHeaders
});
}
for (const route of rewriteRoutes){
const rewriteUrl = matchRouteAndGetDestination(route);
if (!rewriteUrl) {
continue;
}
return _exports.NextResponse.rewrite(rewriteUrl, {
headers: respHeaders
});
}
return new _exports.NextResponse('', {
status: 200,
headers: respHeaders
});
}
//# sourceMappingURL=config-testing-utils.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
export * from './config-testing-utils';
export * from './middleware-testing-utils';
export { getRedirectUrl, getRewrittenUrl, isRewrite } from './utils';

View File

@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
getRedirectUrl: null,
getRewrittenUrl: null,
isRewrite: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
getRedirectUrl: function() {
return _utils.getRedirectUrl;
},
getRewrittenUrl: function() {
return _utils.getRewrittenUrl;
},
isRewrite: function() {
return _utils.isRewrite;
}
});
0 && __export(require("./config-testing-utils")) && __export(require("./middleware-testing-utils"));
_export_star(require("./config-testing-utils"), exports);
_export_star(require("./middleware-testing-utils"), exports);
const _utils = require("./utils");
function _export_star(from, to) {
Object.keys(from).forEach(function(k) {
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
Object.defineProperty(to, k, {
enumerable: true,
get: function() {
return from[k];
}
});
}
});
return from;
}
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testing/server/index.ts"],"sourcesContent":["export * from './config-testing-utils'\nexport * from './middleware-testing-utils'\nexport { getRedirectUrl, getRewrittenUrl, isRewrite } from './utils'\n"],"names":["getRedirectUrl","getRewrittenUrl","isRewrite"],"mappings":";;;;;;;;;;;;;;;;IAESA,cAAc;eAAdA,qBAAc;;IAAEC,eAAe;eAAfA,sBAAe;;IAAEC,SAAS;eAATA,gBAAS;;;;qBAFrC;qBACA;uBAC6C","ignoreList":[0]}

View File

@@ -0,0 +1,19 @@
import type { IncomingHttpHeaders } from 'http';
import type { NextConfig } from '../../../server/config-shared';
import type { MiddlewareConfigMatcherInput } from '../../../build/segment-config/middleware/middleware-config';
export interface MiddlewareSourceConfig {
matcher?: MiddlewareConfigMatcherInput;
}
/**
* Checks whether the middleware config will match the provide URL and request
* information such as headers and cookies. This function is useful for
* unit tests to assert that middleware is matching (and therefore executing)
* only when it should be.
*/
export declare function unstable_doesMiddlewareMatch({ config, url, headers, cookies, nextConfig, }: {
config: MiddlewareSourceConfig;
url: string;
headers?: IncomingHttpHeaders;
cookies?: Record<string, string>;
nextConfig?: NextConfig;
}): boolean;

View File

@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "unstable_doesMiddlewareMatch", {
enumerable: true,
get: function() {
return unstable_doesMiddlewareMatch;
}
});
const _getpagestaticinfo = require("../../../build/analysis/get-page-static-info");
const _middlewareroutematcher = require("../../../shared/lib/router/utils/middleware-route-matcher");
const _url = require("../../../lib/url");
const _utils = require("./utils");
function unstable_doesMiddlewareMatch({ config, url, headers, cookies, nextConfig }) {
if (!config.matcher) {
return true;
}
const matchers = (0, _getpagestaticinfo.getMiddlewareMatchers)(config.matcher, nextConfig ?? {});
const routeMatchFn = (0, _middlewareroutematcher.getMiddlewareRouteMatcher)(matchers);
const { pathname, searchParams = new URLSearchParams() } = (0, _url.parseUrl)(url) || {};
const request = (0, _utils.constructRequest)({
url,
headers,
cookies
});
return routeMatchFn(pathname, request, Object.fromEntries(searchParams));
}
//# sourceMappingURL=middleware-testing-utils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testing/server/middleware-testing-utils.ts"],"sourcesContent":["import type { IncomingHttpHeaders } from 'http'\nimport { getMiddlewareMatchers } from '../../../build/analysis/get-page-static-info'\nimport { getMiddlewareRouteMatcher } from '../../../shared/lib/router/utils/middleware-route-matcher'\nimport type { NextConfig } from '../../../server/config-shared'\nimport { parseUrl } from '../../../lib/url'\nimport { constructRequest } from './utils'\nimport type { MiddlewareConfigMatcherInput } from '../../../build/segment-config/middleware/middleware-config'\n\nexport interface MiddlewareSourceConfig {\n matcher?: MiddlewareConfigMatcherInput\n}\n\n/**\n * Checks whether the middleware config will match the provide URL and request\n * information such as headers and cookies. This function is useful for\n * unit tests to assert that middleware is matching (and therefore executing)\n * only when it should be.\n */\nexport function unstable_doesMiddlewareMatch({\n config,\n url,\n headers,\n cookies,\n nextConfig,\n}: {\n config: MiddlewareSourceConfig\n url: string\n headers?: IncomingHttpHeaders\n cookies?: Record<string, string>\n nextConfig?: NextConfig\n}): boolean {\n if (!config.matcher) {\n return true\n }\n const matchers = getMiddlewareMatchers(config.matcher, nextConfig ?? {})\n const routeMatchFn = getMiddlewareRouteMatcher(matchers)\n const { pathname, searchParams = new URLSearchParams() } = parseUrl(url) || {}\n const request = constructRequest({ url, headers, cookies })\n return routeMatchFn(pathname, request, Object.fromEntries(searchParams))\n}\n"],"names":["unstable_doesMiddlewareMatch","config","url","headers","cookies","nextConfig","matcher","matchers","getMiddlewareMatchers","routeMatchFn","getMiddlewareRouteMatcher","pathname","searchParams","URLSearchParams","parseUrl","request","constructRequest","Object","fromEntries"],"mappings":";;;;+BAkBgBA;;;eAAAA;;;mCAjBsB;wCACI;qBAEjB;uBACQ;AAa1B,SAASA,6BAA6B,EAC3CC,MAAM,EACNC,GAAG,EACHC,OAAO,EACPC,OAAO,EACPC,UAAU,EAOX;IACC,IAAI,CAACJ,OAAOK,OAAO,EAAE;QACnB,OAAO;IACT;IACA,MAAMC,WAAWC,IAAAA,wCAAqB,EAACP,OAAOK,OAAO,EAAED,cAAc,CAAC;IACtE,MAAMI,eAAeC,IAAAA,iDAAyB,EAACH;IAC/C,MAAM,EAAEI,QAAQ,EAAEC,eAAe,IAAIC,iBAAiB,EAAE,GAAGC,IAAAA,aAAQ,EAACZ,QAAQ,CAAC;IAC7E,MAAMa,UAAUC,IAAAA,uBAAgB,EAAC;QAAEd;QAAKC;QAASC;IAAQ;IACzD,OAAOK,aAAaE,UAAUI,SAASE,OAAOC,WAAW,CAACN;AAC5D","ignoreList":[0]}

View File

@@ -0,0 +1,23 @@
import type { IncomingHttpHeaders } from 'http';
import type { BaseNextRequest } from '../../../server/base-http';
import type { NextResponse } from '../../../server/web/exports';
export declare function constructRequest({ url, headers, cookies, }: {
url: string;
headers?: IncomingHttpHeaders;
cookies?: Record<string, string>;
}): BaseNextRequest;
/**
* Returns the URL of the redirect if the response is a redirect response or
* returns null if the response is not.
*/
export declare function getRedirectUrl(response: NextResponse): string | null;
/**
* Checks whether the provided response is a rewrite response to a different
* URL.
*/
export declare function isRewrite(response: NextResponse): boolean;
/**
* Returns the URL of the response rewrite if the response is a rewrite, or
* returns null if the response is not.
*/
export declare function getRewrittenUrl(response: NextResponse): string | null;

View File

@@ -0,0 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
constructRequest: null,
getRedirectUrl: null,
getRewrittenUrl: null,
isRewrite: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
constructRequest: function() {
return constructRequest;
},
getRedirectUrl: function() {
return getRedirectUrl;
},
getRewrittenUrl: function() {
return getRewrittenUrl;
},
isRewrite: function() {
return isRewrite;
}
});
const _mockrequest = require("../../../server/lib/mock-request");
const _node = require("../../../server/base-http/node");
const _url = require("../../../lib/url");
function constructRequest({ url, headers = {}, cookies = {} }) {
if (!headers) {
headers = {};
}
if (!headers.host) {
var _parseUrl;
headers.host = (_parseUrl = (0, _url.parseUrl)(url)) == null ? void 0 : _parseUrl.host;
}
if (cookies) {
headers = {
...headers,
cookie: Object.entries(cookies).map(([name, value])=>`${name}=${value}`).join(';')
};
}
return new _node.NodeNextRequest(new _mockrequest.MockedRequest({
url,
headers,
method: 'GET'
}));
}
function getRedirectUrl(response) {
return response.headers.get('location');
}
function isRewrite(response) {
return Boolean(getRewrittenUrl(response));
}
function getRewrittenUrl(response) {
return response.headers.get('x-middleware-rewrite');
}
//# sourceMappingURL=utils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/experimental/testing/server/utils.ts"],"sourcesContent":["import type { IncomingHttpHeaders } from 'http'\nimport { MockedRequest } from '../../../server/lib/mock-request'\nimport { NodeNextRequest } from '../../../server/base-http/node'\nimport type { BaseNextRequest } from '../../../server/base-http'\nimport type { NextResponse } from '../../../server/web/exports'\nimport { parseUrl } from '../../../lib/url'\n\nexport function constructRequest({\n url,\n headers = {},\n cookies = {},\n}: {\n url: string\n headers?: IncomingHttpHeaders\n cookies?: Record<string, string>\n}): BaseNextRequest {\n if (!headers) {\n headers = {}\n }\n if (!headers.host) {\n headers.host = parseUrl(url)?.host\n }\n if (cookies) {\n headers = {\n ...headers,\n cookie: Object.entries(cookies)\n .map(([name, value]) => `${name}=${value}`)\n .join(';'),\n }\n }\n return new NodeNextRequest(new MockedRequest({ url, headers, method: 'GET' }))\n}\n\n/**\n * Returns the URL of the redirect if the response is a redirect response or\n * returns null if the response is not.\n */\nexport function getRedirectUrl(response: NextResponse): string | null {\n return response.headers.get('location')\n}\n\n/**\n * Checks whether the provided response is a rewrite response to a different\n * URL.\n */\nexport function isRewrite(response: NextResponse): boolean {\n return Boolean(getRewrittenUrl(response))\n}\n\n/**\n * Returns the URL of the response rewrite if the response is a rewrite, or\n * returns null if the response is not.\n */\nexport function getRewrittenUrl(response: NextResponse): string | null {\n return response.headers.get('x-middleware-rewrite')\n}\n"],"names":["constructRequest","getRedirectUrl","getRewrittenUrl","isRewrite","url","headers","cookies","host","parseUrl","cookie","Object","entries","map","name","value","join","NodeNextRequest","MockedRequest","method","response","get","Boolean"],"mappings":";;;;;;;;;;;;;;;;;IAOgBA,gBAAgB;eAAhBA;;IA8BAC,cAAc;eAAdA;;IAgBAC,eAAe;eAAfA;;IARAC,SAAS;eAATA;;;6BA5Cc;sBACE;qBAGP;AAElB,SAASH,iBAAiB,EAC/BI,GAAG,EACHC,UAAU,CAAC,CAAC,EACZC,UAAU,CAAC,CAAC,EAKb;IACC,IAAI,CAACD,SAAS;QACZA,UAAU,CAAC;IACb;IACA,IAAI,CAACA,QAAQE,IAAI,EAAE;YACFC;QAAfH,QAAQE,IAAI,IAAGC,YAAAA,IAAAA,aAAQ,EAACJ,yBAATI,UAAeD,IAAI;IACpC;IACA,IAAID,SAAS;QACXD,UAAU;YACR,GAAGA,OAAO;YACVI,QAAQC,OAAOC,OAAO,CAACL,SACpBM,GAAG,CAAC,CAAC,CAACC,MAAMC,MAAM,GAAK,GAAGD,KAAK,CAAC,EAAEC,OAAO,EACzCC,IAAI,CAAC;QACV;IACF;IACA,OAAO,IAAIC,qBAAe,CAAC,IAAIC,0BAAa,CAAC;QAAEb;QAAKC;QAASa,QAAQ;IAAM;AAC7E;AAMO,SAASjB,eAAekB,QAAsB;IACnD,OAAOA,SAASd,OAAO,CAACe,GAAG,CAAC;AAC9B;AAMO,SAASjB,UAAUgB,QAAsB;IAC9C,OAAOE,QAAQnB,gBAAgBiB;AACjC;AAMO,SAASjB,gBAAgBiB,QAAsB;IACpD,OAAOA,SAASd,OAAO,CAACe,GAAG,CAAC;AAC9B","ignoreList":[0]}