Niklas Korz
banner
niklaskorz.de
Niklas Korz
@niklaskorz.de
🇪🇺 European

Cloud computing, web development, multimedia, languages and pen and paper. Sometimes computer graphics. Mainly 🦀 Rust, Go and TypeScript.

❄️ […]

🌉 bridged from ⁂ https://rheinneckar.social/@niklaskorz, follow @ap.brid.gy to interact
Reposted by Niklas Korz
Das Rhein-Neckar-Fernsehen ist insolvent. Mal wieder… Aber diesmal sieht es besonders düster aus.

https://www.rnz.de/politik/wirtschaft-regional_artikel,-Jetzt-ist-Schicht-im-Schacht-Nach-39-Jahren-stellte-RNF-den-Sendebetrieb-ein-_arid,1836631.html

Auch wenn Fernsehen überhaupt nicht mein […]
Original post on chaos.social
chaos.social
December 19, 2025 at 9:36 PM
Reposted by Niklas Korz
GitHub Actions charging per build minute for *self-hosted-runners*? Shit's about to hit the fan lol
December 16, 2025 at 5:57 PM
Reposted by Niklas Korz
a uh, psa about bincode www.reddit.com/r/rust/comme...
From the rust community on Reddit
Explore this post and more from the rust community
www.reddit.com
December 16, 2025 at 5:51 PM
There aren't many opportunities to mention that one time I have successfully published a paper, but I did sneak it into a GitHub comment just now.
December 15, 2025 at 5:58 PM
I wonder what marketshare #linux gamers need to reach on #steam to make AAA titles consider a #vulkan backend in addition the usual D3D12 (although VKD3D does work great, so probably not something any studio not already using Vulkan is actually taking into consideration)
December 10, 2025 at 7:24 PM
Reposted by Niklas Korz
Rendering partial#MDX to #html with #astro for embedding in an #rss or #atom feed has been very difficult, until recently. Now it can be done using the new Container API and a bit of HTML sanitizing.

