Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building dynamic web apps with Kotlin, WebCompo...

Building dynamic web apps with Kotlin, WebComponents and Htmx (v1) 🇬🇧 @NDC Oslo 2025

The complexity of single-page application (SPA) frameworks is increasing every year. Developers are not only faced with a steep learning curve, but a never-ending one. Every year, existing concepts are replaced by new ones, some of which radically change how we build applications.

Granted, it is not impossible to keep up and write SPAs well. It just takes a lot of time, dedication and knowledge. This leads to teams being **fragmented** into (a few) frontend experts and the others.

In this talk we will look at a modern approach with Htmx and WebComponents that is much simpler and can deliver at least the same user experience as a SPA. Teams don't have to learn two separate stacks for backend and frontend. This gives us full-stack teams that can be productive together. They can focus on the business logic again and less on the technical aspects.

Avatar for Richard

Richard

May 21, 2025
Tweet

More Decks by Richard

Other Decks in Programming

Transcript

  1. 21.05.25 Richard Gross (he/him) Building dynamic WebApps with Kotlin, WebComponents

    and Htmx Archaeology & Health Checks Hypermedia TestDSL Driven Development richargh.de/ richargh.de in/richargh
  2. We are writing everything twice(WET)1,2 Slide 3 by richargh.de from

    1 See https://www.radicalsimpli.city/ and Radically Simple Web Architecture 2 https://en.wikipedia.org/wiki/Don%27t_repeat_yourself Server Client Dependencies Dto Mapping & Validation Controllers Models Routing Authorization Caching Monitoring Business Logic Dependencies Templates Stores View Models Routing Authorization Caching State Manag. Push Telemitry Hydration Etc. Business Logic 1 Never trust the client
  3. We are maintaining at least two versions Slide 4 by

    richargh.de from 2 Client Schema 1.2 V1.2-FE Server V1.2-BE Newest 100% of users Newest 11% of users Client V1.1-FE Last week 80% of users Client V1.0-FE Last month 9% of users Schema 1.1 Schema 1.0
  4. We are about to migrate (again) Slide 5 by richargh.de

    from 3 Client Angular x Angular 19/20 Modules Standalone Binding / RxJS Signals Directives @literal … …
  5. The next project comes knocking And it is data tables,

    forms and diagrams. It is also greenfield. Slide 7 by richargh.de from
  6. 90% of all business apps1 are Data Tables, Modals and

    Forms. Most business apps are not Google Maps, Google Sheets, Spotify or Miro. Most look like Amazon, Twitter or Gmail, just with more tables. Slide 8 by richargh.de from 1 Personal experience in a lot of domains. +Impression when talking to other consultants @MaibornWolff and external.
  7. Can we get good stuff, the same user exp. but

    without the problems? Slide 9 by richargh.de from Code not template languages (i.e. JSX) Components Full-Stack Type&API- Safety Testability Quick UI Prototyping As little redundancy as possible Quick API remodelling Preferably only one tech stack Simple enough for everyone The good stuff we have The good stuff we want
  8. Enter the JavaScript Rising Star of 20241,2 Slide 10 by

    richargh.de from 1 https://risingstars.js.org/2024/en#section-framework 2 https://2024.stateofjs.com/en-US/libraries/front-end-frameworks/ React Vue Angular Svelte Htmx
  9. Looks promising Slide 11 by richargh.de from From React to

    htmx on a real-world SaaS product: we did it, and it’s awesome! No UX Tradeoffs Performance signficantly better 67% less code Everyone became full-stack
  10. Slide 12 by richargh.de from Photo by RDNE Stock project:

    https://www.pexels.com/photo/close-up-of-a-trophy-7005501/
  11. Htmx achieves simplicity by sending html, not json Slide 14

    by richargh.de from Client Server Json Client Server Html Views with declared swaps A dependency that swaps html
  12. The server drives the logic and interactions We can change

    interactions at will : No client updates needed Slide 15 by richargh.de from
  13. Click to load, with two attributes Slide 16 by richargh.de

    from 1. <article> 2. <section id="replaceMe"> 3. <!-- all will be replaced --> 4. <button 5. hx-get="/contacts/2" 6. hx-target="#replaceMe” 7. > 8. Load Contact 9. </button> 10. </section> 11.</article> 1. <article> 2. <section> 3. <h3>Alex</h3> 4. <p>Htmx Expert</p> 5. </section> 6. </article> Click 1. // server sends 2. <section> 3. <h3>Alex</h3> 4. <p>Htmx Expert</p> 5. </section>
  14. Lazy load, with three attributes Slide 17 by richargh.de from

    1. <article> 2. <!-- more sections up here --> 3. <section 4. hx-get="/contacts?page=2" 5. hx-trigger="revealed once” 6. hx-swap="afterend" 7. > 8. <h3>Alex</h3> 9. <p>Htmx Expert</p> 10. </section><!-- the section end --> 11.</section> 1. <article> 2. <!-- more sections up here --> 3. <section 4. hx-get="/contacts?page=2" 5. … 6. > 7. <h3>Alex</h3> 8. <p>Htmx Expert</p> 9. </section> 10. <section> 11. <p>Taylor</h3> 12. <p>Web Expert</p> 13. </section> 14. <!-- more sections here--> 15. <section 16. hx-get="/contacts?page=2" 17. hx-trigger="revealed once” 18. hx-swap="afterend" 19. > 20. <p>Stevie</h3> 21. <p>Component Expert</p> 22. </section> 23.</article> Scroll
  15. Htmx is easy to learn The core 4 • hx-get

    / hx-put / … • hx-trigger click, keydown, load, revealed, changed, every 1s, my-custom-event, … • hx-target #id, closest .class, … • hx-swap inner/outerHTML, before/afterBegin, … The situational ones • hx-push-url=true/false • hx-boost progressive enhancement for links • hx-ext extensions for SSE or WebSockets • hx-swap-oob + hx-select-oob when swapping markup in more than one place Slide 18 by richargh.de from
  16. Htmx covers about 95% of the interactions we want to

    make Slide 19 by richargh.de from
  17. But what about the other 5%? What about the client-only

    markup mutations where we do not want to notify the server. Slide 20 by richargh.de from
  18. Web Components could help • Framework-less components supported in all

    modern Browsers1 • No download required2 • Scoped CSS with Declarative Shadow-Dom3 Slide 21 by richargh.de from 1 They have been baseline Widely available for for more than 2 ½ years1. Meaning all browsers support them. See https://www.webcomponents.org/ 2 Unless you have to polyfill. 3 Baseline Newly available https://web.dev/articles/declarative-shadow-dom 1. // define, only valid with at least one ‘-’ hyphen 2. 3. customElements.define(”awesome-maker", class extends HTMLElement { 4. connectedCallback() { 5. // get an attribute 6. const anAttribute = this.getAttribute(‘factor’); 7. // queryselector scoped to this element 8. const wrappedElement = this.querySelector(‘img’); 9. // TODO change stuff in the element DOM 10. } 11. }); 1. // use 2. 3. <div> 4. <awesome-maker 5. factor=“9001” 6. > 7. <!-- other html here --> 8. </awesome-maker> 9. </div>
  19. Prefer Html Web Components1 Slide 22 by richargh.de from 1

    https://adactio.com/journal/20618 If your custom element is empty, it’s not an Html Web Component. But if you’re using a custom element to extend existing markup, that’s an Html Web Component.
  20. Prefer Html Web Components over vanilla • Html Web Components

    are a restricted subset of Web Components. No shadow dom, no templates, no slots. • The restricted Html Web Components are significantly easier to write • Extending html makes us think in reusable components Slide 23 by richargh.de from 1 https://www.webcomponents.org/
  21. Give html the ability to render relative time What is

    sent to the client What the client renders Slide 24 by richargh.de from 1 https://github.com/github/relative-time-element 1. <!-- cacheable --> 2. <relative-time 3. datetime="2025-05-21T11:40:00Z” 4. > 5. May 21, 2025 11:40am 6. </relative-time> 1. 2. <relative-time 3. datetime="2025-05-21T11:40:00Z” 4. > 5. 15 minutes ago 6. </relative-time>
  22. Give html the ability to compare two images Slide 25

    by richargh.de from See image-compare: https://image-compare-component.netlify.app/ 1. <image-compare> 2. <img src="path/to/image.jpg"/> 3. <img src="path/to/image.jpg"/> 4. </image-compare>
  23. What Components can we re-use? Vanilla Html • <input type=color/date/range…>

    • <details> + <summary> f.ex. Accordions • <datalist> for Autocomplete • <form> validation with required, pattern, etc. • <dialog>, soon with commandFor1 • <picture> and <search> • Soon: Customizable <select>2 • Later: even more OpenUI controls3 Html Web Components • Github Elements4 • Awesome Standalones5,6 Slide 27 by richargh.de from 1 https://developer.chrome.com/blog/command-and-commandfor 2 https://developer.chrome.com/blog/a-customizable-select 3 https://open-ui.org/ 4 https://github.com/github/github-elements 5 https://github.com/davatron5000/awesome-standalones#element-extensions 6 https://www.zachleat.com/web/?category=web-components 7 https://shoelace.style/ 8 https://www.webcomponents.org/ 9 https://github.com/Richargh/aggrid-webcomponent-kotlinxhtml-htmx-spring-boot-krdl-kt-sandbox Web Components Libraries • Shoelace7 • WebComponents.org8 1 2 3 Wrap existing ones as WebComponents9 4
  24. Our Stack after some prototypes Slide 29 by richargh.de from

    1 https://github.com/alexpetros/triptych Choice Html Web Components Code-Flexibility, WebC Ecosystem, no library required Htmx Simplicity of markup Kotlinx.html Type-safety and it’s regular code Kotlin CSS files of customer Alternatives AlpineJs, Stimulus, Hyperscript Turbo, Unpoly, Triptych1 Qute, J2Html, HtmlFlow C#, TypeScript, Java Bootstrap, Pico, Tailwind Client-Markup Mutation Html Swapper Template Language Main Language Design System
  25. Demo Not of our production app, but a very useful

    example Slide 30 by richargh.de from
  26. Offline-capability is not binary, but a sliding scale of increasing

    complexity 1. Caching data for reads (possible with Htmx) 2. Making offline drafts (can be done with Htmx) 3. Making conflict-free offline changes (nope, not for Htmx) 4. Having all server-data available locally so all changes can be made conflict-free 5. Providing offline changes and backend compatibility even when the user is off for weeks or months Slide 32 by richargh.de from
  27. With Htmx you can’t do Unit Testing? • You can

    unit-test the business logic • You can unit-test the rendering: the render functions itself and the controllers that return Html • Component Tests are very much possible • Render your components (aka Storybook) with a server • Use Playwright/Cypress etc. to run your component Tests • Save a lot of time because you don’t do Frontend tests and Backend tests separately. Slide 33 by richargh.de from
  28. Why you shouldn’t use Htmx, as shared by the creators

    of Htmx Some non-obvious points: • Intuition and Developer Experience1 • It takes some time to adapt to the server-driven mindset • Your team must be on-board • You typically cannot copy&paste components rendered by the server • Examples are good but few. Official Website3, books and JetBrains videos4 Slide 34 by richargh.de from 1 https://htmx.org/essays/why-gumroad-didnt-choose-htmx/ 2 https://htmx.org/essays/when-to-use-hypermedia/#hypermedia-not-a-good-fit-if 3 https://htmx.org/examples/ 4 https://www.jetbrains.com/guide/dotnet/tutorials/htmx-aspnetcore/server-powered-modals/
  29. With Htmx you can do 95% of all interactivity you

    need* * We are not trying to build Google Maps or Draw.io here. Slide 35 by richargh.de from
  30. Use the “Rule of Least Power”1 to choose the least

    powerful technology for a given purpose Slide 36 by richargh.de from 1 https://www.w3.org/2001/tag/doc/leastPower.html 2 https://shoelace.style 3 Not Baseline yet https://developer.chrome.com/docs/web-platform/view-transitions/cross-document HTML & Vanilla Html Elements CSS Htmx Regular Web Components (e.g. Shoelace2) Element Extensions Usage (not to scale ☺) Does your app even need more interactivity? Multi-Page CSS Transitions3 are powerful.
  31. It does not have to be our stack. It’s HOWL

    (Hypermedia On Whatever you’d like)1 Slide 37 by richargh.de from 1 https://htmx.org/essays/hypermedia-on-whatever-youd-like/ 2 https://x.com/dhh/status/1275901955995385856 3 https://ahastack.dev/ 4 https://hamy.xyz/blog/2024-02_hamstack Client Server Html Views with declared swaps like you Whatever Html-swapper Client-markup mutators AHA-Stack3 Astro, Htmx, AlpineJs HEY-Stack2 Rails, Turbo, Stimulus HAM-Stack4 Hypermedia on a Monolith f.ex.
  32. Our productive app Slide 38 by richargh.de from 1 Waiting

    for Kotlin Multi-Platform to support ES 2015 2 https://kotlinlang.org/docs/js-project-setup.html#support-for-es2015-features Code not template languages (i.e. JSX) Components Full-Stack Type&API- Safety Testability Quick UI Prototyping As little redundancy as possible Quick API remodelling Preferably only one tech stack Simple enough for everyone The good stuff we have The good stuff we want ✓ with Kotlinx.html ✓ Yep ✓ with Dev-Tools Reload ✓ Less test-code to write ✓ Forms described only on the server ✓ It’s all server-driven ✓ One stack (✓) Still some JavaScript1,2 (✓) Depends on mindset
  33. We’re going to start with a light-weight approach… ... and

    we’ll keep it; unless we run into obvious problems Slide 40 by richargh.de from
  34. richargh.de Slide 41 by richargh.de from Thank you Talk to

    me about these topics ☺ Richard Gross (he/him) Works for maibornwolff.de/ Contact Let’s grab a coffee richargh.de/ richargh.de in/richargh Code https://github.com/Richargh/dynamic- websites-htmx-webcomponents-kotlinx-html Archaeology & Health Checks Hypermedia TestDSL Driven Development Provide feedback, get slides as pdf ☺ Feedback
  35. Authentication works the same way as before • HTTPOnly Cookies

    are a great choice (migrating the most common XSS attacks1) • Bearer Tokens can also be used2 Slide 44 by richargh.de from 1 https://owasp.org/www-community/HttpOnly 2 https://htmx.org/examples/async-auth/
  36. Updating more than one place at once 1. Expand the

    Target • Try swapping both places at once (if they are close enough) 2. Triggering Events • Trigger an event by sending a response header: HX-Trigger:xyzEvent 3. Out of Band Responses • Send more than one element to swap at once: hx-swap-oob="beforeend:#id-of-element-to-swap Slide 47 by richargh.de from See https://htmx.org/examples/update-other-content/
  37. Active search, with three attributes Slide 48 by richargh.de from

    1. <search> 2. <input 3. class="form-control" 4. type="search" 5. name="search" 6. placeholder="Begin Typing To Search Users..." 7. hx-get="/search" 8. hx-trigger="input changed delay:500ms, search" 9. hx-target="#search-results"> 10.</search> 11.<ol id="search-results"></ol>
  38. React and Htmx Side-by-side Slide 49 by richargh.de from Example

    picture here is https://todomvc.com/ 1. // react 2. <input 3. type="checkbox" 4. checked={completed} 5. onChange={toggleItem} /> 6. 7. const toggleItem = useCallback( 8. () => dispatch({ 9. type: TOGGLE_ITEM, 10. payload: { id } 11. }), [dispatch] 12.); 1. // htmx 2. <input 3. type="checkbox" 4. checked=false 5. hx-put=“/todos/2:complete” 6. hx-target=“this” />
  39. NextJs won’t help Slide 50 by richargh.de from 1 https://www.zachleat.com/web/react-criticism/#february-2023

    [In 2023] only 26% of sites using Next.js have good Core Web Vitals. This is lower than React at-large (32.81%) and the entirety of all sites in the data set (39.37%).1
  40. The bloat experience gap Slide 51 by richargh.de from 1

    https://danluu.com/web-bloat/ 2 The Performance Inequality Gap https://infrequently.org/series/performance-inequality 3 https://www.wired.com/2016/04/average-webpage-now-size-original-doom/ 4 The Website Obesity Crisis https://idlewords.com/talks/website_obesity.htm#fixes 5 https://www.kryogenix.org/code/browser/everyonehasjs.html Most users Most developers JavaScript Size Mobile cost 2023 350kB JS $200 75% of users2