Root cause of the 404: _trigger_rebuild was firing bincio render (= full astro build), but:
1. The build took minutes → 404 during that window
2. Even after the build, the output lands in site/dist/ — nginx serves from /var/www/bincio/ which is only updated by the rsync in the post-receive hook, not by the server process
Fixes applied:
1. bincio/render/cli.py: Added --no-build flag — merges sidecars and updates manifests but skips astro build. This is fast (~1 second).
2. bincio/serve/server.py _trigger_rebuild: Now passes --no-build. After an upload, _merged/ and root index.json are updated immediately, so the feed reflects the new activity. The static Astro pages are
only rebuilt on git push.
3. site/src/components/ActivityDetailLoader.svelte (new): Svelte component that reads the activity ID from the URL, calls loadIndex to resolve the shard tree, then renders ActivityDetail dynamically — no
pre-built page needed.
4. site/src/pages/activity/index.astro (new): Generic Astro shell page that renders ActivityDetailLoader. Gets compiled to dist/activity/index.html.
5. docs/deployment/vps.md: Added location /activity/ { try_files $uri $uri/ /activity/index.html; } to the nginx config. When a request arrives for /activity/2026-04-06T153345Z/ and no pre-built file
exists, nginx serves the shell, which loads the data dynamically from /data/ (which nginx already serves live from disk).
This commit is contained in:
@@ -168,6 +168,8 @@ def _link_data(site: Path, data: Path) -> None:
|
||||
help="Deploy after build. Currently supports: github.")
|
||||
@click.option("--handle", default=None,
|
||||
help="(Multi-user) Incrementally re-merge one user's shard only.")
|
||||
@click.option("--no-build", "no_build", is_flag=True,
|
||||
help="Skip the Astro build step (just merge sidecars and update manifests).")
|
||||
def render(
|
||||
config_path: Optional[str],
|
||||
data_dir: Optional[str],
|
||||
@@ -176,6 +178,7 @@ def render(
|
||||
serve: bool,
|
||||
deploy: Optional[str],
|
||||
handle: Optional[str],
|
||||
no_build: bool,
|
||||
) -> None:
|
||||
"""Build (or serve) the BincioActivity static site from a BAS data store."""
|
||||
|
||||
@@ -185,9 +188,14 @@ def render(
|
||||
console.print(f"Site: [cyan]{site}[/cyan]")
|
||||
console.print(f"Data: [cyan]{data}[/cyan]")
|
||||
|
||||
_ensure_npm(site)
|
||||
_merge_edits(data, handle=handle)
|
||||
_write_root_manifest(data)
|
||||
|
||||
if no_build:
|
||||
console.print("[green]Data updated.[/green] Skipping Astro build (--no-build).")
|
||||
return
|
||||
|
||||
_ensure_npm(site)
|
||||
_link_data(site, data)
|
||||
|
||||
env = {**os.environ, "BINCIO_DATA_DIR": str(data)}
|
||||
|
||||
@@ -175,7 +175,8 @@ def _trigger_rebuild(handle: str) -> None:
|
||||
[uv, "run", "bincio", "render",
|
||||
"--data-dir", str(data_dir),
|
||||
"--site-dir", str(site_dir),
|
||||
"--handle", handle],
|
||||
"--handle", handle,
|
||||
"--no-build"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
@@ -253,6 +253,17 @@ server {
|
||||
add_header Cache-Control "no-cache, must-revalidate";
|
||||
}
|
||||
|
||||
# Activity detail pages: fall back to the dynamic shell for activities uploaded
|
||||
# after the last site build (avoids 404 while waiting for a rebuild).
|
||||
location /activity/ {
|
||||
try_files $uri $uri/ /activity/index.html;
|
||||
}
|
||||
|
||||
# Per-user profile pages: same fallback for new users.
|
||||
location /u/ {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Static files
|
||||
location / {
|
||||
try_files $uri $uri/ $uri.html =404;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { loadIndex } from '../lib/dataloader';
|
||||
import ActivityDetail from './ActivityDetail.svelte';
|
||||
import type { ActivitySummary } from '../lib/types';
|
||||
|
||||
export let base: string = '/';
|
||||
|
||||
let activity: ActivitySummary | null = null;
|
||||
let notFound = false;
|
||||
let loading = true;
|
||||
|
||||
onMount(async () => {
|
||||
// Extract activity ID from the URL path: /activity/{id}/
|
||||
const match = window.location.pathname.match(/\/activity\/([^/]+)/);
|
||||
const id = match?.[1];
|
||||
if (!id) { notFound = true; loading = false; return; }
|
||||
|
||||
try {
|
||||
const index = await loadIndex(base);
|
||||
activity = index.activities.find(a => a.id === id) ?? null;
|
||||
if (!activity) notFound = true;
|
||||
} catch {
|
||||
notFound = true;
|
||||
}
|
||||
loading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<p class="text-zinc-500 text-sm mt-8 text-center">Loading activity…</p>
|
||||
{:else if notFound}
|
||||
<div class="text-center mt-16">
|
||||
<p class="text-zinc-400 text-sm mb-2">Activity not found.</p>
|
||||
<p class="text-zinc-600 text-xs">It may still be processing — try refreshing in a moment.</p>
|
||||
<a href={base} class="mt-4 inline-block text-blue-400 hover:text-blue-300 text-sm">← Back to feed</a>
|
||||
</div>
|
||||
{:else if activity}
|
||||
<ActivityDetail {activity} {base} />
|
||||
{/if}
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
import Base from '../../layouts/Base.astro';
|
||||
import ActivityDetailLoader from '../../components/ActivityDetailLoader.svelte';
|
||||
---
|
||||
<Base title="Activity — BincioActivity">
|
||||
<ActivityDetailLoader base={import.meta.env.BASE_URL} client:only="svelte" />
|
||||
</Base>
|
||||
Reference in New Issue
Block a user