https://prass.tech/blog/rss-full-content-rendering/
Astro RSS Feeds with Full MDX Content
RSS is pretty cool. We can pick our favorite websites, magazines, blogs, really anything that offers a public feed, add them to our aggregator of choice, and get notified about new content. I use NetNewsWire, because it has a great UI and lets me sync my reading across Apple devices, but there are many other great options available. This blog is built with Astro. I initially used the recommended @astrojs/rss library to generate an RSS feed, but now I want my feed to include the full post content, not just title and description. Astro has a guide for that, but there is one major problem with that approach. > “Note: this will not process components or JSX expressions in MDX files.” … I’m using MDX. There hasn’t been a clear solution for this problem until recently, when Astro 4.9 was released. In this article, I show you exactly how I use the new Astro Container API to render the full article content when using MDX. ## The Astro Container API This new API, at this point still marked as experimental, lets us render a single Astro Component to a string. That’s perfect for generating RSS feeds, because we only need the HTML of the post content, not the whole document structure. Let’s look at the code. // getPostWithContent.ts import type { APIContext } from 'astro'; import { experimental_AstroContainer as AstroContainer } from 'astro/container'; import { loadRenderers } from 'astro:container'; import { getContainerRenderer as getMDXRenderer } from '@astrojs/mdx'; import { render } from 'astro:content'; import { rehype } from 'rehype'; export default async function getPostsWithContent(context: APIContext) { const siteURL = getSiteURL(context); const container = await AstroContainer.create({ renderers: await loadRenderers([getMDXRenderer()]), }); const posts = await getSortedBlogPosts(); return Promise.all( posts.map(async post => { const { Content } = await render(post); const rawContent = await container.renderToString(Content); const file = await rehype() .data('settings', { fragment: true, }) .use(sanitizeHTML, { siteURL }) .process(rawContent); return { post, content: String(file), }; }) ); } This function is responsible for fetching the post metadata and render the post content to a sanitized string. The `siteURL` comes from Astro’s `APIContext` which is available in all API functions. The function will be used inside of a `GET` request handler, but I’ll get to that later. I’m using MDX for my blog posts, so the renderer I need to load for the Astro container is the MDX renderer (There are more renderers available and you can also write your own). Once the `container` is created, I load the posts from the content collection and `render` it to an Astro component. At this point I could add the `<Content />` component to a `.astro` page, but since this is an API function, I pass it to `container.renderToString()` instead, which renders the Astro component as HTML to a string. I could stop here and put this string into the RSS post content, but there are some issues with the HTML output that I have to fix first. ## Sanitizing the Output Astro, being built for websites, rendered the post for a web page. Links to pages and images of the website use relative paths. Unfortunately that won’t work in RSS readers. To fix this I need to prefix each path with the site URL. To do this properly it requires a three-step process. 1. parse the HTML into AST format 2. modify some of the nodes 3. render the modified AST back into a string This sounds like a lot. Parsing and rendering HTML is far from trivial. Lucky for us, there are great tools available. I choose unified, or rather rehype, because it’s well-documented and widely used. In fact, it’s used by Astro internally for rendering Markdown. I added a single plugin to the processing chain, `sanitizeHTML`. `rehype` internally wraps that into a `rehypeParse`, to turn the HTML string into an abstract syntax tree (AST) and `rehypeStringify`, which turns the AST into serialized HTML. Let me show you the sanitizing plugin. // sanitizeHTML.ts import type { Element, Root } from 'hast'; import type { Plugin } from 'unified'; import { visitParents } from 'unist-util-visit-parents'; interface SanitizeHTMLOptions { siteURL: string; } const sanitizeHTML: Plugin<[SanitizeHTMLOptions], Root> = ({ siteURL }) => { return tree => { visitParents(tree, (node, parents) => { if (node.type !== 'element') { return; } // Remove all style tags if (node.tagName === 'style') { return removeElementNode(node, parents); } // Remove all script tags if (node.tagName === 'script') { return removeElementNode(node, parents); } // Remove all spans inside code tags if ( node.tagName === 'span' && parents.some(parent => parent.type === 'element' && parent.tagName === 'code') ) { return removeElementNode(node, parents, true); } // Fix relative link URLs if (node.tagName === 'a' && typeof node.properties.href === 'string') { node.properties.href = new URL(node.properties.href, siteURL).href; } if (node.tagName === 'a' && 'target' in node.properties) { delete node.properties.target; } // Fix relative image URLs if (node.tagName === 'img' && typeof node.properties.src === 'string') { node.properties.src = new URL(node.properties.src, siteURL).href; } // Drop all style attributes if ('style' in node.properties) { delete node.properties.style; } // Drop all class attributes if ('className' in node.properties) { delete node.properties.className; } // Remove Astros data-astro-cid-... attributes for (const key of Object.keys(node.properties)) { if (key.startsWith('dataAstroCid')) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete node.properties[key]; } } }); }; }; export default sanitizeHTML; I’m using unist-util-visit-parents because it gives me access to the parent nodes of each visited node. `removeElementNode` is a simple helper function that replaces a node with its children. The plugin does the following sanitization steps. 1. Remove all `<style>` tags. RSS readers take care of text styling and an RSS feed shouldn’t come with styles attached. 2. Remove all `<script>` tags, for similar reasons. RSS feeds should provide only text content and semantics and most RSS readers will ignore inline scripts. 3. Remove all `<span>` tags inside `<code>` tags. Astro’s syntax highlighting plugin adds a lot of `<span>` tags for styling purposes, but without styles they only add bloat. 4. Fix relative link URLs. For example, a link to /blog/view-transitions will be converted to https://prass.tech/blog/view-transitions 5. Fix relative image URLs for similar reasons. 6. Drop inline style attributes. 7. Drop all class attributes. 8. Remove `data-astro-cid-...` attributes. Those are used for styling in Astro. This will produce minimal clutter-free HTML output. ## Creating RSS, ATOM and JSON feeds @astrojs/rss has no built-in support for Atom feeds. That’s why I decided to use the popular feed library. It can handle RSS 2.0, Atom 1.0 and also JSON Feed 1.0. Perfect! In Astro, to create an XML file, we can use a `GET` handler. I added the following files to the `/pages/feed` folder: `atom.ts`, `json.ts` and `rss.ts`. The `generateFeed` function contains the shared logic to create a `new Feed()`. import { SITE } from '@config'; import getPostsWithContent from '@utils/feed/getPostsWithContent'; import type { APIContext } from 'astro'; import { Feed, type Author } from 'feed'; import getSiteURL from './getSiteURL'; export async function generateFeed(context: APIContext): Promise<Feed> { const siteURL = getSiteURL(context); const author: Author = { name: SITE.author, email: SITE.email, link: SITE.website, }; const feed = new Feed({ id: SITE.website, link: siteURL, language: SITE.language, title: SITE.title, description: SITE.desc, favicon: new URL('/favicon.ico', siteURL).toString(), copyright: SITE.license, author, feedLinks: { json: new URL('/feed/json', siteURL).toString(), atom: new URL('/feed/atom', siteURL).toString(), rss: new URL('/feed/rss', siteURL).toString(), }, }); const postsWithContent = await getPostsWithContent(context); for (const { post, content } of postsWithContent) { const link = new URL(`/blog/${post.id}/`, siteURL).toString(); feed.addItem({ id: link, link, title: post.data.title, description: post.data.description, published: post.data.pubDate, content, date: post.data.updatedDate || post.data.pubDate, category: post.data.tags.map(tag => ({ name: tag, term: tag.toLowerCase(), domain: new URL(`/tags/${tag.toLowerCase()}/`, siteURL).toString(), })), }); } return feed; } On each page I added the `GET` function with the feed output in the `Response`. import { generateFeed } from '@utils/feed/generateFeed'; import type { APIContext } from 'astro'; export async function GET(context: APIContext) { const feed = await generateFeed(context); return new Response(feed.rss2(), { headers: { 'Content-Type': 'application/xml', }, }); } _NOTE: The`Content-Type` header for `feed.json1()` is `application/json` and for `feed.atom1()` it’s `application/atom+xml`._ ## Discoverability One more small change is needed to make the feeds discoverable from every page of my website. I’m using a shared layout component, where I added the following lines inside of the `<head>` tag. <link rel="alternate" type="application/rss+xml" title={`${SITE.shortTitle} RSS Feed`} href="/feed/rss" /> <link rel="alternate" type="application/json" title={`${SITE.shortTitle} JSON Feed`} href="/feed/json" /> <link rel="alternate" type="application/atom+xml" title={`${SITE.shortTitle} Atom Feed`} href="/feed/atom" /> And that’s it. Everyone can now read the full article content inside their favorite RSS/ATOM reader. Curious to see the live result? * RSS: https://prass.tech/feed/rss * Atom: https://prass.tech/feed/atom * JSON: https://prass.tech/feed/json I hope you enjoyed this little excursion into the world of RSS and HTML parsing.
prass.tech
December 7, 2025 at 1:09 PM
Reposted by Niklas Korz
Had an extremely normal evening with extremely neurotypical people (@somsnosa, @cas, @spyraks). /s
December 7, 2025 at 10:18 AM
Reposted by Niklas Korz
@niklaskorz Bending spoons acquired a much loved camera app - Filmic Pro - and drove it to the grounds within months by wrecklessly monetising against the app's community.
December 5, 2025 at 2:54 PM
Im Angesicht der #heise Kontroverse mal @Golem folgen... Auch wenns da etwas mau ausschaut mit Beiträgen.
December 3, 2025 at 12:41 PM
Reposted by Niklas Korz
Facebook hält uns als Geiseln, sagt Cory Doctorow – und beschreibt in der SZ, wie Plattformen systematisch schlechter werden. Erst nützlich, dann abhängig machend, schließlich unbrauchbar: Enshittification.
Lesenswertes Interview über digitale Mauern, Lock-ins und die Notwendigkeit von […]
Original post on chaos.social
chaos.social
December 2, 2025 at 9:02 PM
#eventbrite to be acquired by #bendingspoons, following their acquisition of #Meetup.com which caused many users to migrate to Eventbrite in the first place. I think at this point if becomes very obvious that the #Fediverse is the best place to coordinate your meetups, and you have many options […]
Original post on rheinneckar.social
rheinneckar.social
December 2, 2025 at 2:23 PM
RE: https://mastodon.social/@mainz_de/115628583057729789

