<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Programmieren auf cbrueggenolte.de</title>
    <link>https://cbrueggenolte.de/tags/programmieren/</link>
    <description>Neueste Beiträge in Programmieren auf cbrueggenolte.de</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>de-DE</language>
    <managingEditor>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</managingEditor>
    <webMaster>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</webMaster>
    <copyright>2018-2026 cbrueggenolte</copyright>
    <lastBuildDate>Wed, 18 Feb 2026 19:19:37 +0100</lastBuildDate>
    <atom:link href="https://cbrueggenolte.de/tags/programmieren/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Verstehen, bevor ich automatisiere</title>
      <link>https://cbrueggenolte.de/verstehen-bevor-ich-automatisiere/</link>
      <pubDate>Wed, 18 Feb 2026 19:19:37 +0100</pubDate><author>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</author>
      <guid>https://cbrueggenolte.de/verstehen-bevor-ich-automatisiere/</guid>
      <description>&lt;p&gt;Ich versuche seit geraumer Zeit, mich wirklich hinzusetzen und Golang zu lernen.&#xA;Grundlagen verstehen. Nicht nur anwenden, sondern wirklich begreifen.&lt;/p&gt;&#xA;&lt;p&gt;Und dann taucht das nächste Bootcamp auf.&#xA;Alles mit KI. Alles automatisiert.&#xA;„Warum noch selbst programmieren, wenn das System den Code für dich schreibt?“&lt;/p&gt;&#xA;&lt;p&gt;Ich bin ehrlich gesagt ein wenig hin- und hergerissen.&lt;/p&gt;&#xA;&lt;p&gt;Einerseits faszinieren mich die Möglichkeiten.&#xA;Die Tools sind beeindruckend, und es wäre naiv, das zu ignorieren.&lt;/p&gt;&#xA;&lt;p&gt;Andererseits frage ich mich, ob wir gerade dabei sind, uns selbst aus der Gleichung zu nehmen.&#xA;Ob wir noch bauen – oder nur noch prompten?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Ich versuche seit geraumer Zeit, mich wirklich hinzusetzen und Golang zu lernen.
Grundlagen verstehen. Nicht nur anwenden, sondern wirklich begreifen.</p>
<p>Und dann taucht das nächste Bootcamp auf.
Alles mit KI. Alles automatisiert.
„Warum noch selbst programmieren, wenn das System den Code für dich schreibt?“</p>
<p>Ich bin ehrlich gesagt ein wenig hin- und hergerissen.</p>
<p>Einerseits faszinieren mich die Möglichkeiten.
Die Tools sind beeindruckend, und es wäre naiv, das zu ignorieren.</p>
<p>Andererseits frage ich mich, ob wir gerade dabei sind, uns selbst aus der Gleichung zu nehmen.
Ob wir noch bauen – oder nur noch prompten?</p>
<p>Da klingt der Satz</p>
<blockquote>
<p>Wenn du nicht der KI-Revolution beitrittst, wirst du überflüssig und zurückgelassen.</p>
</blockquote>
<p>plötzlich nicht mehr ganz so motivierend.</p>
<p>Vielleicht ist genau das die eigentliche Frage:
Wie viel KI ist sinnvoll?
Und wo beginnt sie, unsere eigene Kompetenz zu ersetzen?</p>
<p>Noch habe ich ein paar Tage Zeit, bis das neue <a href="https://bootcamp.dev">Bootcamp</a> startet.
Dennoch habe ich mich noch nicht entschieden.</p>
<p>Ich tendiere allerdings dazu, teilzunehmen –
allein um zu sehen, wie die KI-Integration dort tatsächlich aussieht
und um einschätzen zu können, ob das wirklich substanziell ist
oder doch eher gut verpacktes Marketing.</p>
<p>Ich will verstehen, bevor ich automatisiere.
Ich will bauen können, bevor ich delegiere.
Wenn KI ein Werkzeug ist, dann will ich es führen –
nicht von ihm geführt werden.
Vielleicht ist genau das die Kompetenz, die in Zukunft zählt.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Thoughts on Angular 20</title>
      <link>https://cbrueggenolte.de/thoughts-on-angular-20/</link>
      <pubDate>Thu, 13 Nov 2025 00:00:00 +0100</pubDate><author>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</author>
      <guid>https://cbrueggenolte.de/thoughts-on-angular-20/</guid>
      <description>&lt;p&gt;Angular 20 ist nun schon eine Weile veröffentlicht,&#xA;und auch ich lerne weiterhin die neuen Features und Best Practices.&lt;/p&gt;&#xA;&lt;p&gt;Mit dem Release von Angular 20&#xA;haben die Entwickler weit mehr eingeführt&#xA;als das übliche Framework-Housekeeping.&#xA;Das Team treibt einen klaren Wandel voran:&#xA;hin zu saubereren Bezeichnungen,&#xA;domänenorientierten Strukturen&#xA;und einer insgesamt moderneren Projektorganisation.&lt;/p&gt;&#xA;&lt;p&gt;Das habe ich selbst erlebt:&#xA;Nachdem ich meine Tools auf Angular 20 aktualisiert&#xA;und ein neues Projekt mit der Angular CLI erstellt hatte,&#xA;fiel mir in VS Code etwas Merkwürdiges auf.&#xA;VS Code bot plötzlich keinerlei Autovervollständigung mehr&#xA;in meinen HTML-Templates für Properties und Methoden,&#xA;die ich in den zugehörigen TypeScript-Dateien definiert hatte.&#xA;Sogar der gewohnte „Go to Component“-Link war verschwunden.&#xA;Mit anderen Worten:&#xA;Der Editor verstand nicht mehr,&#xA;dass .html- und .ts-Dateien zusammengehörten —&#xA;einfach weil das neue Namensschema nicht erkannt wurde.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Angular 20 ist nun schon eine Weile veröffentlicht,
