Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Example applications demonstrating LiveTemplate usage with various features and patterns.

📚 **Framework documentation:** **<https://livetemplate.fly.dev>** — guides, recipes, patterns catalog (with live demos), full reference. The `/examples` and `/patterns` sections of the docs site index every app in this repo.

## Showcase: Todo App

The todo app demonstrates LiveTemplate's core features in ~150 lines of Go + ~80 lines of HTML:
Expand Down
37 changes: 37 additions & 0 deletions landing-demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Deployable image for the LiveTemplate landing-demo. The docs site
# proxies same-origin to this app so the home page can iframe a real
# LiveTemplate counter without cross-origin or chrome friction.

ARG EXAMPLES_REF=main

# ---- Build stage ----
FROM golang:1.26-alpine AS go-builder
ARG EXAMPLES_REF
RUN apk add --no-cache git ca-certificates
ENV GOTOOLCHAIN=auto
WORKDIR /src
RUN git clone --depth=1 --branch=${EXAMPLES_REF} https://github.com/livetemplate/examples.git .
Comment on lines +5 to +13
# Fail fast (vs. silently shipping a stale build) if the requested ref
# does not contain landing-demo. Common cause: deploying before this app
# is merged to main without overriding --build-arg EXAMPLES_REF=<branch>.
RUN test -d /src/landing-demo || (echo "ERROR: landing-demo/ not found at ref '${EXAMPLES_REF}'. If deploying from a branch, pass --build-arg EXAMPLES_REF=<branch-name>." && exit 1)
WORKDIR /src/landing-demo
RUN go mod download -C ..
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-o /out/landing-demo .

# ---- Runtime stage ----
FROM alpine:3.21
RUN apk add --no-cache ca-certificates tzdata
RUN adduser -D -u 1000 demo
WORKDIR /app
COPY --from=go-builder /out/landing-demo /usr/local/bin/landing-demo
# counter.tmpl is loaded via livetemplate.WithParseFiles at runtime as
# a relative path, so cwd must contain it at process start.
COPY --from=go-builder /src/landing-demo/counter.tmpl /app/counter.tmpl
RUN chown -R demo:demo /app
USER demo
EXPOSE 8080
ENV PORT=8080
CMD ["landing-demo"]
36 changes: 36 additions & 0 deletions landing-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# landing-demo

The minimal LiveTemplate counter that powers the live demo on
[livetemplate.fly.dev](https://livetemplate.fly.dev). Deployed standalone
as `lt-landing-demo.fly.dev` and proxied same-origin by the docs site so
the landing page can iframe it without cross-origin friction.

The whole app is `main.go` (~50 lines) plus `counter.tmpl` (~25 lines).
Same code, three transports:

- **Without JS**: form POST, page reloads with new state.
- **With the JS client (fetch)**: same form POSTs via `fetch()`; the DOM is patched in place.
- **With WebSocket**: actions ride the WS; other tabs in the same browser session sync automatically.

## How cross-tab sync works

Two pieces enable it:

- `Count int \`lvt:"persist"\`` makes the field session-store backed, so it survives reconnects and is visible to every connection in the same session group.
- The controller defines a `Sync()` method. The framework treats `Sync` as a reserved name: when present, it auto-dispatches it to peer connections after every action. Peer tabs reload `Count` from the SessionStore (because of the `persist` tag) and re-render with the new value.

Without `Sync()`, peer tabs would only see the latest value on their own next action or a full reload. Without `persist`, peer tabs would re-render but with stale local state.

## Run locally

```bash
go run .
```

Then open http://localhost:8080.

## Deploy

```bash
flyctl deploy --remote-only
```
32 changes: 32 additions & 0 deletions landing-demo/counter.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>Counter — LiveTemplate</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
{{if .lvt.DevMode}}
<link rel="stylesheet" href="/livetemplate.css">
<script defer src="/livetemplate-client.js"></script>
{{else}}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@livetemplate/client@latest/livetemplate.css">
<script defer src="https://cdn.jsdelivr.net/npm/@livetemplate/client@latest/dist/livetemplate-client.browser.js"></script>
{{end}}
</head>
<body>
<main class="container">
<article>
<h1 class="visually-hidden">Live Counter</h1>
<p>Count: <output aria-live="polite"><strong>{{.Count}}</strong></output></p>
<form method="POST">
<fieldset role="group">
<button name="decrement" class="secondary">−1</button>
<button name="reset" class="contrast">Reset</button>
<button name="increment">+1</button>
</fieldset>
</form>
</article>
</main>
</body>
</html>
24 changes: 24 additions & 0 deletions landing-demo/fly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Deployment of the LiveTemplate landing-demo.
# Iframed by the docs site (livetemplate.fly.dev) on the landing page,
# routed via tinkerdown's same-origin proxy at /demo/counter/.

app = "lt-landing-demo"
primary_region = "sjc"

[build]
dockerfile = "Dockerfile"

[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
# This app is iframed on the docs landing page. min_machines_running=1
# keeps a warm machine so the first visitor after idle doesn't see an
# empty iframe during a 10-25s machine wake-up.
min_machines_running = 1

[[vm]]
cpu_kind = "shared"
cpus = 1
memory_mb = 512
Loading
Loading