diff --git a/docs/server-settings.md b/docs/server-settings.md
index 0cf5efb67..f0c522bdd 100644
--- a/docs/server-settings.md
+++ b/docs/server-settings.md
@@ -22,7 +22,7 @@ When TrogonEventStore is installed as a Linux service, the following locations a
- **Data:** `/var/lib/eventstore`
- **Server logs:** `/var/log/eventstore`
- **Test client logs:** `./testclientlog`
-- **Web content:** `./ui-assets` then `{Content}/ui-assets`
+- **Web content:** `wwwroot/ui/assets` beside the server executable
- **Projections:** `./projections` then `{Content}/projections`
- **Prelude:** `./Prelude` then `{Content}/Prelude`
@@ -32,7 +32,7 @@ When TrogonEventStore is installed as a Linux service, the following locations a
- **Data:** `./data`
- **Server logs:** `./logs`
- **Test client log:** `./testclientlogs`
-- **Web content:** `./ui-assets`
+- **Web content:** `wwwroot/ui/assets` beside the server executable
- **Projections:** `./projections`
- **Prelude:** `./Prelude`
@@ -44,12 +44,14 @@ When running TrogonEventStore using local binaries, either downloaded or built f
- **Data:** `./data`
- **Server logs:** `./logs`
- **Test client log:** `./testclientlogs`
-- **Web content:** `./ui-assets`
+- **Web content:** `wwwroot/ui/assets` beside the server executable
- **Projections:** `./projections`
- **Prelude:** `./Prelude`
Depending on the platform and installation type, the location of TrogonEventStore executables, configuration and other necessary files vary.
+Deploy the complete published output, including `wwwroot` and the static-asset endpoint manifest, together. UI asset URLs are content-fingerprinted so browsers can safely cache them across upgrades. Replacing asset files independently of the server build is not supported.
+
## Database settings
### Database location
diff --git a/scripts/publish-tests.sh b/scripts/publish-tests.sh
index dc759054d..7a857f352 100755
--- a/scripts/publish-tests.sh
+++ b/scripts/publish-tests.sh
@@ -4,16 +4,29 @@ set -eu
source_directory=$1
output_directory=$2
-test_projects=$(mktemp)
-trap 'rm -f "$test_projects"' EXIT
+staging_directory=$(mktemp -d)
+trap 'rm -rf "$staging_directory"' EXIT
+test_projects="$staging_directory/test-projects"
+node_publish_directory="$staging_directory/node"
-find "$source_directory" -maxdepth 1 -type d -name "*.Tests" -print > "$test_projects"
+# Test projects do not inherit the web SDK's published static-asset manifest.
+dotnet publish \
+ --runtime="${RUNTIME}" \
+ --no-self-contained \
+ --configuration Release \
+ --output "$node_publish_directory" \
+ "$source_directory/EventStore.ClusterNode"
+
+find "$source_directory" -maxdepth 2 -type f -name "*.Tests.csproj" -print > "$test_projects"
while IFS= read -r test_project; do
+ test_output_directory="$output_directory/$(basename "$test_project" .csproj)"
dotnet publish \
--runtime="${RUNTIME}" \
--no-self-contained \
--configuration Release \
- --output "$output_directory/$(basename "$test_project")" \
+ --output "$test_output_directory" \
"$test_project"
+ cp "$node_publish_directory/EventStore.ClusterNode.staticwebassets.endpoints.json" "$test_output_directory/"
+ cp -R "$node_publish_directory/wwwroot" "$test_output_directory/"
done < "$test_projects"
diff --git a/src/EventStore.ClusterNode/Components/App.razor b/src/EventStore.ClusterNode/Components/App.razor
index ca46ae726..2e36c7468 100644
--- a/src/EventStore.ClusterNode/Components/App.razor
+++ b/src/EventStore.ClusterNode/Components/App.razor
@@ -4,17 +4,17 @@