und auch ich lerne weiterhin die neuen Features und Best Practices.</p>
<p>Mit dem Release von Angular 20
haben die Entwickler weit mehr eingeführt
als das übliche Framework-Housekeeping.
Das Team treibt einen klaren Wandel voran:
hin zu saubereren Bezeichnungen,
domänenorientierten Strukturen
und einer insgesamt moderneren Projektorganisation.</p>
<p>Das habe ich selbst erlebt:
Nachdem ich meine Tools auf Angular 20 aktualisiert
und ein neues Projekt mit der Angular CLI erstellt hatte,
fiel mir in VS Code etwas Merkwürdiges auf.
VS Code bot plötzlich keinerlei Autovervollständigung mehr
in meinen HTML-Templates für Properties und Methoden,
die ich in den zugehörigen TypeScript-Dateien definiert hatte.
Sogar der gewohnte „Go to Component“-Link war verschwunden.
Mit anderen Worten:
Der Editor verstand nicht mehr,
dass .html- und .ts-Dateien zusammengehörten —
einfach weil das neue Namensschema nicht erkannt wurde.</p>
<p>Die Ursache war banal:
Ich nutzte noch eine ältere Version der Angular Language Service Extension.
Wenn du mit VS Code arbeitest,
stelle sicher,
dass Angular Language Service 20.3.3 oder neuer installiert ist.
Diese Version unterstützt die neuen Angular-20-Namenskonventionen vollständig
und stellt IntelliSense sowie Navigation korrekt wieder her.</p>
<p>Ist das erledigt,
funktioniert die Autovervollständigung wieder einwandfrei,
und du kannst die neuen Angular-20-Konventionen konsequent nutzen.</p>
<h2 id="ein-klarerer-namensstil">Ein klarerer Namensstil</h2>
<p>Angular 20 ermutigt Entwickler,
sich vom alten Muster zu lösen,
bei dem der Dateityp im Dateinamen untergebracht wurde:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">user-profile.component.ts
</span></span><span class="line"><span class="ln">2</span><span class="cl">auth.service.ts
</span></span><span class="line"><span class="ln">3</span><span class="cl">highlight.directive.ts</span></span></code></pre></div><p>Stattdessen liegt der Fokus nun auf aussagekräftigen,
domänenorientierten Namen:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">user-profile.ts
</span></span><span class="line"><span class="ln">2</span><span class="cl">auth-store.ts
</span></span><span class="line"><span class="ln">3</span><span class="cl">auth-api.ts
</span></span><span class="line"><span class="ln">4</span><span class="cl">highlight.ts</span></span></code></pre></div><p>Einige Suffixe bleiben —
vor allem dann,
wenn sie tatsächliches Verhalten oder einen Vertrag beschreiben:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">auth-guard.ts
</span></span><span class="line"><span class="ln">2</span><span class="cl">currency-pipe.ts
</span></span><span class="line"><span class="ln">3</span><span class="cl">shared-module.ts</span></span></code></pre></div><p>Die Idee ist einfach:
<strong>Benenne Dinge nach dem, was sie repräsentieren</strong> —
nicht nach dem Angular-Konstrukt,
das sich zufällig darin befindet.
Der Code wirkt dadurch leichter
und deutlich besser lesbar,
insbesondere bei größeren Projekten.</p>
<h2 id="eine-durchdachtere-ordnerstruktur">Eine durchdachtere Ordnerstruktur</h2>
<p>Angular 20 orientiert sich noch stärker
an einer domänengetriebenen Organisation.
Statt eines „features“-Ordners mit etwas von allem
strukturierst du deine App nach Kontexten.</p>
<p>Ein typisches Setup sieht heute so aus:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">src/
</span></span><span class="line"><span class="ln">2</span><span class="cl">└── app/
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── core/
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── domains/
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── shared/</span></span></code></pre></div><h3 id="core--globale-anwendungslogik"><code>core/</code> – Globale Anwendungslogik</h3>
<p>Alles in <code>core</code> gilt app-weit,
ist aber nicht fachlich spezifisch:
Authentifizierung,
Layout-Grundstruktur,
Fehlerbehandlung,
Konfiguration usw.</p>
<p>Beispiel:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">core/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">auth/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">guards/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">auth-guard.ts
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">stores/
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">auth-store.ts
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">pages/
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">login/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">login.ts
</span></span><span class="line"><span class="ln">10</span><span class="cl">login.html
</span></span><span class="line"><span class="ln">11</span><span class="cl">login.css</span></span></code></pre></div><p>Geroutete Views liegen sauber in einem <code>pages/</code>-Verzeichnis,
damit Navigation klar und nachvollziehbar bleibt.</p>
<h3 id="domains--deine-eigentliche-fachlogik"><code>domains/</code> – Deine eigentliche Fachlogik</h3>
<p>Statt eines großen Sammelordners mit lose gruppierten „Features“
teilst du deine Anwendung in fachliche Bereiche:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">domains/
</span></span><span class="line"><span class="ln">2</span><span class="cl">jobs/
</span></span><span class="line"><span class="ln">3</span><span class="cl">pages/
</span></span><span class="line"><span class="ln">4</span><span class="cl">models/
</span></span><span class="line"><span class="ln">5</span><span class="cl">stores/
</span></span><span class="line"><span class="ln">6</span><span class="cl">services/
</span></span><span class="line"><span class="ln">7</span><span class="cl">jobs.routes.ts
</span></span><span class="line"><span class="ln">8</span><span class="cl">events/
</span></span><span class="line"><span class="ln">9</span><span class="cl">groups/</span></span></code></pre></div><p>Diese Struktur skaliert deutlich besser.
Gerade für mich als jemand,
der Angular noch lernt,
macht sie den Zweck jedes Ordners wesentlich klarer.
Jede Domäne kann sich eigenständig weiterentwickeln,
und Teams können parallel arbeiten,
ohne sich gegenseitig in die Quere zu kommen.</p>
<h3 id="shared--wiederverwendbare-logikunabhängige-bausteine"><code>shared/</code> – Wiederverwendbare, logikunabhängige Bausteine</h3>
<p>Alles hier sollte möglichst „dumm“ und generisch sein:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">shared/
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">components/
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">notification/
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">notification.ts
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">notification.html
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">notification.css
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">pipes/
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">format-date-pipe.ts
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">utils/
</span></span><span class="line"><span class="ln">10</span><span class="cl">string-utils.ts
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">```text
</span></span><span class="line"><span class="ln">13</span><span class="cl">Eine `notification`-Komponente sollte zum Beispiel nicht wissen,
</span></span><span class="line"><span class="ln">14</span><span class="cl">ob sie eine Sicherheitswarnung
</span></span><span class="line"><span class="ln">15</span><span class="cl">oder eine Erfolgsmeldung
</span></span><span class="line"><span class="ln">16</span><span class="cl">aus einer völlig anderen Domäne anzeigt.
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">## Standalone Components sind der Standard. Nutze das.
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">Schon vor einiger Zeit eingeführt,
</span></span><span class="line"><span class="ln">21</span><span class="cl">aber weiterhin relevant:
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">Seit Angular 17 sind Standalone Components der Standard,
</span></span><span class="line"><span class="ln">24</span><span class="cl">und Angular 20 setzt diesen Weg konsequent fort.
</span></span><span class="line"><span class="ln">25</span><span class="cl">Ein modernes Setup sieht typischerweise so aus:
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">```text
</span></span><span class="line"><span class="ln">28</span><span class="cl">app/
</span></span><span class="line"><span class="ln">29</span><span class="cl">app.ts
</span></span><span class="line"><span class="ln">30</span><span class="cl">app.routes.ts
</span></span><span class="line"><span class="ln">31</span><span class="cl">app.config.ts</span></span></code></pre></div><p>Module sind nur noch nötig,
wenn ein spezieller Kompatibilitätsfall es erfordert.
Die meisten Projekte profitieren sofort davon,
Boilerplate zu reduzieren
und Standalone-Komposition vollständig zu nutzen.</p>
<h2 id="fazit">Fazit</h2>
<p>Angular 20 fühlt sich an wie ein bewusster Schritt
hin zu einer saubereren,
skalierbaren Architektur —
nicht durch starre Regeln,
sondern indem der moderne Ansatz zum natürlichen Standard wird.
Wenn deine Tools aktuell sind
(vor allem der Language Server),
verläuft der Umstieg reibungslos und intuitiv.</p>
<h3 id="ein-paar-zentrale-punkte">Ein paar zentrale Punkte</h3>
<ul>
<li>Nutze bewusst gewählte Dateinamen statt <code>.component.ts</code> oder <code>.service.ts</code>.</li>
<li>Strukturiere deine App nach <strong>Domänen</strong>, nicht nach Angular-Konstrukten.</li>
<li>Halte gemeinsame Bausteine bewusst „dumm“.</li>
<li>Setze auf Standalone Components — das Ökosystem ist bereit dafür.</li>
</ul>
<p>Es ist ein guter Zeitpunkt,
ein bestehendes Projekt noch einmal zu überprüfen,
veraltetes Naming-Chaos aufzuräumen
und eine klarere Struktur einzuführen,
die auch langfristig trägt.</p>
<p>Viel Freude beim Coden mit Angular 20!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Mein Microblog – Von der Idee zur stabilen App</title>
      <link>https://cbrueggenolte.de/mein-microblog-von-der-idee-zur-stabilen-app/</link>
      <pubDate>Sat, 06 Sep 2025 00:00:00 +0200</pubDate><author>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</author>
      <guid>https://cbrueggenolte.de/mein-microblog-von-der-idee-zur-stabilen-app/</guid>
      <description>&lt;p&gt;Die letzten Wochen habe ich mich intensiv mit meinem eigenen Microblog beschäftigt. Was als kleines Experiment begann,&#xA;wuchs schnell zu einem vollwertigen Projekt heran.&#xA;Die Idee war klar: eine minimalistische Plattform für kurze Beiträge in Markdown,&#xA;ohne schweres CMS, ohne unnötigen Ballast.&#xA;Ich wollte etwas Eigenes schaffen – unabhängig von großen Anbietern, leichtgewichtig und trotzdem funktional.&lt;/p&gt;&#xA;&lt;h2 id=&#34;der-weg-zur-richtigen-technologie&#34;&gt;Der Weg zur richtigen Technologie&lt;/h2&gt;&#xA;&lt;p&gt;Ich war sofort von &lt;a href=&#34;https://bun.sh/&#34;&gt;Bun&lt;/a&gt; begeistert – schnell, modern,&#xA;mit einer eleganten All-in-One-Philosophie.&#xA;Doch auf meinem Uberspace-Host scheiterte jede Bun-Version&#xA;an einer zu alten glibc.&#xA;Schade, aber Plan B war schnell gefunden: &lt;a href=&#34;https://deno.land/&#34;&gt;Deno&lt;/a&gt;.&#xA;Deno versprach Sicherheit und moderne Features.&#xA;Allerdings war die Kompatibilität mit manchen Bibliotheken hakelig,&#xA;und ich stieß bei der Integration auf zu viele kleine Hürden.&#xA;Nach diesen Experimenten landete ich bei klassischem Node.js (mit pnpm).&#xA;Es ist robust, weit verbreitet und ließ sich auf meinem Host&#xA;ohne Probleme einrichten.&#xA;Mit TypeScript und ESM war ich trotzdem nah&#xA;an einer modernen Entwicklungsumgebung.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Die letzten Wochen habe ich mich intensiv mit meinem eigenen Microblog beschäftigt. Was als kleines Experiment begann,
