Nino
04/08/2026, 4:38 PMNino
04/08/2026, 4:51 PMapp
app/build.gradle.kts
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
kotlin {
js {
browser()
binaries.executable()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
binaries.executable()
}
// ... dependencies, etc
}
app/src/webMain/kotlin/main.kt
fun main() {
ComposeViewport {
AppTheme {
configureWebResources {
resourcePathMapping { path -> "./$path" }
}
// my Compose app with screens, etc...
}
}
I have a Ktor module to distribute the web app and to store data (called from the Compose app)
server/build.gradle.kts
plugins {
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.ktor)
application
}
group = "foo.bar"
version = "1.0.0"
application {
mainClass.set("foo.bar.ApplicationKt")
val isDevelopment: Boolean = project.ext.has("development")
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}
dependencies {
// Ktor libs, etc...
}
ktor {
development = true
}
val copyResourcesTask = tasks.register<Copy>("copyWebResources") {
dependsOn(":app:wasmJsBrowserDistribution")
from(project(":app").file("build/dist/wasmJs/productionExecutable"))
destinationDir = file("src/main/resources/web-resources")
}
tasks.getByName("processResources") {
dependsOn(copyResourcesTask)
}
tasks {
register("stage") {
dependsOn("installDist")
}
}
server/src/main/kotlin/foo/bar/Application.kt
fun main() {
embeddedServer(
factory = Netty,
port = System.getenv("PORT")?.toIntOrNull() ?: 8081,
module = Application::module
).start(wait = true)
}
suspend fun Application.module() {
// modules like CORS, ContentNegotiation, etc..
routing {
static("/") {
val staticDir = listOf(
"../web/build/distributions",
"web/build/distributions"
).map { File(it) }
.firstOrNull { it.exists() }
if (staticDir != null) {
// If staticDir is not null, it means we run "./gradlew server:run" task
staticRootFolder = staticDir
files(".")
default("index.html")
} else {
// If staticDir is null, it means we already package all with "./gradlew installDist" task
resources("web-resources")
files(".")
defaultResource("web-resources/index.html")
}
}
}
// GET and POST configurations used by the app...
}
Does everything there look correct? I can't even find the post where I found the way to distribute the Compose App throught Ktor. Is it the reason why the Compose app crashes / freeze when failing to load a simple icon ?
Simply trying to display this Compose while offline freeze the web app :
Icon(
painter = painterResource(Res.drawable.error),
contentDescription = null,
)
(the Res.drawable.error is inside the app composeResources/drawable folder