Drei Plattformen des gleichen US-Konzerns bespielen geht klar, aber die eine mit freiem Protokoll und Entwicklung innerhalb der EU wohl nicht. Uff. Wer auch immer das beim Stadtmarketing zu verantworten hat sollte sich gerne mal mit den […]
Original post on rheinneckar.social
rheinneckar.social
November 29, 2025 at 7:41 PM
Shout-out to local co-op games that show the correct input symbols for both players even though we're playing with two different kinds of gamepads (Xbox one and Dual Sense).

(In this case, Traveler's Rest and Split Fiction)
November 29, 2025 at 2:10 PM
Anyone here using #linux on a #MINISFORUM v3 and can give a short experience report? Other recommendations for tablets with pens that work great with Linux also welcome!
November 28, 2025 at 11:36 PM
#seafile has been removed in #nixos 25.11 because "upstream only supports docker now", but I didn't want to migrate to docker. Good thing that NixOS 25.11 also introduces an #opencloud module and that was really easy to setup. Data migration was a bit harder than it should be because Seafile has […]
Original post on rheinneckar.social
rheinneckar.social
November 23, 2025 at 10:36 PM
Dank Vodafones neuestem Geniestreich, DE-CIX zu verlassen, kann ich jetzt also nicht Mal mehr wirklich auf meinen @netcup Server zugreifen... 100kbit/s Bandbreite

#Vodafone #netcup
November 23, 2025 at 6:00 PM
When #ovh states their event is held entirely in English but then you come to the two hour keynote and it's entirely in French without any bullet points or subtitles. So that was a damn lie. Hardest listening comprehension exercise of this year.

Very […]

[Original post on rheinneckar.social]
November 20, 2025 at 10:16 AM
November 20, 2025 at 10:13 AM
Well guess I'll have to go back to selfhosting Matrix

#cloudflare #matrix
November 18, 2025 at 12:49 PM
Reposted by Niklas Korz
Hi! Today is my first official day as the Executive Director of Mastodon, replacing @gargon@mastodon.social as CEO. I joined the Mastodon team more than 5 years ago, mostly working for Mastodon in the evenings on top of a 120% day job. I was the driving force behind the incorporation of the […]
Original post on mastodon.social
mastodon.social
November 18, 2025 at 7:53 AM
Reposted by Niklas Korz
Today I am stepping down from my role as the CEO of #mastodon. Though this has been in the works for a while, I can't say I've fully processed how I feel about it. There is a bittersweet part to it, and I think I will miss it, but it also felt necessary. It feels like a goodbye, but it isn't—I […]
Original post on mastodon.social
mastodon.social
November 18, 2025 at 8:46 AM
Reposted by Niklas Korz
🇪🇺Today, EU gov'ts rejected changes to mandatory backdoor #chatcontrol & anonymity-destroying age checks. EU ambassadors are set to approve this soon.
ℹ️https://www.patrick-breyer.de/en/posts/chat-control/#councilpositions

📢Our protest isn't loud […]

[Original post on digitalcourage.social]
November 13, 2025 at 10:04 AM
I wish there was a #javascript / #Typescript package manager that behaved more like #RustLang's cargo. Specifically, in a workspace of many inter-dependent packages, I only want to fetch and build packages that the package I am working on depends on, as well as automatically rebuild workspace […]
Original post on rheinneckar.social
rheinneckar.social
November 12, 2025 at 10:38 AM
Reposted by Niklas Korz
What's the list of clippy lints you deny in production? I'm thinking await_while_holding_lock, stuff like that?
November 9, 2025 at 9:48 AM
In der alten @rnvgmbh Straßenbahn durchs Mannheimer Stadtgebiet :blobcataww:

#rnv #tram #ÖPNV #vintage #mannheim
November 8, 2025 at 6:26 PM