wuchs schnell zu einem vollwertigen Projekt heran.
Die Idee war klar: eine minimalistische Plattform für kurze Beiträge in Markdown,
ohne schweres CMS, ohne unnötigen Ballast.
Ich wollte etwas Eigenes schaffen – unabhängig von großen Anbietern, leichtgewichtig und trotzdem funktional.</p>
<h2 id="der-weg-zur-richtigen-technologie">Der Weg zur richtigen Technologie</h2>
<p>Ich war sofort von <a href="https://bun.sh/">Bun</a> begeistert – schnell, modern,
mit einer eleganten All-in-One-Philosophie.
Doch auf meinem Uberspace-Host scheiterte jede Bun-Version
an einer zu alten glibc.
Schade, aber Plan B war schnell gefunden: <a href="https://deno.land/">Deno</a>.
Deno versprach Sicherheit und moderne Features.
Allerdings war die Kompatibilität mit manchen Bibliotheken hakelig,
und ich stieß bei der Integration auf zu viele kleine Hürden.
Nach diesen Experimenten landete ich bei klassischem Node.js (mit pnpm).
Es ist robust, weit verbreitet und ließ sich auf meinem Host
ohne Probleme einrichten.
Mit TypeScript und ESM war ich trotzdem nah
an einer modernen Entwicklungsumgebung.</p>
<p>Für das Web-Framework fiel die Wahl auf <a href="https://hono.dev/">Hono</a>.
Es ist klein, schnell und hat eine klare, intuitive API – perfekt für ein
Projekt, das keinen Overhead verträgt.
Als Template-Engine wählte ich <a href="https://eta.js.org/">Eta</a>, weil sie
minimalistisch ist, eine einfache Syntax bietet und mir volle Kontrolle über
die Templates gibt.
Für die Datenhaltung experimentierte ich zunächst mit Markdown-Dateien im
Dateisystem.
Das war simpel, aber unpraktisch, sobald es um dynamische Inhalte oder
Metadaten ging.
Also wechselte ich zu <a href="https://github.com/typicode/lowdb">LowDB</a>, einem
JSON-Datei-Store, der sich schnell einbinden ließ und für ein
Single-Instance-Projekt wie dieses ideal ist.</p>
<p>Das Design hielt ich mit <a href="https://tailwindcss.com/">Tailwind CSS</a> bewusst schlicht.
Tailwind ermöglicht es,
schnell ansprechende Layouts zu bauen,
ohne sich in CSS-Details zu verlieren.
Für die Umwandlung von Markdown in sauberes HTML
kam <a href="https://markdown-it.github.io/">Markdown-It</a> zum Einsatz,
das zuverlässig und flexibel ist.</p>
<h2 id="technik-stack">Technik Stack</h2>
<ul>
<li><strong>Runtime</strong>: Node.js (ESM + TypeScript) – robust und kompatibel</li>
<li><strong>Framework</strong>: Hono – schlank, schnell, mit klarer API</li>
<li><strong>Templates</strong>: Eta – minimalistisch und leicht kontrollierbar</li>
<li><strong>Datenhaltung</strong>: LowDB – JSON-Datei-Store, ideal für kleine Projekte</li>
<li><strong>Styling</strong>: Tailwind CSS (CLI v4) – für ein sauberes, modernes Design</li>
<li><strong>Markdown</strong>: Markdown-It – für zuverlässige Markdown-zu-HTML-Konvertierung</li>
<li><strong>Sicherheit</strong>: CSRF-Schutz, Rate Limiting, sichere Cookies</li>
<li><strong>Build/Deploy</strong>: <code>tsc</code>, Tailwind CLI, rsync-Deploy mit Backup</li>
<li><strong>Performance</strong>: Statische Assets mit Cache-Headers, minimale JS-Bundles</li>
</ul>
<h2 id="herausforderungen-und-lösungen">Herausforderungen und Lösungen</h2>
<p>Die größte Herausforderung war,
die Balance zwischen Einfachheit und Funktionalität zu finden.
Zum Beispiel war die Entscheidung für LowDB nicht trivial:
Ich hatte zunächst überlegt,
eine richtige Datenbank wie PostgreSQL einzusetzen.
Aber für ein kleines Projekt mit überschaubarem Datenvolumen
wäre das Overkill gewesen.
SQLite schied leider auch aus, weil Uberspace keine Unterstützung
aufgrund von fehlenden Bibliotheken bot.
LowDB bot die perfekte Mischung aus Flexibilität und Einfachheit,
auch wenn ich für größere Projekte vielleicht noch mal umdenken würde.</p>
<h2 id="sicherheit-und-betrieb">Sicherheit und Betrieb</h2>
<p>Sicherheit war mir von Anfang an wichtig.
Sessions werden im Speicher gehalten,
was bei einem Server-Neustart zu einem Logout führt –
für mein kleines Projekt akzeptabel.
Cookies sind sicher konfiguriert
(<code>HttpOnly</code>, <code>SameSite</code>, <code>Secure</code> in Produktion),
und ein CSRF-Schutz sichert Login- und Admin-Formulare ab.
Um Brute-Force-Attacken vorzubeugen,
habe ich ein einfaches IP-basiertes Rate Limiting implementiert.
Es ist nicht perfekt,
sollte für meine Zwecke aber ausreichen.</p>
<p>Der Build-Prozess ist bewusst schlank:
TypeScript wird mit <code>tsc</code> kompiliert,
Tailwind CSS über die CLI gebaut.
Die Artefakte werden mit <code>rsync</code> auf den Server übertragen,
mit einem automatischen Backup vor jedem Deploy.
Um CSS-Cache-Probleme zu vermeiden,
nutze ich Cache-Busting durch Versions-Hashes in den Dateinamen.
Das sorgt dafür,
dass Nutzer immer die aktuelle Version der Stylesheets laden,
ohne dass ich manuell eingreifen muss.</p>
<h2 id="ausblick-was-kommt-noch">Ausblick: Was kommt noch?</h2>
<p>Das Projekt ist jetzt stabil, aber es gibt noch Ideen für die Zukunft.
Erst heute habe ich die RSS-Feed Funktion implementiert.
Eine Kommentarfunktion wäre auch spannend,
aber ich müsste entscheiden, wie ich das ohne großen Verwaltungsaufwand umsetze.
Von daher gibt es erst einmal keine Kommentare.
Wenn mir jemand etwas schreiben möchte,
gibt es genügend Möglichkeiten, dies zu tun.
Außerdem spiele ich mit dem Gedanken,
Beiträge nach Veröffentlichung automatisiert auch ins Fediverse zu pushen.</p>
<h2 id="fazit">Fazit</h2>
<p>Aus einer kleinen Idee ist in kurzer Zeit ein stabiler,
sicherer Microblog geworden – bewusst schlank gebaut,
aber erweiterbar.
Hono, Eta und LowDB haben sich als hervorragende Kombi
für ein leichtgewichtiges Projekt erwiesen.
Die Entscheidung für Node.js und TypeScript
hat sich als pragmatisch und zukunftssicher herausgestellt.
Am meisten freut mich, dass ich jetzt eine Plattform habe,
die genau das tut, was ich wollte: kurze, fokussierte Inhalte teilen,
ohne Overhead und unabhängig von großen Anbietern.
Es war ein Lernprozess, aber jeder Schritt –
von den gescheiterten Bun-Experimenten
bis zur finalen Deploy-Strategie – hat sich gelohnt.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Tailwind CSS v4 IntelliSense für `.eta`-Dateien in VS Code aktivieren</title>
      <link>https://cbrueggenolte.de/tailwind-css-v4-intellisense-fuer-eta-dateien-in-vs-code-aktivieren/</link>
      <pubDate>Thu, 24 Jul 2025 00:00:00 +0200</pubDate><author>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</author>
      <guid>https://cbrueggenolte.de/tailwind-css-v4-intellisense-fuer-eta-dateien-in-vs-code-aktivieren/</guid>
      <description>&lt;p&gt;Mit der Veröffentlichung von &lt;strong&gt;Tailwind CSS v4&lt;/strong&gt; hat sich einiges geändert – unter anderem die Art, wie Konfiguration und Styles gehandhabt werden. Wenn du &lt;code&gt;.eta&lt;/code&gt;-Dateien (&lt;a href=&#34;https://eta.js.org/&#34;&gt;eta&lt;/a&gt;-Templates) verwendest, bekommst du möglicherweise &lt;strong&gt;keine IntelliSense-Unterstützung&lt;/strong&gt; in VS Code. Hier zeige ich dir, wie du das behebst.&lt;/p&gt;&#xA;&lt;h2 id=&#34;voraussetzungen&#34;&gt;Voraussetzungen&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;VS Code&lt;/strong&gt; installiert&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Tailwind CSS v4&lt;/strong&gt; korrekt eingerichtet&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Tailwind CSS IntelliSense Extension&lt;/strong&gt; installiert&#xA;👉 &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss&#34;&gt;Hier herunterladen&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;schritt-1-vscodesettingsjson-konfigurieren&#34;&gt;Schritt 1: &lt;code&gt;.vscode/settings.json&lt;/code&gt; konfigurieren&lt;/h2&gt;&#xA;&lt;p&gt;Erstelle oder bearbeite die Datei &lt;code&gt;.vscode/settings.json&lt;/code&gt; in deinem Projektordner und füge Folgendes hinzu:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Mit der Veröffentlichung von <strong>Tailwind CSS v4</strong> hat sich einiges geändert – unter anderem die Art, wie Konfiguration und Styles gehandhabt werden. Wenn du <code>.eta</code>-Dateien (<a href="https://eta.js.org/">eta</a>-Templates) verwendest, bekommst du möglicherweise <strong>keine IntelliSense-Unterstützung</strong> in VS Code. Hier zeige ich dir, wie du das behebst.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li><strong>VS Code</strong> installiert</li>
<li><strong>Tailwind CSS v4</strong> korrekt eingerichtet</li>
<li><strong>Tailwind CSS IntelliSense Extension</strong> installiert
👉 <a href="https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss">Hier herunterladen</a></li>
</ul>
<hr>
<h2 id="schritt-1-vscodesettingsjson-konfigurieren">Schritt 1: <code>.vscode/settings.json</code> konfigurieren</h2>
<p>Erstelle oder bearbeite die Datei <code>.vscode/settings.json</code> in deinem Projektordner und füge Folgendes hinzu:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;tailwindCSS.includeLanguages&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nt">&#34;eta&#34;</span><span class="p">:</span> <span class="s2">&#34;html&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nt">&#34;tailwindCSS.experimental.configFile&#34;</span><span class="p">:</span> <span class="s2">&#34;src/styles/styles.css&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><ul>
<li><code>includeLanguages</code>: Sagt VS Code, dass <code>.eta</code>-Dateien wie HTML behandelt werden sollen.</li>
<li><code>experimental.configFile</code>: Zeigt auf deine zentrale Tailwind-CSS-Datei (z. B. <code>styles.css</code>), in der du Tailwind importierst.</li>
</ul>
<hr>
<h2 id="schritt-2-tailwind-in-stylescss-importieren">Schritt 2: Tailwind in <code>styles.css</code> importieren</h2>
<p>In Tailwind v4 sieht deine zentrale CSS-Datei typischerweise so aus:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">@</span><span class="k">import</span> <span class="s2">&#34;tailwindcss&#34;</span><span class="p">;</span></span></span></code></pre></div><p>Optional kannst du auch Plugins oder Custom Styles hinzufügen:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">@</span><span class="k">import</span> <span class="s2">&#34;tailwindcss&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">@</span><span class="k">import</span> <span class="s2">&#34;./custom.css&#34;</span><span class="p">;</span></span></span></code></pre></div><hr>
<h2 id="schritt-3-tailwindconfigjs-anpassen">Schritt 3: <code>tailwind.config.js</code> anpassen</h2>
<p>Stelle sicher, dass deine <code>.eta</code>-Dateien im <code>content</code>-Array enthalten sind:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">content</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;./views/**/*.eta&#34;</span><span class="p">,</span> <span class="s2">&#34;./src/**/*.js&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">theme</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">extend</span><span class="o">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">};</span></span></span></code></pre></div><hr>
<h2 id="schritt-4-vs-code-neu-starten">Schritt 4: VS Code neu starten</h2>
<p>Nach den Änderungen solltest du <strong>VS Code neu starten</strong>, damit die IntelliSense-Erweiterung die neuen Einstellungen erkennt. Das ist nicht immer erforderlich, aber in diesem Fall ist es ratsam.</p>
<hr>
<p>Jetzt solltest du in <code>.eta</code>-Dateien:</p>
<ul>
<li>Autovervollständigung für Tailwind-Klassen erhalten</li>
<li>Hover-Informationen und Vorschauen sehen</li>
<li>Fehler bei ungültigen Klassen erkennen</li>
</ul>
<hr>
<p>Viel Spaß beim Coden mit Tailwind v4 und Eta! 🎨✨</p>
]]></content:encoded>
    </item>
    <item>
      <title>Geo‑Daten dynamisch mit Leaflet.js laden</title>
      <link>https://cbrueggenolte.de/geo-daten-dynamisch-mit-leaflet-js-laden/</link>
      <pubDate>Tue, 08 Jul 2025 00:00:00 +0200</pubDate><author>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</author>
      <guid>https://cbrueggenolte.de/geo-daten-dynamisch-mit-leaflet-js-laden/</guid>
      <description>&lt;p&gt;Hier ein Überblick über die wichtigsten Änderungen:&lt;/p&gt;&#xA;&lt;p&gt;Statt die Layer-Reihenfolge mit &lt;code&gt;bringToFront()&lt;/code&gt; zu steuern, haben ich nun Leaflet-Panes benutzt.&#xA;So kann ich die Z-Index-Reihenfolge der Layer exakt bestimmen:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Länder liegen im Pane &lt;code&gt;countriesPane&lt;/code&gt; (zIndex 400)&lt;/li&gt;&#xA;&lt;li&gt;POIs im Pane &lt;code&gt;poiPane&lt;/code&gt; (zIndex 500)&lt;/li&gt;&#xA;&lt;li&gt;Hauptstädte/Capitals im Pane &lt;code&gt;capitalsPane&lt;/code&gt; (zIndex 600)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Das sorgt für eine saubere und vorhersehbare Darstellung – unabhängig von Interaktionen oder Nachladen von Daten.&#xA;Vorallem aber wird bei einem Hover-Over über Länder die Darstellung der POIs nicht mehr beeinflusst.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Hier ein Überblick über die wichtigsten Änderungen:</p>
