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,12 @@
type OgModule = typeof import('next/dist/compiled/@vercel/og');
/**
* The ImageResponse class allows you to generate dynamic images using JSX and CSS.
* This is useful for generating social media images such as Open Graph images, Twitter cards, and more.
*
* Read more: [Next.js Docs: `ImageResponse`](https://nextjs.org/docs/app/api-reference/functions/image-response)
*/
export declare class ImageResponse extends Response {
static displayName: string;
constructor(...args: ConstructorParameters<OgModule['ImageResponse']>);
}
export {};

View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ImageResponse", {
enumerable: true,
get: function() {
return ImageResponse;
}
});
function importModule() {
return import(process.env.NEXT_RUNTIME === 'edge' ? 'next/dist/compiled/@vercel/og/index.edge.js' : 'next/dist/compiled/@vercel/og/index.node.js');
}
class ImageResponse extends Response {
static #_ = this.displayName = 'ImageResponse';
constructor(...args){
const readable = new ReadableStream({
async start (controller) {
const OGImageResponse = // So far we have to manually determine which build to use,
// as the auto resolving is not working
(await importModule()).ImageResponse;
const imageResponse = new OGImageResponse(...args);
if (!imageResponse.body) {
return controller.close();
}
const reader = imageResponse.body.getReader();
while(true){
const { done, value } = await reader.read();
if (done) {
return controller.close();
}
controller.enqueue(value);
}
}
});
const options = args[1] || {};
const headers = new Headers({
'content-type': 'image/png',
'cache-control': process.env.NODE_ENV === 'development' ? 'no-cache, no-store' : 'public, max-age=0, must-revalidate'
});
if (options.headers) {
const newHeaders = new Headers(options.headers);
newHeaders.forEach((value, key)=>headers.set(key, value));
}
super(readable, {
headers,
status: options.status,
statusText: options.statusText
});
}
}
//# sourceMappingURL=image-response.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/server/og/image-response.ts"],"sourcesContent":["type OgModule = typeof import('next/dist/compiled/@vercel/og')\n\nfunction importModule(): Promise<\n typeof import('next/dist/compiled/@vercel/og')\n> {\n return import(\n process.env.NEXT_RUNTIME === 'edge'\n ? 'next/dist/compiled/@vercel/og/index.edge.js'\n : 'next/dist/compiled/@vercel/og/index.node.js'\n )\n}\n\n/**\n * The ImageResponse class allows you to generate dynamic images using JSX and CSS.\n * This is useful for generating social media images such as Open Graph images, Twitter cards, and more.\n *\n * Read more: [Next.js Docs: `ImageResponse`](https://nextjs.org/docs/app/api-reference/functions/image-response)\n */\nexport class ImageResponse extends Response {\n public static displayName = 'ImageResponse'\n constructor(...args: ConstructorParameters<OgModule['ImageResponse']>) {\n const readable = new ReadableStream({\n async start(controller) {\n const OGImageResponse: typeof import('next/dist/compiled/@vercel/og').ImageResponse =\n // So far we have to manually determine which build to use,\n // as the auto resolving is not working\n (await importModule()).ImageResponse\n const imageResponse = new OGImageResponse(...args) as Response\n\n if (!imageResponse.body) {\n return controller.close()\n }\n\n const reader = imageResponse.body!.getReader()\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n return controller.close()\n }\n controller.enqueue(value)\n }\n },\n })\n\n const options = args[1] || {}\n\n const headers = new Headers({\n 'content-type': 'image/png',\n 'cache-control':\n process.env.NODE_ENV === 'development'\n ? 'no-cache, no-store'\n : 'public, max-age=0, must-revalidate',\n })\n if (options.headers) {\n const newHeaders = new Headers(options.headers)\n newHeaders.forEach((value, key) => headers.set(key, value))\n }\n super(readable, {\n headers,\n status: options.status,\n statusText: options.statusText,\n })\n }\n}\n"],"names":["ImageResponse","importModule","process","env","NEXT_RUNTIME","Response","displayName","constructor","args","readable","ReadableStream","start","controller","OGImageResponse","imageResponse","body","close","reader","getReader","done","value","read","enqueue","options","headers","Headers","NODE_ENV","newHeaders","forEach","key","set","status","statusText"],"mappings":";;;;+BAkBaA;;;eAAAA;;;AAhBb,SAASC;IAGP,OAAO,MAAM,CACXC,QAAQC,GAAG,CAACC,YAAY,KAAK,SACzB,gDACA;AAER;AAQO,MAAMJ,sBAAsBK;qBACnBC,cAAc;IAC5BC,YAAY,GAAGC,IAAsD,CAAE;QACrE,MAAMC,WAAW,IAAIC,eAAe;YAClC,MAAMC,OAAMC,UAAU;gBACpB,MAAMC,kBAGJ,AAFA,2DAA2D;gBAC3D,uCAAuC;gBACtC,CAAA,MAAMZ,cAAa,EAAGD,aAAa;gBACtC,MAAMc,gBAAgB,IAAID,mBAAmBL;gBAE7C,IAAI,CAACM,cAAcC,IAAI,EAAE;oBACvB,OAAOH,WAAWI,KAAK;gBACzB;gBAEA,MAAMC,SAASH,cAAcC,IAAI,CAAEG,SAAS;gBAC5C,MAAO,KAAM;oBACX,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAE,GAAG,MAAMH,OAAOI,IAAI;oBACzC,IAAIF,MAAM;wBACR,OAAOP,WAAWI,KAAK;oBACzB;oBACAJ,WAAWU,OAAO,CAACF;gBACrB;YACF;QACF;QAEA,MAAMG,UAAUf,IAAI,CAAC,EAAE,IAAI,CAAC;QAE5B,MAAMgB,UAAU,IAAIC,QAAQ;YAC1B,gBAAgB;YAChB,iBACEvB,QAAQC,GAAG,CAACuB,QAAQ,KAAK,gBACrB,uBACA;QACR;QACA,IAAIH,QAAQC,OAAO,EAAE;YACnB,MAAMG,aAAa,IAAIF,QAAQF,QAAQC,OAAO;YAC9CG,WAAWC,OAAO,CAAC,CAACR,OAAOS,MAAQL,QAAQM,GAAG,CAACD,KAAKT;QACtD;QACA,KAAK,CAACX,UAAU;YACde;YACAO,QAAQR,QAAQQ,MAAM;YACtBC,YAAYT,QAAQS,UAAU;QAChC;IACF;AACF","ignoreList":[0]}