Skip to content

Commit b1cf429

Browse files
docs: split lifecycle & ssr into separate docs (#5)
1 parent c5c229c commit b1cf429

File tree

6 files changed

+244
-151
lines changed

6 files changed

+244
-151
lines changed

docs/.vitepress/config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,10 @@ export default defineConfig({
4545
{ text: "Animations & Transitions", link: "/animations" },
4646
],
4747
},
48-
{ text: "Usage", collapsed: false, items: u.scanDir("usage", "/usage") },
4948
{
5049
text: "CSS",
5150
collapsed: false,
52-
items: [{ text: "Volt CSS", link: "/css/volt-css" }, { text: "Reference", link: "/css/semantics" }],
51+
items: [{ text: "VoltX CSS", link: "/css/volt-css" }, { text: "Reference", link: "/css/semantics" }],
5352
docFooterText: "Auto-generated CSS Docs",
5453
},
5554
{ text: "Specs", collapsed: true, items: u.scanDir("spec", "/spec") },

docs/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ For SSR applications, use the `hydrate()` function instead of `charge()` to pres
139139
</script>
140140
```
141141

142-
See the [Server-Side Rendering & Lifecycle](./usage/lifecycle) documentation for complete SSR patterns and hydration strategies.
142+
See the [Server-Side Rendering guide](./usage/ssr) for complete hydration patterns and lifecycle coordination tips.
143143

144144
## Plugin Setup
145145

docs/usage/counter.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,5 @@ This counter demonstrates core VoltX.js concepts:
285285
- [State Management](./state) for advanced signal patterns
286286
- [Bindings](./bindings) for complete binding reference
287287
- [Expressions](./expressions) for template syntax details
288-
- [Lifecycle](./lifecycle) for SSR and hydration
288+
- [Lifecycle](./lifecycle) for mount/unmount hooks
289+
- [Server-Side Rendering](./ssr) for hydration strategies

docs/usage/lifecycle.md

Lines changed: 62 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,95 @@
1-
# Server-Side Rendering & Lifecycle
1+
# Lifecycle Hooks
22

3-
Server-Side Rendering (SSR) with VoltX enables you to render initial HTML on the server and seamlessly hydrate it on the client without re-rendering or flash of unstyled content.
3+
Volt's runtime exposes lifecycle hooks so you can observe mounts, run cleanup logic, and coordinate plugins without re-implementing binding internals. Hooks run consistently for both SSR hydration and client-only mounts.
44

5-
## When to use SSR
5+
## Lifecycle Layers
66

7-
- Content-heavy pages that benefit from SEO
8-
- Applications requiring fast initial render
9-
- Progressive web apps with offline capabilities
10-
- When you need to support users with JavaScript disabled
7+
- **Global hooks** fire for every mount/unmount operation and are ideal for analytics, logging, or cross-cutting concerns.
8+
- **Element hooks** attach to a single DOM element and let you react to that element entering or leaving the document.
9+
- **Plugin hooks** are available while authoring custom bindings and let you scope mount/unmount work to a plugin instance.
1110

12-
## When to use client-side rendering (CSR)
11+
## Global Hooks
1312

14-
- Highly interactive single-page applications
15-
- Applications behind authentication (no SEO needed)
16-
- Rapid prototyping and development
17-
- When server-side rendering adds unnecessary complexity
13+
Register global hooks with `registerGlobalHook(name, callback)`. The available events are:
1814

19-
## Concepts
15+
| Event | Position |
16+
| -------------------------- | ---------------------------------------------------------------------------------------------------- |
17+
| `beforeMount(root, scope)` | Runs right before bindings initialize |
18+
| | This is the place to patch the scope or read serialized state |
19+
| `afterMount(root, scope)` | Runs after VoltX has attached bindings and lifecycle state |
20+
| `beforeUnmount(root)` | Runs before a root is torn down, giving you time to flush pending work |
21+
| `afterUnmount(root)` | Runs after cleanup finishes |
22+
| | Use this to release global resources |
2023

21-
### Server-Side: Rendering Initial HTML
24+
```ts
25+
import { registerGlobalHook } from "@volt/volt";
2226

23-
The server generates HTML with `data-volt` attributes and embedded state. Volt only requires:
27+
const unregister = registerGlobalHook("afterMount", (root, scope) => {
28+
console.debug("[volt] mounted", root.id, scope);
29+
});
2430

25-
1. HTML elements with `data-volt-*` attributes
26-
2. A `<script>` tag containing serialized state as JSON
27-
28-
### Client-Side: Hydration
29-
30-
Instead of re-rendering the DOM, VoltX.js "hydrates" the existing server-rendered HTML by:
31-
32-
1. Reading the embedded state from the `<script>` tag
33-
2. Recreating reactive signals from the serialized values
34-
3. Attaching event listeners and bindings to existing DOM nodes
35-
4. Preserving the existing DOM structure without modifications
36-
37-
## State Serialization
38-
39-
### Server-Side Pattern
40-
41-
Embed initial state in a `<script>` tag with a specific ID pattern:
42-
43-
```html
44-
<div id="app" data-volt>
45-
<script type="application/json" id="volt-state-app">
46-
{"count": 0, "username": "alice"}
47-
</script>
48-
49-
<p data-volt-text="count">0</p>
50-
<p data-volt-text="username">alice</p>
51-
</div>
31+
unregister();
5232
```
5333

54-
- Script tag must have `type="application/json"`
55-
- ID must follow pattern: `volt-state-{element-id}`
56-
- Root element must have an `id` attribute
57-
- State must be valid JSON
34+
### Working with the Scope Object
5835

59-
### Client-Side Deserialization
36+
`beforeMount` and `afterMount` receive the reactive scope for the root element so you can read signal values or stash helpers on the scope.
37+
Avoid mutating DOM inside these hooks-leave DOM updates to bindings/plugins to prevent hydration mismatches.
6038

61-
Use the `hydrate()` function instead of `charge()` to hydrate all `[data-volt]` roots on the page. Volt will:
39+
### Managing Global Hooks
6240

63-
1. Find all elements matching the root selector (default: `[data-volt]`)
64-
2. Check for embedded state in `<script>` tags
65-
3. Deserialize JSON to reactive signals
66-
4. Mount bindings without re-rendering
67-
5. Mark elements as hydrated to prevent double-hydration
41+
- Use `unregisterGlobalHook` when the callback is no longer needed.
42+
- Call `clearGlobalHooks("beforeMount")` or `clearAllGlobalHooks()` in test teardown code to avoid cross-test leakage.
43+
- Prefer one central module to register global hooks so they are easy to audit.
6844

69-
## Avoiding Flash of Unstyled Content (FOUC)
45+
## Element Hooks
7046

71-
### CSS-Based Hiding
47+
When you need per-element notifications, register element hooks:
7248

73-
Hide content until VoltX.js hydrates:
49+
```ts
50+
import { registerElementHook, isElementMounted } from "@volt/volt";
7451

75-
```html
76-
<style>
77-
[data-volt]:not([data-volt-hydrated]) {
78-
visibility: hidden;
79-
}
52+
const panel = document.querySelector("[data-volt-panel]");
8053

81-
[data-volt][data-volt-hydrated] {
82-
visibility: visible;
83-
}
84-
</style>
54+
registerElementHook(panel!, "mount", () => {
55+
console.log("panel is live");
56+
});
8557

86-
<div id="app" data-volt>
87-
<!-- Content is hidden until hydrated -->
88-
</div>
89-
```
58+
registerElementHook(panel!, "unmount", () => {
59+
console.log("panel removed, dispose timers");
60+
});
9061

91-
### Strategy 2: Loading Indicator
92-
93-
Show a loading state during hydration:
94-
95-
```html
96-
<style>
97-
.loading-overlay {
98-
position: fixed;
99-
inset: 0;
100-
background: white;
101-
display: flex;
102-
align-items: center;
103-
justify-content: center;
104-
}
105-
106-
[data-volt-hydrated] ~ .loading-overlay {
107-
display: none;
108-
}
109-
</style>
110-
111-
<div id="app" data-volt>
112-
<!-- App content -->
113-
</div>
114-
<div class="loading-overlay">Loading...</div>
115-
116-
<script>
117-
document.addEventListener('DOMContentLoaded', () => {
118-
Volt.hydrate();
119-
});
120-
</script>
62+
if (isElementMounted(panel!)) {
63+
// Safe to touch DOM or read bindings immediately.
64+
}
12165
```
12266

123-
### Progressive Enhancement
67+
Element hooks automatically dispose after the element unmounts. Use `getElementBindings(element)` when debugging to see which binding directives are attached to a node.
12468

125-
Render fully functional HTML that works without JavaScript, then enhance with interactivity:
69+
## Plugin Lifecycle Hooks
12670

127-
```html
128-
<!-- Form works without JavaScript -->
129-
<form id="contact" method="POST" action="/submit" data-volt>
130-
<script type="application/json" id="volt-state-contact">
131-
{"submitted": false}
132-
</script>
71+
Custom plugins receive lifecycle helpers on the plugin context:
13372

134-
<input type="email" name="email" required>
73+
```ts
74+
import type { PluginContext } from "@volt/volt";
13575

136-
<!-- Enhanced with VoltX.js for client-side validation -->
137-
<p data-volt-if="submitted" data-volt-text="'Thank you!'"></p>
76+
export function focusPlugin(ctx: PluginContext) {
77+
const el = ctx.element as HTMLElement;
13878

139-
<button type="submit">Submit</button>
140-
</form>
79+
ctx.lifecycle.onMount(() => el.focus());
80+
ctx.lifecycle.onUnmount(() => el.blur());
81+
}
14182
```
14283

143-
Can you believe FOUC is an [actual](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) acronym?
144-
145-
## Guidelines/Best Practices
146-
147-
### When to Use SSR vs CSR
148-
149-
**Use SSR for:**
150-
151-
- Any page requiring SEO
152-
153-
**Use CSR for:**
154-
155-
- Complex, interactive and/or real-time applications
156-
157-
### State Management
158-
159-
**Do:**
160-
161-
- Keep server-rendered state minimal (only essential data)
162-
- Use computed signals for derived values (don't serialize them)
163-
- Validate and sanitize state on the server
164-
- Use consistent data structures between server and client
165-
166-
**Don't:**
84+
- `ctx.lifecycle.onMount` and `ctx.lifecycle.onUnmount` let you coordinate DOM state with the binding's lifetime.
85+
- Use `ctx.lifecycle.beforeBinding` and `ctx.lifecycle.afterBinding` to measure binding creation or guard against duplicate initialization.
86+
- Always combine lifecycle hooks with `ctx.addCleanup` if you create subscriptions that outlive a single mount cycle.
16787

168-
- Serialize functions or complex objects
169-
- Include sensitive data in client-side state
170-
- Serialize computed signals (they're recalculated on hydration)
171-
- Embed large datasets (fetch them after hydration instead)
88+
## Best Practices
17289

173-
### Security
90+
- Keep hook callbacks side-effect free whenever possible; defer heavy work to asynchronous tasks.
91+
- Never mutate the DOM tree that VoltX currently manages from `beforeMount`; wait for `afterMount` or plugin hooks instead.
92+
- When adding analytics or telemetry, remember to remove hooks on navigation or single-page route changes to avoid duplicate events.
93+
- In tests, seed hooks inside the test body and tear them down with the disposer returned from `registerGlobalHook` to preserve isolation.
17494

175-
- Escape user-generated content in server-rendered HTML
176-
- Validate state data before serialization
177-
- Use Content Security Policy (CSP) headers
178-
- Sanitize JSON to prevent XSS attacks
95+
For server-rendered workflows and hydration patterns, refer to [ssr](./ssr).

0 commit comments

Comments
 (0)