<p>Statt die Layer-Reihenfolge mit <code>bringToFront()</code> zu steuern, haben ich nun Leaflet-Panes benutzt.
So kann ich die Z-Index-Reihenfolge der Layer exakt bestimmen:</p>
<ul>
<li>Länder liegen im Pane <code>countriesPane</code> (zIndex 400)</li>
<li>POIs im Pane <code>poiPane</code> (zIndex 500)</li>
<li>Hauptstädte/Capitals im Pane <code>capitalsPane</code> (zIndex 600)</li>
</ul>
<p>Das sorgt für eine saubere und vorhersehbare Darstellung – unabhängig von Interaktionen oder Nachladen von Daten.
Vorallem aber wird bei einem Hover-Over über Länder die Darstellung der POIs nicht mehr beeinflusst.</p>
<h3 id="neue-mapjs-datei">Neue map.js Datei</h3>
<p>Hier die neue <code>map.js</code> Datei, die die Ländergrenzen, POIs und Hauptstädte verwaltet:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kr">const</span> <span class="nx">map</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="s1">&#39;map&#39;</span><span class="p">).</span><span class="nx">setView</span><span class="p">([</span><span class="mf">52.0705</span><span class="p">,</span> <span class="mf">4.707</span><span class="p">],</span> <span class="mi">6</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">(</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="p">{}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kd">var</span> <span class="nx">capitalsIcon</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">divIcon</span><span class="p">({</span> <span class="nx">className</span><span class="o">:</span> <span class="s1">&#39;capitals-icon&#39;</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="c1">// Create custom panes for z-order control
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="nx">map</span><span class="p">.</span><span class="nx">createPane</span><span class="p">(</span><span class="s1">&#39;countriesPane&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="nx">map</span><span class="p">.</span><span class="nx">getPane</span><span class="p">(</span><span class="s1">&#39;countriesPane&#39;</span><span class="p">).</span><span class="nx">style</span><span class="p">.</span><span class="nx">zIndex</span> <span class="o">=</span> <span class="mi">400</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="nx">map</span><span class="p">.</span><span class="nx">createPane</span><span class="p">(</span><span class="s1">&#39;poiPane&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="nx">map</span><span class="p">.</span><span class="nx">getPane</span><span class="p">(</span><span class="s1">&#39;poiPane&#39;</span><span class="p">).</span><span class="nx">style</span><span class="p">.</span><span class="nx">zIndex</span> <span class="o">=</span> <span class="mi">500</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="nx">map</span><span class="p">.</span><span class="nx">createPane</span><span class="p">(</span><span class="s1">&#39;capitalsPane&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="nx">map</span><span class="p">.</span><span class="nx">getPane</span><span class="p">(</span><span class="s1">&#39;capitalsPane&#39;</span><span class="p">).</span><span class="nx">style</span><span class="p">.</span><span class="nx">zIndex</span> <span class="o">=</span> <span class="mi">600</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1">// Highlight-Funktionen
</span></span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="kd">function</span> <span class="nx">highlightFeature</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">  <span class="kr">const</span> <span class="nx">layer</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">  <span class="nx">layer</span><span class="p">.</span><span class="nx">setStyle</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="nx">weight</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;#ff0000&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="nx">fillOpacity</span><span class="o">:</span> <span class="mf">0.7</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">  <span class="nx">layer</span><span class="p">.</span><span class="nx">bringToFront</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="kd">function</span> <span class="nx">resetHighlight</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">  <span class="nx">geojson</span><span class="p">.</span><span class="nx">resetStyle</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">  <span class="nx">overlayGroup</span><span class="p">.</span><span class="nx">bringToFront</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="c1">// Layer groups for better control
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="kr">const</span> <span class="nx">baseGroup</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">layerGroup</span><span class="p">().</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="kr">const</span> <span class="nx">overlayGroup</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">layerGroup</span><span class="p">().</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="kr">const</span> <span class="nx">capitalsLayer</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">layerGroup</span><span class="p">().</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="c1">// Country boundaries with outlines and optional fill
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="kd">let</span> <span class="nx">geojson</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">
</span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="c1">// Call layers in correct order
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="kd">function</span> <span class="nx">initLayers</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">  <span class="nx">addCountryLayer</span><span class="p">(</span><span class="nx">addPOILayer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="kd">function</span> <span class="nx">addCountryLayer</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">  <span class="nx">fetch</span><span class="p">(</span><span class="s1">&#39;/data/vm_medium_countries.geo.json&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">baseGroup</span><span class="p">)</span> <span class="nx">baseGroup</span><span class="p">.</span><span class="nx">clearLayers</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">      <span class="nx">geojson</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="nx">pane</span><span class="o">:</span> <span class="s1">&#39;countriesPane&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="nx">style</span><span class="o">:</span> <span class="p">(</span><span class="nx">feature</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">          <span class="nx">color</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">color</span> <span class="o">||</span> <span class="s1">&#39;#333&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">          <span class="nx">weight</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">          <span class="nx">opacity</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">          <span class="nx">fillColor</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">fillColor</span> <span class="o">||</span> <span class="s1">&#39;#eee&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">          <span class="nx">fillOpacity</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="p">}),</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="nx">onEachFeature</span><span class="o">:</span> <span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">layer</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">          <span class="nx">layer</span><span class="p">.</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">          <span class="nx">layer</span><span class="p">.</span><span class="nx">on</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">            <span class="nx">mouseover</span><span class="o">:</span> <span class="nx">highlightFeature</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">            <span class="nx">mouseout</span><span class="o">:</span> <span class="nx">resetHighlight</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">          <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">      <span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">baseGroup</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="nx">callback</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="kd">function</span> <span class="nx">addPOILayer</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">  <span class="nx">fetch</span><span class="p">(</span><span class="s1">&#39;/data/poi.geo.json&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">overlayGroup</span><span class="p">)</span> <span class="nx">overlayGroup</span><span class="p">.</span><span class="nx">clearLayers</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">      <span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="nx">pane</span><span class="o">:</span> <span class="s1">&#39;poiPane&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="nx">style</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">feature</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">          <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">)</span> <span class="k">return</span> <span class="p">{};</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">          <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">            <span class="nx">color</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">stroke</span> <span class="o">||</span> <span class="s1">&#39;#3388ff&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="nx">weight</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">[</span><span class="s1">&#39;stroke-width&#39;</span><span class="p">]</span> <span class="o">||</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="nx">opacity</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">[</span><span class="s1">&#39;stroke-opacity&#39;</span><span class="p">]</span> <span class="o">||</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="nx">fillColor</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">fill</span> <span class="o">||</span> <span class="s1">&#39;#3388ff&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">            <span class="nx">fillOpacity</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">[</span><span class="s1">&#39;fill-opacity&#39;</span><span class="p">]</span> <span class="o">||</span> <span class="mf">0.2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="nx">onEachFeature</span><span class="o">:</span> <span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">layer</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">          <span class="k">if</span> <span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span> <span class="o">&amp;&amp;</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="nx">layer</span><span class="p">.</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">      <span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">overlayGroup</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="c1">// Capitals logic
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="kd">function</span> <span class="nx">clearCapitals</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">  <span class="nx">capitalsLayer</span><span class="p">.</span><span class="nx">clearLayers</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;capitals-btn&#39;</span><span class="p">).</span><span class="nx">onclick</span> <span class="o">=</span> <span class="kr">async</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">  <span class="nx">clearCapitals</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">  <span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="s1">&#39;/api/capitals&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">  <span class="kr">const</span> <span class="nx">capitals</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">  <span class="nx">capitals</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">capital</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="kr">const</span> <span class="nx">marker</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">marker</span><span class="p">([</span><span class="nx">capital</span><span class="p">.</span><span class="nx">lat</span><span class="p">,</span> <span class="nx">capital</span><span class="p">.</span><span class="nx">lng</span><span class="p">],</span> <span class="p">{</span> <span class="nx">icon</span><span class="o">:</span> <span class="nx">capitalsIcon</span><span class="p">,</span> <span class="nx">pane</span><span class="o">:</span> <span class="s1">&#39;capitalsPane&#39;</span> <span class="p">}).</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">capital</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="nx">capitalsLayer</span><span class="p">.</span><span class="nx">addLayer</span><span class="p">(</span><span class="nx">marker</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">  <span class="nx">capitalsLayer</span><span class="p">.</span><span class="nx">bringToFront</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;remove-capitals-btn&#39;</span><span class="p">).</span><span class="nx">onclick</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">  <span class="nx">clearCapitals</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="nx">initLayers</span><span class="p">();</span></span></span></code></pre></div><p>Die Layer für Länder, POIs und Hauptstädte werden jetzt in eigenen LayerGroups verwaltet. Das macht das Nachladen, Entfernen und Aktualisieren von Daten viel einfacher und übersichtlicher.</p>
<h3 id="neue-html-struktur">Neue HTML-Struktur</h3>
<p>Hier die neue <code>index.html</code> Datei, die die Karte und die Buttons enthält:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;de&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Minimal Leaflet Karte<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;styles.css&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/public/leaflet/leaflet.css&#34;</span> <span class="na">crossorigin</span><span class="o">=</span><span class="s">&#34;&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">&lt;</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Leaflet Demo Map<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="p">&lt;</span><span class="nt">p</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;subtitle&#34;</span><span class="p">&gt;</span>A simple demonstration of a Leaflet map<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">      <span class="p">&lt;</span><span class="nt">button</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;capitals-btn&#34;</span><span class="p">&gt;</span>Load Capitals via API<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">      <span class="p">&lt;</span><span class="nt">button</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;remove-capitals-btn&#34;</span><span class="p">&gt;</span>Remove Capitals<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;map&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;/public/leaflet/leaflet.js&#34;</span> <span class="na">crossorigin</span><span class="o">=</span><span class="s">&#34;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;map.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span></span></span></code></pre></div><h3 id="neue-styles">Neue Styles</h3>
<p>Dazu passend die neuen Styles in der <code>styles.css</code> Datei, für die Karte und die Buttons:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c">/* Header ersetzen */</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nt">header</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="mh">#f8f9fa</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">padding</span><span class="p">:</span> <span class="mi">1</span><span class="kt">em</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">text-align</span><span class="p">:</span> <span class="kc">center</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="k">border-bottom</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="mh">#ccc</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c">/* und in der Map die Werte für &#39;margin&#39; neu setzen */</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">#</span><span class="nn">map</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="k">margin</span><span class="p">:</span> <span class="mi">1</span><span class="kt">rem</span> <span class="mi">1</span><span class="kt">rem</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nt">button</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="k">padding</span><span class="p">:</span> <span class="mf">0.5</span><span class="kt">em</span> <span class="mi">1</span><span class="kt">em</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="k">margin</span><span class="p">:</span> <span class="mf">0.5</span><span class="kt">em</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="k">border</span><span class="p">:</span> <span class="kc">none</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="k">border-radius</span><span class="p">:</span> <span class="mi">4</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="mh">#007bff</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="k">color</span><span class="p">:</span> <span class="kc">white</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="k">cursor</span><span class="p">:</span> <span class="kc">pointer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">.</span><span class="nc">capitals-icon</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="mh">#007bff</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">  <span class="k">border-radius</span><span class="p">:</span> <span class="mi">50</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">24</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">24</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">display</span><span class="p">:</span> <span class="kc">flex</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="k">align-items</span><span class="p">:</span> <span class="kc">center</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="k">justify-content</span><span class="p">:</span> <span class="kc">center</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="neue-indexts-datei">Neue index.ts Datei</h3>
<p>Jetzt noch die <code>index.ts</code> Datei, die die API für die Hauptstädte bereitstellt:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">serve</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@hono/node-server&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Hono</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;hono&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">serveStatic</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@hono/node-server/serve-static&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Hono</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">// Serve static files from &#39;public&#39;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="s1">&#39;/public/*&#39;</span><span class="p">,</span> <span class="nx">serveStatic</span><span class="p">({</span> <span class="nx">root</span><span class="o">:</span> <span class="s1">&#39;./&#39;</span> <span class="p">}));</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// Serve static files from &#39;data&#39;
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="s1">&#39;/data/*&#39;</span><span class="p">,</span> <span class="nx">serveStatic</span><span class="p">({</span> <span class="nx">root</span><span class="o">:</span> <span class="s1">&#39;./&#39;</span> <span class="p">}));</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">// API endpoint for dynamic location data
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;/api/capitals&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="kr">const</span> <span class="nx">capitals</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">1</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Berlin&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">52.52</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">13.405</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">2</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Paris&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">48.8566</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">2.3522</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">3</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;London&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">51.5074</span><span class="p">,</span> <span class="nx">lng</span><span class="o">:</span> <span class="o">-</span><span class="mf">0.1278</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">4</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Madrid&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">40.4168</span><span class="p">,</span> <span class="nx">lng</span><span class="o">:</span> <span class="o">-</span><span class="mf">3.7038</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">5</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Rome&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">41.9028</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">12.4964</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">6</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Vienna&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">48.2082</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">16.3738</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">7</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Amsterdam&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">52.3676</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">4.9041</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">8</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Brussels&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">50.8503</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">4.3517</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">9</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Copenhagen&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">55.6761</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">12.5683</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">10</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Stockholm&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">59.3293</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">18.0686</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">11</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Oslo&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">59.9139</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">10.7522</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">12</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Helsinki&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">60.1695</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">24.9354</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">13</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Lisbon&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">38.7223</span><span class="p">,</span> <span class="nx">lng</span><span class="o">:</span> <span class="o">-</span><span class="mf">9.1393</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">14</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Dublin&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">53.3498</span><span class="p">,</span> <span class="nx">lng</span><span class="o">:</span> <span class="o">-</span><span class="mf">6.2603</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">15</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Warsaw&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">52.2297</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">21.0122</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">16</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Prague&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">50.0755</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">14.4378</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">17</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Budapest&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">47.4979</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">19.0402</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">18</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Bratislava&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">48.1482</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">17.1067</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">19</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Ljubljana&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">46.0569</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">14.5051</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="p">{</span> <span class="nx">id</span>: <span class="kt">20</span><span class="p">,</span> <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Zagreb&#39;</span><span class="p">,</span> <span class="nx">lat</span>: <span class="kt">45.815</span><span class="p">,</span> <span class="nx">lng</span>: <span class="kt">15.9819</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">  <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">capitals</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="c1">// Redirect root to public/index.html
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">c</span><span class="p">.</span><span class="nx">html</span><span class="p">(</span><span class="s1">&#39;&lt;script&gt;location.href=&#34;https://cbrueggenolte.de/public/index.html&#34;&lt;/script&gt;&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1">// Start the server
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="nx">serve</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="nx">fetch</span>: <span class="kt">app.fetch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="nx">port</span>: <span class="kt">3000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">  <span class="p">(</span><span class="nx">info</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Server is running on http://localhost:</span><span class="si">${</span><span class="nx">info</span><span class="p">.</span><span class="nx">port</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="p">);</span></span></span></code></pre></div><p>Die API liefert eine Liste von Hauptstädten mit ihren Koordinaten. Diese werden später in der Karte als Marker angezeigt. Und mit den neu hinzugefügten Buttons kann der Nutzer die Hauptstädte dynamisch laden und entfernen.</p>
<h3 id="neue-daten">Neue Daten</h3>
<p>Und ins Verzeichnis <code>data</code> kommt neben der GeoJSON-Datei <code>vm_medium_countries.geo.json</code>, die du von <a href="https://geojson-maps.kyd.au/">geojson-maps.kyd.au</a> heruntergeladen hast, die neue Version der <a href="/geo-daten-dynamisch-mit-leaflet-js-laden/poi.geo.json">`poi.geo.json</a> Datei welche neue POI enthält.</p>
<p>Wie in der <code>map.js</code> Datei zu sehen ist, werden die Information zur Darstellung der POIs jetzt direkt aus den GeoJSON-Properties (<code>stroke</code>, <code>stroke-width</code>, <code>fill</code>, etc.) übernommen. So kann jeder POI individuell gestaltet werden – z.B. für besondere Städte oder Flächen.</p>
<h2 id="fazit">Fazit</h2>
<p>Durch die Nutzung von LayerGroups, Panes und klaren Funktionsaufrufen ist der Code jetzt deutlich modularer und leichter zu erweitern.</p>
<p>Wenn alles geklappt hat, solltest du nun eine funktionierende Leaflet-Karte haben, die Ländergrenzen anzeigt und dynamisch POIs und Hauptstädte laden kann und ungefähr so aussieht:</p>
]]></content:encoded>
    </item>
    <item>
      <title>Minimalistische Offline-Leaflet-Karte mit Node.js &amp; Hono</title>
      <link>https://cbrueggenolte.de/minimalistische-offline-leaflet-karte-mit-node-js-hono/</link>
      <pubDate>Mon, 07 Jul 2025 00:00:00 +0200</pubDate><author>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</author>
      <guid>https://cbrueggenolte.de/minimalistische-offline-leaflet-karte-mit-node-js-hono/</guid>
      <description>&lt;p&gt;Für ein Projekt benötigte ich eine einfache, offlinefähige &lt;a href=&#34;https://leafletjs.com/&#34;&gt;Leaflet&lt;/a&gt;-Karte, die nur Ländergrenzen und dynamisch injizierte Punkte anzeigt. Hier ist eine Schritt-für-Schritt-Anleitung, wie du so etwas mit Node.js und &lt;a href=&#34;https://hono.dev&#34;&gt;Hono&lt;/a&gt; umsetzen kannst.&lt;/p&gt;&#xA;&lt;p&gt;Hono ist ein leichtgewichtiger Web-Framework für Node.js, das sich hervorragend für einfache APIs eignet.&#xA;Wenn wir fertig sind, haben wir eine minimalistische Leaflet-Karte, die offline funktioniert und die Ländergrenzen sowie dynamisch geladene Punkte anzeigt.&lt;/p&gt;&#xA;&lt;h2 id=&#34;schritt-1-projekt-initialisieren&#34;&gt;Schritt 1: Projekt initialisieren&lt;/h2&gt;&#xA;&lt;p&gt;Erstelle ein neues Hono-Projekt:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Für ein Projekt benötigte ich eine einfache, offlinefähige <a href="https://leafletjs.com/">Leaflet</a>-Karte, die nur Ländergrenzen und dynamisch injizierte Punkte anzeigt. Hier ist eine Schritt-für-Schritt-Anleitung, wie du so etwas mit Node.js und <a href="https://hono.dev">Hono</a> umsetzen kannst.</p>
<p>Hono ist ein leichtgewichtiger Web-Framework für Node.js, das sich hervorragend für einfache APIs eignet.
Wenn wir fertig sind, haben wir eine minimalistische Leaflet-Karte, die offline funktioniert und die Ländergrenzen sowie dynamisch geladene Punkte anzeigt.</p>
<h2 id="schritt-1-projekt-initialisieren">Schritt 1: Projekt initialisieren</h2>
<p>Erstelle ein neues Hono-Projekt:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">npm create hono@latest</span></span></code></pre></div><p>Folge den Anweisungen im Terminal und wähle die folgenden Optionen:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">create-hono version 0.19.2
</span></span><span class="line"><span class="ln">2</span><span class="cl">✔ Target directory leaflet-hono
</span></span><span class="line"><span class="ln">3</span><span class="cl">✔ Which template <span class="k">do</span> you want to use? nodejs
</span></span><span class="line"><span class="ln">4</span><span class="cl">✔ Do you want to install project dependencies? Yes
</span></span><span class="line"><span class="ln">5</span><span class="cl">✔ Which package manager <span class="k">do</span> you want to use? npm
</span></span><span class="line"><span class="ln">6</span><span class="cl">✔ Cloning the template
</span></span><span class="line"><span class="ln">7</span><span class="cl">✔ Installing project dependencies
</span></span><span class="line"><span class="ln">8</span><span class="cl">🎉 Copied project files
</span></span><span class="line"><span class="ln">9</span><span class="cl">Get started with: <span class="nb">cd</span> leaflet-hono</span></span></code></pre></div><p>Wir installieren hier auch Leaflet um aus dem node_modules Verzeichnis auf die Leaflet-Bibliothek zugreifen zu können.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> leaflet-hono
</span></span><span class="line"><span class="ln">2</span><span class="cl">npm install leaflet</span></span></code></pre></div><hr>
<h2 id="schritt-2-projektstruktur-anlegen">Schritt 2: Projektstruktur anlegen</h2>





<pre tabindex="0"><code>leaflet-hono/
├── data/
├── public/
│   ├── leaflet/
│   │   ├── images/ ( &lt;&lt; siehe unten für Details &gt;&gt; )
│   │   ├── leaflet.css
│   │   ├── leaflet.js
│   │   └── leaflet.js.map
├── src/
│   ├── index.ts</code></pre><ul>
<li><strong>data/</strong> enthält heruntergeladene GeoJSON-Dateien (z. B. Natural Earth 50m).</li>
<li><strong>public/</strong> enthält Frontend-Dateien.</li>
<li><strong>server.js</strong> enthält den Hono-Server und API-Endpunkte.</li>
<li><code>public/leaflet/images</code> Ordner mit Leaflet-Bildern aus dem node_modules/leaflet/dist/images Verzeichnis)</li>
</ul>
<p>Kopiere die Leaflet Dateien aus <code>node_modules/leaflet/dist/</code> in den <code>public/leaflet/</code> Ordner:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">mkdir -p public/leaflet/images
</span></span><span class="line"><span class="ln">2</span><span class="cl">cp node_modules/leaflet/dist/images/* public/leaflet/images/
</span></span><span class="line"><span class="ln">3</span><span class="cl">cp node_modules/leaflet/dist/leaflet.css public/leaflet/
</span></span><span class="line"><span class="ln">4</span><span class="cl">cp node_modules/leaflet/dist/leaflet.js public/leaflet/
</span></span><span class="line"><span class="ln">5</span><span class="cl">cp node_modules/leaflet/dist/leaflet.js.map public/leaflet/</span></span></code></pre></div><hr>
<h2 id="schritt-3-app-starten">Schritt 3: App starten</h2>
<p>Bevor wir weiter machen, stellen wir sicher, dass wir unsere App einmal starten können. Starte dazu den Hono-Server:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">npm run dev</span></span></code></pre></div><p>Wenn alles richtig eingerichtet ist, solltest du die Standard-Hono-Seite unter <strong><a href="http://localhost:3000">http://localhost:3000</a></strong> sehen.</p>
<hr>
<h2 id="schritt-4-srcindexts">Schritt 4: <code>src/index.ts</code></h2>
<p>Nun erweitern wir die <code>src/index.ts</code> Datei, um die Daten aus dem <code>public</code> und <code>data</code> Ordner bereitzustellen.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">serve</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;@hono/node-server&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Hono</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;hono&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">serveStatic</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;@hono/node-server/serve-static&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Hono</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1">// Serve static files from &#39;public&#39;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="s2">&#34;/public/*&#34;</span><span class="p">,</span> <span class="nx">serveStatic</span><span class="p">({</span> <span class="nx">root</span><span class="o">:</span> <span class="s2">&#34;./&#34;</span> <span class="p">}));</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">// Serve static files from &#39;data&#39;
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="s2">&#34;/data/*&#34;</span><span class="p">,</span> <span class="nx">serveStatic</span><span class="p">({</span> <span class="nx">root</span><span class="o">:</span> <span class="s2">&#34;./&#34;</span> <span class="p">}));</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">,</span> <span class="p">(</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">text</span><span class="p">(</span><span class="s2">&#34;Hello Hono!&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nx">serve</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="nx">fetch</span>: <span class="kt">app.fetch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nx">port</span>: <span class="kt">3000</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="p">(</span><span class="nx">info</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Server is running on http://localhost:</span><span class="si">${</span><span class="nx">info</span><span class="p">.</span><span class="nx">port</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">);</span></span></span></code></pre></div><p>Zum Testen erstellen wir eine einfache Test-Datei, die wir später in der Karte verwenden werden. Erstelle dazu eine Datei <code>data/poi.geo.json</code> mit folgendem Inhalt:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;FeatureCollection&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nt">&#34;features&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Feature&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="nt">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Den Haag&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="nt">&#34;geometry&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Point&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nt">&#34;coordinates&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mf">4.3007</span><span class="p">,</span> <span class="mf">52.0705</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Feature&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">      <span class="nt">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Utrecht&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">      <span class="nt">&#34;geometry&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Point&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="nt">&#34;coordinates&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mf">5.09</span><span class="p">,</span> <span class="mf">52.0907</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Wenn nun <code>&lt;https://localhost:3000/data/poi.geo.json&gt;</code> aufgerufen wird, sollte die GeoJSON-Datei angezeigt werden.</p>
<hr>
<h2 id="schritt-5-publicindexhtml-und-publicstylescss">Schritt 5: <code>public/index.html</code> und <code>public/styles.css</code></h2>
<p>In die <code>index.html</code> Datei fügen wir die grundlegende Struktur für die Leaflet-Karte ein. Erstelle die Datei <code>public/index.html</code> mit folgendem Inhalt:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">&lt;</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;de&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Minimal Leaflet Karte<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;styles.css&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/public/leaflet/leaflet.css&#34;</span> <span class="na">crossorigin</span><span class="o">=</span><span class="s">&#34;&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">&lt;</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Leaflet Demo Map<span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="p">&lt;</span><span class="nt">p</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;subtitle&#34;</span><span class="p">&gt;</span>A simple demonstration of a Leaflet map<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;map&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;/public/leaflet/leaflet.js&#34;</span> <span class="na">crossorigin</span><span class="o">=</span><span class="s">&#34;&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;map.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span></span></span></code></pre></div><p>Um das nicht ganz so langweilig zu gestalten, fügen wir noch etwas CSS hinzu. Erstelle die Datei <code>public/styles.css</code> mit folgendem Inhalt:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">html</span><span class="o">,</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="k">margin</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">padding</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">width</span><span class="p">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">%</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="k">font-family</span><span class="p">:</span> <span class="n">Arial</span><span class="p">,</span> <span class="kc">sans-serif</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="k">height</span><span class="p">:</span> <span class="mi">100</span><span class="kt">vh</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="k">box-sizing</span><span class="p">:</span> <span class="kc">border-box</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="k">display</span><span class="p">:</span> <span class="kc">flex</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="k">flex-direction</span><span class="p">:</span> <span class="kc">column</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nt">h1</span><span class="o">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">.</span><span class="nc">subtitle</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="k">margin</span><span class="p">:</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mf">0.5</span><span class="kt">em</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="k">text-align</span><span class="p">:</span> <span class="kc">center</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="p">.</span><span class="nc">subtitle</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">  <span class="k">font-size</span><span class="p">:</span> <span class="mf">1.2</span><span class="kt">em</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="k">color</span><span class="p">:</span> <span class="mh">#666</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="k">margin-bottom</span><span class="p">:</span> <span class="mi">1</span><span class="kt">em</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="p">#</span><span class="nn">map</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">  <span class="k">flex</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">  <span class="k">margin</span><span class="p">:</span> <span class="mi">0</span> <span class="mi">2</span><span class="kt">vw</span> <span class="mi">2</span><span class="kt">vw</span> <span class="mi">2</span><span class="kt">vw</span><span class="p">;</span> <span class="c">/* margin around the map */</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="mh">#f0f0f0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">  <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="mh">#ccc</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">  <span class="k">border-radius</span><span class="p">:</span> <span class="mi">8</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><hr>
<h2 id="schritt-6-publicmapjs">Schritt 6: <code>public/map.js</code></h2>
<p>Was nun noch fehlt, ist die Logik, um die Karte zu initialisieren und die GeoJSON-Daten zu laden. Erstelle die Datei <code>public/map.js</code> mit folgendem Inhalt:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">const</span> <span class="nx">map</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="s2">&#34;map&#34;</span><span class="p">).</span><span class="nx">setView</span><span class="p">([</span><span class="mf">52.0705</span><span class="p">,</span> <span class="mf">4.707</span><span class="p">],</span> <span class="mi">11</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">(</span><span class="s2">&#34;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nx">maxZoom</span><span class="o">:</span> <span class="mi">19</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">fetch</span><span class="p">(</span><span class="s2">&#34;/data/poi.geo.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">(</span><span class="nx">data</span><span class="p">).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="p">});</span></span></span></code></pre></div><p>Hiermit initialisieren wir die Karte auf den Koordinaten zwischen Den Haag und Utrecht und laden die GeoJSON-Daten von unserem Hono-Server. Die Punkte werden dann als Layer zur Karte hinzugefügt.</p>
<p>Wenn alles geklappt hat, sollte es ungefähr so aussehen:</p>
<p><a href="/minimalistische-offline-leaflet-karte-mit-node-js-hono/leaflet-demo_hu_772b2bc636fdd6d6.webp"><img class="content-image" src="/minimalistische-offline-leaflet-karte-mit-node-js-hono/leaflet-demo_hu_8590a9d8d27efc5c.webp" alt="Screenshot Leaflet Karte im Browser" loading="lazy" width="1440" height="1129" /></a></p>
<hr>
<h2 id="schritt-7-osm-ersetzen-und-ländergrenzen-hinzufügen">Schritt 7: OSM ersetzen und Ländergrenzen hinzufügen</h2>
<p>Da wir aber eine offlinefähige Karte erstellen wollen, müssen wir die Ländergrenzen und andere Geodaten lokal bereitstellen. Wir verwenden dafür GeoJSON-Dateien von <a href="https://www.naturalearthdata.com/downloads/50m-cultural-vectors/">Natural Earth</a> die sich gut für kleinere Karten eignen.</p>
<p>Um die Ländergrenzen herunterzuladen, navigiere zu <a href="https://geojson-maps.kyd.au/">geojson-maps.kyd.au</a>, wähle &ldquo;Medium Resolution&rdquo; und aktiviere die Checkbox &ldquo;All&rdquo;. Die mittlere Auflösung ist für unsere Zwecke ausreichend und hat mit nur 4,5 MB eine handhabbare Größe. Die Option &ldquo;All&rdquo; stellt sicher, dass alle Ländergrenzen enthalten sind.</p>
<p><a href="/minimalistische-offline-leaflet-karte-mit-node-js-hono/download-geojson-countries_hu_737010276674c80d.webp"><img class="content-image" src="/minimalistische-offline-leaflet-karte-mit-node-js-hono/download-geojson-countries_hu_edc6e6d41c2b19c3.webp" alt="Leaflet Ländergrenzen Download" loading="lazy" width="1440" height="1151" /></a></p>
<p>Klicke anschließend auf &ldquo;Build Custom GeoJSON&rdquo; in der rechten unteren Ecke, um die Datei herunterzuladen. Diese Datei enthält alle Ländergrenzen im GeoJSON-Format, das mit Leaflet kompatibel ist. Speichere die Datei im <code>data</code>-Ordner unter dem Namen <code>vm_medium_countries.geo.json</code>.</p>
<p>Anschließend erweitern wir die <code>public/map.js</code> Datei, um die Ländergrenzen zu laden und anzuzeigen:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">const</span> <span class="nx">map</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="s2">&#34;map&#34;</span><span class="p">).</span><span class="nx">setView</span><span class="p">([</span><span class="mf">52.0705</span><span class="p">,</span> <span class="mf">4.707</span><span class="p">],</span> <span class="mi">5</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="p">{}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">fetch</span><span class="p">(</span><span class="s2">&#34;/data/vm_medium_countries.geo.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="nx">style</span><span class="o">:</span> <span class="p">(</span><span class="nx">feature</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nx">color</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">color</span> <span class="o">||</span> <span class="s2">&#34;#333&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nx">weight</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="nx">opacity</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nx">fillColor</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">fillColor</span> <span class="o">||</span> <span class="s2">&#34;#eee&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="nx">fillOpacity</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">      <span class="p">}),</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">});</span></span></span></code></pre></div><p>Der Code initialisiert eine Leaflet-Karte ohne Hintergrund-Layer (also ohne Basiskarte) und zeigt die Ländergrenzen aus einer lokalen GeoJSON-Datei an.</p>
<ul>
<li>Die Karte wird auf Europa zentriert und auf Zoomstufe 5 gesetzt.</li>
<li>Es wird ein leerer Tile-Layer hinzugefügt, damit Leaflet korrekt funktioniert, aber keine Online-Kartenkacheln geladen werden.</li>
<li>Die Ländergrenzen werden per <code>fetch</code> aus der Datei <code>/data/vm_medium_countries.geo.json</code> geladen.</li>
<li>Mit <code>L.geoJSON</code> werden die Länder als Layer zur Karte hinzugefügt. Die Darstellung (Linienfarbe, Füllfarbe) kann für jedes Land individuell über die Eigenschaften im GeoJSON gesteuert werden (<code>feature.properties.color</code> und <code>feature.properties.fillColor</code>). Gibt es keine Angabe, werden Standardwerte verwendet.</li>
</ul>
<p>Um das ganze noch etwas abzurunden können wir die Hintergrundfarbe der Karte in der <code>public/styles.css</code> Datei anpassen:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">#</span><span class="nn">map</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">flex</span><span class="p">:</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">margin</span><span class="p">:</span> <span class="mi">0</span> <span class="mi">2</span><span class="kt">vw</span> <span class="mi">2</span><span class="kt">vw</span> <span class="mi">2</span><span class="kt">vw</span><span class="p">;</span> <span class="c">/* margin around the map */</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="mh">#87ceeb</span><span class="p">;</span> <span class="c">/* light blue, resembling the sea */</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="k">border</span><span class="p">:</span> <span class="mi">1</span><span class="kt">px</span> <span class="kc">solid</span> <span class="mh">#ccc</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="k">border-radius</span><span class="p">:</span> <span class="mi">8</span><span class="kt">px</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Damit erhält die Karte eine hellblaue Hintergrundfarbe, die visuell das Meer andeuten soll. Die Länder werden in einem neutralen Grau dargestellt.</p>
<p><a href="/minimalistische-offline-leaflet-karte-mit-node-js-hono/leaflet-with-countries_hu_703e1f339303e184.webp"><img class="content-image" src="/minimalistische-offline-leaflet-karte-mit-node-js-hono/leaflet-with-countries_hu_914aca009a1ac637.webp" alt="Leaflet Karte im Browser" loading="lazy" width="1440" height="1129" /></a></p>
<hr>
<h2 id="schritt-8-ländergrenzen-und-poi-kombinieren">Schritt 8: Ländergrenzen und POI kombinieren</h2>
<p>Nun wollen wir noch die POI-Punkte aus der <code>data/poi.geo.json</code> Datei auf der Karte anzeigen. Dazu erweitern wir die <code>public/map.js</code> Datei:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">fetch</span><span class="p">(</span><span class="s2">&#34;/data/poi.geo.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">      <span class="nx">onEachFeature</span><span class="o">:</span> <span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">layer</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="nx">layer</span><span class="p">.</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">  <span class="p">});</span></span></span></code></pre></div><p>Nach dem Neuladen der Seite sollten nun die POI-Punkte (Den Haag und Utrecht) auf der Karte angezeigt werden. Wenn du auf einen Punkt klickst, öffnet sich ein Popup mit dem Namen des Ortes.</p>
<p><a href="/minimalistische-offline-leaflet-karte-mit-node-js-hono/leaflet-with-countries-and-poi_hu_e2e98c4609c4b661.webp"><img class="content-image" src="/minimalistische-offline-leaflet-karte-mit-node-js-hono/leaflet-with-countries-and-poi_hu_1e4550772837d4ee.webp" alt="Leaflet Karte im Browser" loading="lazy" width="1440" height="1036" /></a></p>
<hr>
<h2 id="bonus-fancy-leaflet-funktionen">Bonus: Fancy Leaflet Funktionen</h2>
<p>Um die Karte ein wenig interaktiver zu gestalten, können wir noch einige zusätzliche Features hinzufügen. Leaflet bietet viele Möglichkeiten, um die Benutzererfahrung zu verbessern. Ersetze die <code>public/map.js</code> Datei mit folgendem Inhalt:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">const</span> <span class="nx">map</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="s2">&#34;map&#34;</span><span class="p">).</span><span class="nx">setView</span><span class="p">([</span><span class="mf">52.0705</span><span class="p">,</span> <span class="mf">4.707</span><span class="p">],</span> <span class="mi">6</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">// Leaflet benötigt mindestens einen Tile-Layer, auch wenn er leer ist.
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">// Deshalb wird hier ein leerer Tile-Layer eingebunden, um die Engine korrekt zu initialisieren.
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="p">{}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// Highlight-Funktionen
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">function</span> <span class="nx">highlightFeature</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kr">const</span> <span class="nx">layer</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="nx">layer</span><span class="p">.</span><span class="nx">setStyle</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nx">weight</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="s2">&#34;#ff0000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nx">fillOpacity</span><span class="o">:</span> <span class="mf">0.7</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="nx">layer</span><span class="p">.</span><span class="nx">bringToFront</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="kd">function</span> <span class="nx">resetHighlight</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">  <span class="nx">geojson</span><span class="p">.</span><span class="nx">resetStyle</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">// Country boundaries with outlines and optional fill
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="kd">let</span> <span class="nx">geojson</span><span class="p">;</span> <span class="c1">// Global variable to hold the geojson layer
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nx">fetch</span><span class="p">(</span><span class="s2">&#34;/data/vm_medium_countries.geo.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nx">geojson</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">      <span class="nx">style</span><span class="o">:</span> <span class="p">(</span><span class="nx">feature</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="nx">color</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">color</span> <span class="o">||</span> <span class="s2">&#34;#333&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="nx">weight</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="nx">opacity</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="nx">fillColor</span><span class="o">:</span> <span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">fillColor</span> <span class="o">||</span> <span class="s2">&#34;#eee&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="nx">fillOpacity</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">      <span class="p">}),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">      <span class="nx">onEachFeature</span><span class="o">:</span> <span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">layer</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="nx">layer</span><span class="p">.</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="nx">layer</span><span class="p">.</span><span class="nx">on</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">          <span class="nx">mouseover</span><span class="o">:</span> <span class="nx">highlightFeature</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">          <span class="nx">mouseout</span><span class="o">:</span> <span class="nx">resetHighlight</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="c1">// POI data with popups
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="nx">fetch</span><span class="p">(</span><span class="s2">&#34;/data/poi.geo.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">data</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="nx">L</span><span class="p">.</span><span class="nx">geoJSON</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">      <span class="nx">onEachFeature</span><span class="o">:</span> <span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">layer</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="nx">layer</span><span class="p">.</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">feature</span><span class="p">.</span><span class="nx">properties</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">  <span class="p">});</span></span></span></code></pre></div><p>Dieser Code fügt folgende Features hinzu:</p>
<ul>
<li><strong>Hover-Effekte</strong>: Länder werden hervorgehoben, wenn der Mauszeiger darüber schwebt.</li>
<li><strong>Popups</strong>: Beim Klicken auf Länder oder POI-Punkte öffnet sich ein Popup mit dem Namen.</li>
<li><strong>Popup für Länder</strong>: Beim Klicken auf ein Land öffnet sich ein Popup mit dem Namen des Landes.</li>
<li><strong>Popup für POI-Punkte</strong>: Beim Klicken auf einen POI-Punkt öffnet sich ein Popup mit dem Namen des Ortes.</li>
</ul>
<hr>
<h2 id="-fazit">🧭 Fazit</h2>
<p><a href="/minimalistische-offline-leaflet-karte-mit-node-js-hono/cloc-without-css_hu_f8aa5fa6c2a886f2.webp"><img class="content-image" src="/minimalistische-offline-leaflet-karte-mit-node-js-hono/cloc-without-css_hu_1490c2b7d8b18c1e.webp" alt="CLOC" loading="lazy" width="1440" height="1048" /></a></p>
<p>Mit weniger als 100 Zeilen Code lässt sich eine minimalistische, offlinefähige Leaflet-Karte mit Hono realisieren. Ideal für Prototyping oder einfache Anwendungen – ohne externe Abhängigkeiten und voll anpassbar.</p>
<p>Die Karte kann leicht erweitert werden, um weitere Features hinzuzufügen, wie z. B. Marker, Polygone oder andere GeoJSON-Daten. <a href="https://leafletjs.com/">Leaflet</a> bietet eine Vielzahl von Möglichkeiten, um Karten interaktiv und ansprechend zu gestalten.</p>
<p>Das Projekt in Version 1 kannst du hier herunterladen: <a href="https://codeberg.org/cblte/leaflet-hono-offline/releases/tag/v1">https://codeberg.org/cblte/leaflet-hono-offline/releases/tag/v1</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Filament Manager: Wie LLMs beim Bauen geholfen haben</title>
      <link>https://cbrueggenolte.de/filament-manager-llms-beim-bauen/</link>
      <pubDate>Mon, 05 May 2025 00:00:00 +0200</pubDate><author>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</author>
      <guid>https://cbrueggenolte.de/filament-manager-llms-beim-bauen/</guid>
      <description>&lt;p&gt;Heute war Brückentag, und ich hatte noch frei.&#xA;Neben ein paar administrativen Dingen habe ich weiter an meinem &lt;strong&gt;Filament Manager&lt;/strong&gt; programmiert.&#xA;Zwar habe ich das Datenbank-Layout ein paar Mal über den Haufen geworfen und hier und da das Design geändert,&#xA;aber insgesamt ist das Projekt, glaube ich, schon ganz gut geworden – und als Programmierübung war es ebenfalls hilfreich.&#xA;Außerdem kann ich den Manager selbst gut gebrauchen, um meine Filamente zu verwalten.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Heute war Brückentag, und ich hatte noch frei.
Neben ein paar administrativen Dingen habe ich weiter an meinem <strong>Filament Manager</strong> programmiert.
Zwar habe ich das Datenbank-Layout ein paar Mal über den Haufen geworfen und hier und da das Design geändert,
aber insgesamt ist das Projekt, glaube ich, schon ganz gut geworden – und als Programmierübung war es ebenfalls hilfreich.
Außerdem kann ich den Manager selbst gut gebrauchen, um meine Filamente zu verwalten.</p>
<p>Eine große Hilfe waren tatsächlich auch die <strong>Large Language Models</strong>.
Dank <em>Gemma</em>, <em>Qwen</em>, <em>Claude</em> und Co. ließen sich größere HTML-Abschnitte ziemlich schnell und ohne viel Aufwand umbauen.
Auch Anpassungen im Design mit Tailwind – das Einfügen von Klassen, das Ausprobieren verschiedener Layouts oder das Anpassen von Farben über ganze Seiten hinweg – gehen mit Unterstützung durch LLMs erstaunlich fix.
Sind also gar nicht so schlecht, die Dinger.
Leider konnte ich nicht alles mit einem lokalen Gemma- oder Qwen-Modell umsetzen,
aber das wird langsam besser.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Bun, Hono, TailwindCSS, JSX: Eine tolle Kombination!</title>
      <link>https://cbrueggenolte.de/bun-hono-tailwindcss-jsx-eine-tolle-kombination/</link>
      <pubDate>Thu, 01 May 2025 00:00:00 +0200</pubDate><author>96ca5227@cbrueggenolte.de (cbrueggenolte.de)</author>
      <guid>https://cbrueggenolte.de/bun-hono-tailwindcss-jsx-eine-tolle-kombination/</guid>
      <description>&lt;h2 id=&#34;einleitung&#34;&gt;Einleitung&lt;/h2&gt;&#xA;&lt;p&gt;Ich hänge immer noch ein wenig im Bootcamp hinterher.&#xA;Da ich die Aufgaben aber nicht schleifen lassen will, habe ich mir gestern einen kleinen Plan aufgestellt,&#xA;um mein Projekt zu pushen: Es soll ein einfacher Filament-Manager werden,&#xA;mit dem ich meine Spulen und deren Eigenschaften verwalten kann.&lt;/p&gt;&#xA;&lt;p&gt;Da ich das nicht mit Astro, sondern mit &lt;a href=&#34;https://bun.sh&#34;&gt;Bun&lt;/a&gt;, &lt;a href=&#34;https://hono.dev&#34;&gt;Hono&lt;/a&gt;&#xA;und &lt;a href=&#34;https://tailwindcss.com&#34;&gt;TailwindCSS&lt;/a&gt; umsetzen möchte, stellte sich die Frage:&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Wie kann ich TailwindCSS in mein Projekt integrieren?&lt;/strong&gt;&#xA;Für viele Frameworks gibt es entsprechende Plugins – aber für Bun und Hono leider nicht.&#xA;Nach langem Suchen und Ausprobieren habe ich aber endlich eine Lösung gefunden!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="einleitung">Einleitung</h2>
<p>Ich hänge immer noch ein wenig im Bootcamp hinterher.
Da ich die Aufgaben aber nicht schleifen lassen will, habe ich mir gestern einen kleinen Plan aufgestellt,
um mein Projekt zu pushen: Es soll ein einfacher Filament-Manager werden,
mit dem ich meine Spulen und deren Eigenschaften verwalten kann.</p>
<p>Da ich das nicht mit Astro, sondern mit <a href="https://bun.sh">Bun</a>, <a href="https://hono.dev">Hono</a>
und <a href="https://tailwindcss.com">TailwindCSS</a> umsetzen möchte, stellte sich die Frage:</p>
<p><strong>Wie kann ich TailwindCSS in mein Projekt integrieren?</strong>
Für viele Frameworks gibt es entsprechende Plugins – aber für Bun und Hono leider nicht.
Nach langem Suchen und Ausprobieren habe ich aber endlich eine Lösung gefunden!</p>
<h2 id="projekt-setup-mit-bun--hono">Projekt-Setup mit Bun &amp; Hono</h2>
<p>Als Erstes legen wir ein <code>hono</code>-Projekt mit <code>bun</code> an:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">bun create hono@latest my-project
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">cd</span> my-project</span></span></code></pre></div><p>Wir erstellen eine Startseite für die spätere Filamentübersicht.
Lege den Ordner <code>src/routes</code> an, falls er noch nicht existiert,
und erstelle darin die Datei <code>index.ts</code>.
Trage dort den folgenden Code ein:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// src/routes/index.ts
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Hono</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;hono&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kr">const</span> <span class="nx">route</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Hono</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">route</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">text</span><span class="p">(</span><span class="s1">&#39;🎉 Hello from your Filament Manager!****&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">route</span><span class="p">;</span></span></span></code></pre></div><p>Anschließend binden wir diese Route in unsere neue Datei <code>src/app.ts</code> ein.
Diese Datei wird die zentrale Stelle sein, an der wir unsere Hono-App starten.
Lege sie an (falls sie noch nicht existiert), und füge folgenden Code ein:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// src/app.ts
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Hono</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;hono&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">import</span> <span class="nx">indexRoute</span> <span class="kr">from</span> <span class="s1">&#39;./routes/index&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Hono</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="nx">indexRoute</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">app</span><span class="p">;</span></span></span></code></pre></div><p>Zum Schluss starten wir den Server.
Öffne die Datei <code>src/index.ts</code>
und ersetze den bisherigen Inhalt vollständig durch den folgenden Code:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// src/index.ts
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="nx">app</span> <span class="kr">from</span> <span class="s1">&#39;./app&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kr">const</span> <span class="nx">port</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span> <span class="o">||</span> <span class="mi">3000</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Server is running on http://localhost:</span><span class="si">${</span><span class="nx">port</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">Bun</span><span class="p">.</span><span class="nx">serve</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="nx">port</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="nx">fetch</span>: <span class="kt">app.fetch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>Starte den Server mit folgendem Befehl in einem Terminal:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl">bun run dev</span></span></code></pre></div><p>Wenn alles richtig eingerichtet ist, erreichst du die Startseite unter <code>http://localhost:3000</code> in deinem Browser.</p>
<p><a href="/bun-hono-tailwindcss-jsx-eine-tolle-kombination/bun-hono-start_hu_e69e1fba925bf7ee.webp"><img class="content-image" src="/bun-hono-tailwindcss-jsx-eine-tolle-kombination/bun-hono-start_hu_f75f17cabe1b2730.webp" alt="Ergebnis von bun run dev" title="Ergebnis von bun run dev" loading="lazy" width="1440" height="654" /></a></p>
<p>:::tip
Da wir das Terminal gleich wieder brauchen, stoppen wir den Server mit <code>Ctrl + C</code>.
:::</p>
<h2 id="tailwindcss-integrieren">TailwindCSS integrieren</h2>
<p>Jetzt kümmern wir uns um die Integration von TailwindCSS.</p>
<p>Installiere zuerst die nötigen Pakete als Entwicklungsabhängigkeiten:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">bun add -D tailwindcss @tailwindcss/cli</span></span></code></pre></div><p>Erstelle nun das Verzeichnis <code>src/styles</code> (falls es noch nicht existiert)
und eine Datei namens <code>global.css</code> und füge folgenden Inhalt ein:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">/* src/styles/global.css */</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">@</span><span class="k">import</span> <span class="s2">&#34;tailwindcss&#34;</span><span class="p">;</span></span></span></code></pre></div><p>Da es derzeit keine offiziellen Plugins für Bun + Hono gibt,
müssen wir die CSS-Datei selbst kompilieren.
Führe dazu im Terminal folgenden Befehl aus:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">bunx @tailwindcss/cli -i ./src/styles/global.css -o ./public/output.css --watch</span></span></code></pre></div><p>:::note
Damit wird Tailwind verarbeitet und die fertige CSS-Datei unter <code>./public/output.css</code> gespeichert.
Der <code>--watch</code>-Modus sorgt dafür, dass Änderungen an deiner CSS-Datei
oder deinem Projekt automatisch neu kompiliert werden.
Du kannst ihn jederzeit mit <code>Ctrl + C</code> beenden.
:::</p>
<h2 id="html-template-einbinden">HTML-Template einbinden</h2>
<p>Jetzt, da wir unsere <code>output.css</code> im <code>public</code>-Ordner haben,
können wir ein HTML-Template erstellen und in unsere Hono-App integrieren.</p>
<p>Lege im Verzeichnis <code>src/templates</code> eine Datei mit dem Namen <code>layout.tsx</code> an.
Falls der Ordner <code>templates</code> noch nicht existiert, erstelle ihn zuerst.</p>
<p>Füge dann folgenden Code ein:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// src/templates/layout.tsx
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">Layout</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">children</span> <span class="p">}</span><span class="o">:</span> <span class="p">{</span> <span class="nx">children</span>: <span class="kt">string</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="k">return</span> <span class="sb">`
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="sb">    &lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sb">    &lt;html lang=&#34;de&#34;&gt;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sb">      &lt;head&gt;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sb">        &lt;meta charset=&#34;UTF-8&#34; /&gt;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="sb">        &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34; /&gt;
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="sb">        &lt;title&gt;Filament Manager&lt;/title&gt;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="sb">        &lt;link href=&#34;https://cbrueggenolte.de/public/output.css&#34; rel=&#34;stylesheet&#34;&gt;
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="sb">      &lt;/head&gt;
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="sb">      &lt;body class=&#34;bg-gray-100 text-gray-900 p-4&#34;&gt;
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="sb">        &lt;main class=&#34;max-w-2xl mx-auto&#34;&gt;
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="sb">          </span><span class="si">${</span><span class="nx">children</span><span class="si">}</span><span class="sb">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="sb">        &lt;/main&gt;
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="sb">      &lt;/body&gt;
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="sb">    &lt;/html&gt;
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="sb">  `</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>:::note
Das Template ist eine Funktion, die ein vollständiges HTML-Dokument als String zurückgibt.
Das Platzhalterfeld <code>${children}</code> wird später mit dem eigentlichen Seiteninhalt ersetzt.
:::</p>
<p>Jetzt passen wir unsere Startseite an, damit sie das HTML-Template verwendet.</p>
<p>Öffne die Datei <code>src/routes/index.ts</code> und ersetze ihren Inhalt durch:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// src/routes/index.ts
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Hono</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;hono&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Layout</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;../templates/layout&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kr">const</span> <span class="nx">route</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Hono</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">route</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="kr">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="sb">`
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="sb">    &lt;h1 class=&#34;text-3xl font-medium mb-4&#34;&gt;🎨 Filament Manager&lt;/h1&gt;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="sb">    &lt;p class=&#34;text-lg&#34;&gt;Hier verwalten wir bald unsere Filamente.&lt;/p&gt;
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="sb">  `</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="k">return</span> <span class="nx">c</span><span class="p">.</span><span class="nx">html</span><span class="p">(</span><span class="nx">Layout</span><span class="p">({</span> <span class="nx">children</span>: <span class="kt">content</span> <span class="p">}));</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">route</span><span class="p">;</span></span></span></code></pre></div><p>:::note
Damit wird deine Startseite jetzt mit dem Template <code>layout.tsx</code> gerendert.
Der HTML-Inhalt in der Variable <code>content</code> wird automatisch an der Stelle <code>${children}</code> eingefügt.
:::</p>
<h2 id="statische-dateien-bereitstellen">Statische Dateien bereitstellen</h2>
<p>Damit die Datei <code>output.css</code> im Browser geladen werden kann, muss Hono den Ordner <code>/public</code> freigeben.
Das erreichen wir mit der Middleware <code>serveStatic</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="s1">&#39;/public/*&#39;</span><span class="p">,</span> <span class="nx">serveStatic</span><span class="p">({</span> <span class="nx">root</span><span class="o">:</span> <span class="s1">&#39;./&#39;</span> <span class="p">}));</span></span></span></code></pre></div><p>Füge diese Zeile in <code>src/app.ts</code> ein – zwischen der App-Initialisierung und dem Routing.
Oder ersetze den gesamten Inhalt der Datei <code>src/app.ts</code> durch den folgenden Code:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// src/app.ts
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Hono</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;hono&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">serveStatic</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;hono/bun&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kr">import</span> <span class="nx">indexRoute</span> <span class="kr">from</span> <span class="s1">&#39;./routes/index&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Hono</span><span class="p">();</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// Statische Dateien wie /public/output.css bereitstellen
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="s1">&#39;/public/*&#39;</span><span class="p">,</span> <span class="nx">serveStatic</span><span class="p">({</span> <span class="nx">root</span><span class="o">:</span> <span class="s1">&#39;./&#39;</span> <span class="p">}));</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">route</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="nx">indexRoute</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">app</span><span class="p">;</span></span></span></code></pre></div><p>Die Middleware sorgt dafür, dass der Browser statische Dateien aus dem <code>public</code>-Verzeichnis laden kann –
zum Beispiel die Datei <code>output.css</code>.</p>
<p>Starte nun den Server erneut (ggf. in einem zweiten Terminalfenster) mit:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl">bun run dev</span></span></code></pre></div><p>:::tip
Falls du den TailwindCSS-Compiler vorher gestoppt hast (z. B. mit <code>Ctrl + C</code>),
starte ihn jetzt erneut – sonst wird die eingebundene Datei <code>output.css</code> nicht aktualisiert.</p>
<p>Verwende dazu denselben Befehl wie zuvor:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl">bunx @tailwindcss/cli -i ./src/styles/global.css -o ./public/output.css --watch</span></span></code></pre></div><p>Am besten lässt du den Compiler dauerhaft in einem separaten Terminalfenster laufen,
während du am Projekt arbeitest.
:::</p>
<p>Wenn alles funktioniert, wird die Startseite unter
<a href="http://localhost:3000">http://localhost:3000</a> angezeigt – diesmal mit TailwindCSS-Styling.</p>
<p><a href="/src/assets/bun-hono-tailwindcss.png"><img class="content-image" src="/src/assets/bun-hono-tailwindcss.png" alt="Bun Hono und Tailwindcss" title="Bun Hono und Tailwindcss" loading="lazy" /></a></p>
<p>Deine Version ist bereits sehr gut – klar strukturiert, technisch korrekt und stilistisch passend zum Blog.
Ich habe sie jetzt nur leicht sprachlich geglättet
und einen durchgehenden, natürlichen Lesefluss hergestellt.
Hier die überarbeitete Fassung:</p>
<hr>
<h2 id="dev-workflow-verbessern">Dev-Workflow verbessern</h2>
<p>Damit du nicht ständig zwei Terminals gleichzeitig offen haben musst,
eines für Hono und eines für Tailwind,
kannst du deinen Workflow etwas bequemer gestalten.</p>
<p>Wir verwenden das Tool <a href="https://www.npmjs.com/package/concurrently"><code>concurrently</code></a>, um beide Prozesse parallel zu starten:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">bun add -D concurrently</span></span></code></pre></div><p>Zunächst kannst du den folgenden <code>dev</code>-Befehl in deiner <code>package.json</code> unter <code>&quot;scripts&quot;</code> ergänzen:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="s2">&#34;scripts&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;dev&#34;</span><span class="p">:</span> <span class="s2">&#34;concurrently &#39;bun run --hot src/index.ts&#39; &#39;bunx @tailwindcss/cli -i ./src/styles/global.css -o ./public/output.css --watch&#39;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Wenn du möchtest, kannst du den Prozessen zusätzlich <strong>Namen und Farben</strong> zuweisen – das macht die Terminalausgabe übersichtlicher:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="s2">&#34;scripts&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;dev&#34;</span><span class="p">:</span> <span class="s2">&#34;concurrently --names &#39;server,styles&#39; --prefix-colors &#39;yellow,blue&#39;  &#39;bun run --hot src/index.ts&#39; &#39;bunx @tailwindcss/cli -i ./src/styles/global.css -o ./public/output.css --watch&#39;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Statt alle Befehle in eine lange
und schwer lesbare Zeile zu packen,
kannst du das Ganze noch besser strukturieren,
indem du <strong>zwei separate Scripts</strong> anlegst:
Eines für den Server, eines für Tailwind.
Dann kombinierst du sie im <code>dev</code>-Befehl über <code>concurrently</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nt">&#34;start:server&#34;</span><span class="p">:</span> <span class="s2">&#34;bun run --hot src/index.ts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nt">&#34;start:styles&#34;</span><span class="p">:</span> <span class="s2">&#34;bunx @tailwindcss/cli -i ./src/styles/global.css -o ./public/output.css --watch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nt">&#34;dev&#34;</span><span class="p">:</span> <span class="s2">&#34;concurrently --names &#39;server,styles&#39; --prefix-colors &#39;yellow,blue&#39; &#39;npm run start:server&#39; &#39;npm run start:styles&#39;&#34;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>:::note
<strong>Vorteil:</strong> Du kannst <code>bun run start:server</code> und <code>bun run start:styles</code> auch separat ausführen –
z. B. wenn du nur einen der beiden Prozesse debuggen möchtest.
:::</p>
<p>Wenn du jetzt <code>bun run dev</code> ausführst, starten <strong>Hono-Server und Tailwind-Watcher gleichzeitig</strong>,
farblich getrennt, sauber strukturiert, und mit einem einzigen Befehl.</p>
<h2 id="fazit">Fazit</h2>
<p>Und das war’s auch schon!
Eigentlich gar nicht so schwer – aber für mich war es trotzdem eine kleine Herausforderung,
weil ich mit <strong>Bun</strong> und <strong>Hono</strong> bisher noch wenig Erfahrung hatte.</p>
<p>Aber genau deshalb hat es sich gelohnt:
Ich habe nicht nur die Tools kennengelernt, sondern gleich eine funktionierende Entwicklungsumgebung aufgebaut,
mit TailwindCSS, HTML-Template und Live-Reloading.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
