A small Kotlin/Ktor sample that renders HTML on the server and uses htmx for component-style page updates without a separate JavaScript application framework.
This repository is best treated as a compact example or starting point, not as a production-ready web application template.
- Server-rendered HTML with Ktor and
kotlinx.html - Showcase content that explains the Ktor route and htmx fragment-swap model
- htmx-powered navigation for dynamic component swaps
- Active navigation state for direct loads, htmx swaps, and browser history
- Direct component URLs that still render the full application shell on refresh
- Interactive calendar rows that load server-rendered event details
- Tailwind utility classes compiled into a static CSS asset
- Netty-based Ktor server
- Sitemap and robots.txt endpoints for the configured public URL
- Dockerfile-based container packaging for deployment demos
- Route and htmx contract tests with Ktor
testApplication - JaCoCo coverage reporting and verification
- Kotlin
2.3.21 - Ktor
3.5.0 - Gradle wrapper
9.5.1 - Logback
1.5.33 - JaCoCo
0.8.14 - htmx
2.0.10 - Tailwind CSS
4.3.0 - Tailwind CLI
4.3.0
- JDK 17 or 21
- Node.js and npm for rebuilding Tailwind CSS
- No separate Gradle installation is required; use the checked-in Gradle wrapper.
- Docker is optional and only required for container build checks.
The project was last verified locally with OpenJDK 21.0.10.
git clone https://github.com/rf43/htmx-ktor.git
cd htmx-ktor
npm ci
npm run build:css
./gradlew runOpen http://localhost:8080.
Development mode is enabled by default through gradle.properties.
To run without Ktor development mode:
./gradlew run -Pdevelopment=falseRun tests:
./gradlew testThe test suite verifies the root application shell, static asset routing, htmx navigation attributes, component route registration, and route-specific showcase content.
Rebuild the compiled Tailwind CSS asset:
npm run build:cssGradle packages the current src/main/resources/static/app.css; it does not regenerate Tailwind CSS. Run npm run build:css after changing Tailwind classes or src/main/resources/styles/tailwind.css.
Watch Tailwind source inputs during local UI work:
npm run watch:cssBuild the project:
./gradlew buildBuild the container image:
docker build -t htmx-ktor .The Docker build regenerates app.css before packaging the Ktor distribution.
The Docker build runs Kotlin compilation in-process to avoid short-lived Kotlin daemon files during hosted image builds.
Run the container locally:
docker run --rm -p 8080:8080 htmx-ktorThe server reads PORT from the environment and defaults to 8080, which keeps local runs simple while allowing managed platforms to provide the runtime port.
The application supports these environment variables:
PORT- HTTP port used by the Netty server. Defaults to8080.PUBLIC_SITE_URL- absolute public base URL used by/sitemap.xmland/robots.txt. Defaults tohttps://example.com/.
Set PUBLIC_SITE_URL in the deployment environment so generated search endpoints point at the deployed site. Forks should set this to their own public URL rather than changing source code.
Generate the JaCoCo report:
./gradlew jacocoTestReportVerify coverage:
./gradlew jacocoTestCoverageVerificationThe HTML coverage report is written to build/reports/jacoco/test/html/index.html.
- CI documents the GitHub Actions checks for pull requests and pushes to
main. - Deployment documents the DigitalOcean App Platform demo deployment, search endpoints, and rollback notes.
htmx-ktor/
|-- .github/workflows/
| `-- ci.yml
|-- project_docs/
| |-- ci.md
| `-- deployment.md
|-- src/
| |-- main/
| | |-- kotlin/io/ivycreek/
| | | |-- about/
| | | |-- calendar/
| | | |-- contact/
| | | |-- content/
| | | |-- dashboard/
| | | |-- navbar/
| | | |-- plugins/
| | | |-- projects/
| | | |-- team/
| | | `-- Application.kt
| | `-- resources/
| | |-- logback.xml
| | |-- styles/
| | `-- static/
| `-- test/kotlin/io/ivycreek/
|-- build.gradle.kts
|-- Dockerfile
|-- package.json
|-- package-lock.json
|-- .dockerignore
|-- gradle.properties
|-- settings.gradle.kts
`-- gradle/wrapper/
Each page package generally has two files:
Feature.ktrenders the HTML fragment.FeatureRouter.ktregisters the/components/...route.
The root page and static resource routing live in src/main/kotlin/io/ivycreek/plugins/Routing.kt.
Component routes return fragments for htmx requests and the full application shell for normal browser requests, so pushed URLs remain refreshable and bookmarkable.
- Tailwind is built with the CLI from
src/main/resources/styles/tailwind.cssintosrc/main/resources/static/app.css, which Ktor serves at/app.css. - The project currently loads htmx from a pinned 2.x CDN URL with subresource integrity.
This project is licensed under the MIT License. See LICENSE for details.