Skip to content

Commit 91a4385

Browse files
authored
[STG-1823] docs: add server-side caching best practices (browserbase#1994)
1 parent 66f6dbf commit 91a4385

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

packages/docs/v3/best-practices/caching.mdx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,107 @@ console.log(actResult.cacheStatus); // "HIT" or "MISS"
7272
- The page URL factors in to the cache key. If the action is being made on a page with a dynamic URL, caching may not work as expected. We do filter out certain query parameters like referral trackers and analytics, but we don't catch everything just yet.
7373
- If the page content or structure changes, the action won't get a cache `HIT` and the LLM will be called. The subsequent actions will attempt to hit the resulting cache entry.
7474

75+
### Best Practices
76+
77+
<AccordionGroup>
78+
79+
<Accordion title="Wait for the page to finish loading">
80+
If you call a Stagehand method immediately after `page.goto()`, the page content may still be streaming in. This means the accessibility tree captured for the cache key is shorter than what the page will eventually render — so subsequent calls on the fully-loaded page will produce a different hash and miss the cache. Call `page.waitForLoadState("networkidle")` first to ensure the full tree is stable before Stagehand snapshots it.
81+
82+
```typescript
83+
async function example(stagehand: Stagehand) {
84+
const page = stagehand.context.pages()[0];
85+
await page.goto("https://www.google.com/search?q=browserbase");
86+
87+
await page.waitForLoadState("networkidle");
88+
89+
// All content is loaded — consistent hash across runs
90+
const result1 = await stagehand.observe("click the first search result");
91+
// MISS
92+
93+
const result2 = await stagehand.observe("click the first search result");
94+
// HIT
95+
}
96+
```
97+
</Accordion>
98+
99+
<Accordion title="Scope by selector">
100+
When targeting a specific part of a page, pass a `selector` to scope the accessibility tree snapshot to that container. This reduces token costs, speeds up inference, and — crucially for caching — means that changes *outside* the container don't affect the cache key.
101+
102+
```typescript
103+
async function example(stagehand: Stagehand) {
104+
const page = stagehand.context.pages()[0];
105+
await page.goto("https://www.google.com/search?q=browserbase");
106+
107+
await page.waitForLoadState("networkidle");
108+
109+
const result = await stagehand.observe("click the first search result", {
110+
// Scope to the search results container so ads, headers,
111+
// and other surrounding content don't pollute the cache key.
112+
selector: "#rcnt",
113+
});
114+
}
115+
```
116+
</Accordion>
117+
118+
<Accordion title="Use variables for dynamic values">
119+
Using variables in `act()` lets you generalize a single cache entry across many different values. The cache key is built from the variable *keys*, not the values — so `{ email: "alice@example.com" }` and `{ email: "bob@example.com" }` share the same entry.
120+
121+
Without variables you'd accumulate a separate cache miss for every unique email address you type. With variables you prime the cache once and hit it forever.
122+
123+
```typescript
124+
async function example(stagehand: Stagehand) {
125+
const page = stagehand.context.pages()[0];
126+
await page.goto("https://example.com/login");
127+
128+
await page.waitForLoadState("networkidle");
129+
130+
// First run with alice@example.com → MISS (primes cache)
131+
// Any future run, regardless of email value → HIT
132+
await stagehand.act(
133+
"type %email% into the Email address field",
134+
{ variables: { email: "alice@example.com" } },
135+
);
136+
}
137+
```
138+
</Accordion>
139+
140+
<Accordion title="Stabilize the environment">
141+
Small differences in runtime state produce different accessibility trees and therefore different cache keys. Keep your environment as deterministic as possible:
142+
143+
- **Fixed viewport size**`await page.setViewportSize({ width: 1280, height: 720 })`
144+
- **Consistent user agent and locale** — set these in your browser launch options if your stack supports it
145+
- **Block noisy third-party requests** — analytics, A/B testing scripts, and ad trackers can inject DOM nodes that shift the cache key on every load
146+
147+
```typescript
148+
const page = stagehand.context.pages()[0];
149+
150+
// Lock the viewport so the layout is identical across runs
151+
await page.setViewportSize({ width: 1280, height: 720 });
152+
153+
// Block third-party noise that pollutes the accessibility tree
154+
await page.route("**/*.{png,jpg,gif,svg,woff,woff2}", (route) => route.abort());
155+
```
156+
</Accordion>
157+
158+
<Accordion title="Keep prompts deterministic">
159+
The instruction string is part of the cache key. Even minor wording changes — synonyms, extra adjectives, punctuation — produce a new key and a cache miss.
160+
161+
- Anchor instructions to visible UI labels: `"click the Sign in button"` not `"click the button to log me in"`
162+
- Keep instructions short and free of filler words
163+
- Avoid instructions that contain runtime-variable text inline — use `variables` instead
164+
165+
```typescript
166+
// Good: anchored to the visible label, no variance
167+
await stagehand.act("click the Sign in button");
168+
169+
// Bad: inline dynamic value creates a new cache key every time
170+
await stagehand.act(`type ${email} into the Email address field`);
171+
```
172+
</Accordion>
173+
174+
</AccordionGroup>
175+
75176
---
76177

77178
## Local Cache

0 commit comments

Comments
 (0)