@timber-js/app 0.2.0-alpha.167 → 0.2.0-alpha.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{actions-TSxpXLHJ.js → actions-O_LsyCE4.js} +3 -3
- package/dist/_chunks/{actions-TSxpXLHJ.js.map → actions-O_LsyCE4.js.map} +1 -1
- package/dist/_chunks/{cache-api-DzpQQOEx.js → cache-api-B-lhk9p4.js} +56 -11
- package/dist/_chunks/cache-api-B-lhk9p4.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-wX-i90Og.js → cli-schema-sync-B73L6pMq.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-wX-i90Og.js.map → cli-schema-sync-B73L6pMq.js.map} +1 -1
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js +63 -0
- package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +1 -0
- package/dist/_chunks/{logger-t3uxAmbX.js → logger-AWfuX-KJ.js} +2 -19
- package/dist/_chunks/logger-AWfuX-KJ.js.map +1 -0
- package/dist/_chunks/{walkers-Cfwvl-UC.js → walkers-CoOC8Hga.js} +2 -2
- package/dist/_chunks/{walkers-Cfwvl-UC.js.map → walkers-CoOC8Hga.js.map} +1 -1
- package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +9 -2
- package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
- package/dist/cache/cache-api.d.ts.map +1 -1
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cache/json-lossy-check.d.ts +11 -0
- package/dist/cache/json-lossy-check.d.ts.map +1 -0
- package/dist/cache/redis-handler.d.ts +42 -0
- package/dist/cache/redis-handler.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/stores/cloudflare-kv.d.ts +1 -1
- package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -1
- package/dist/cache/stores/memory.d.ts +1 -1
- package/dist/cache/stores/memory.d.ts.map +1 -1
- package/dist/cache/stores/redis.d.ts +1 -1
- package/dist/cache/stores/redis.d.ts.map +1 -1
- package/dist/cache/stores/vercel.d.ts +1 -1
- package/dist/cache/stores/vercel.d.ts.map +1 -1
- package/dist/cache/tag-aware-handler.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/cdn/cloudflare-purge.d.ts.map +1 -1
- package/dist/cdn/fastly-purge.d.ts.map +1 -1
- package/dist/cdn/workers-cache-purge.d.ts.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts +1 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/child-segment-context.d.ts +2 -2
- package/dist/client/child-segment-context.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/internal.js +4 -3
- package/dist/client/internal.js.map +1 -1
- package/dist/client/router.d.ts +6 -0
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-update-context.d.ts +3 -3
- package/dist/client/segment-update-context.d.ts.map +1 -1
- package/dist/client/slot-outlet.d.ts +1 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +2 -1
- package/dist/codec.js.map +1 -1
- package/dist/config-types.d.ts +16 -37
- package/dist/config-types.d.ts.map +1 -1
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/dev-tools/instrumentation.d.ts.map +1 -1
- package/dist/fonts/pipeline.d.ts +19 -0
- package/dist/fonts/pipeline.d.ts.map +1 -1
- package/dist/fonts/transform.d.ts.map +1 -1
- package/dist/fonts/virtual-modules.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +344 -84
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts +1 -0
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +27 -3
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/server/action-client.d.ts +1 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/body-limits.d.ts.map +1 -1
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts.map +1 -1
- package/dist/server/index.js +20 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +24 -40
- package/dist/server/internal.js.map +1 -1
- package/dist/server/middleware-runner.d.ts +0 -17
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +6 -3
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +18 -0
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/prebuilt/capture-state.d.ts.map +1 -1
- package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -1
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +14 -14
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +1 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +44 -74
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/shared/redirect-type.d.ts +2 -2
- package/dist/shared/redirect-type.d.ts.map +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/dist/shims/image.d.ts +120 -120
- package/dist/shims/image.d.ts.map +1 -1
- package/docs/api/32-api-cache.mdx +112 -0
- package/docs/api/34-api-config.mdx +0 -26
- package/docs/learn/09-caching.mdx +121 -5
- package/docs/learn/12-client-navigation.mdx +9 -1
- package/docs/learn/13-configuration.mdx +6 -8
- package/docs/learn/14-deploying.mdx +18 -18
- package/docs/more/04-metadata-and-fonts.mdx +1 -1
- package/package.json +6 -6
- package/src/adapters/cloudflare-kv-cache.ts +27 -6
- package/src/cache/index.ts +1 -1
- package/src/cache/json-lossy-check.ts +75 -0
- package/src/cache/redis-handler.ts +65 -14
- package/src/cache/timber-cache.ts +10 -2
- package/src/client/browser-entry/index.ts +2 -0
- package/src/client/browser-entry/router-init.ts +2 -0
- package/src/client/router.ts +13 -3
- package/src/codec.ts +3 -1
- package/src/config-types.ts +16 -37
- package/src/config-validation.ts +7 -5
- package/src/fonts/pipeline.ts +32 -0
- package/src/fonts/transform.ts +30 -24
- package/src/plugins/adapter-build.ts +131 -13
- package/src/plugins/entries.ts +40 -43
- package/src/plugins/fonts.ts +30 -0
- package/src/plugins/server-bundle.ts +7 -15
- package/src/server/action-client.ts +1 -1
- package/src/server/middleware-runner.ts +0 -44
- package/src/server/param-coercion.ts +1 -0
- package/src/server/pipeline-phases.ts +11 -13
- package/src/server/pipeline.ts +51 -3
- package/src/server/route-element-builder.ts +10 -1
- package/src/server/rsc-entry/action-middleware-runner.ts +14 -14
- package/src/server/rsc-entry/index.ts +30 -40
- package/src/server/rsc-entry/render-route.ts +3 -1
- package/src/server/rsc-entry/wrap-action-dispatch.ts +109 -372
- package/dist/_chunks/cache-api-DzpQQOEx.js.map +0 -1
- package/dist/_chunks/logger-t3uxAmbX.js.map +0 -1
- package/dist/_chunks/tree-match-D2l830j2.js +0 -102
- package/dist/_chunks/tree-match-D2l830j2.js.map +0 -1
package/dist/shims/image.d.ts
CHANGED
|
@@ -31,126 +31,6 @@ export type ImageProps = ImgHTMLAttributes<HTMLImageElement> & {
|
|
|
31
31
|
* props (priority, quality, fill, placeholder, blurDataURL).
|
|
32
32
|
*/
|
|
33
33
|
export declare function Image({ priority: _priority, quality: _quality, fill: _fill, placeholder: _placeholder, blurDataURL: _blurDataURL, ...rest }: ImageProps): import("react").DetailedReactHTMLElement<{
|
|
34
|
-
alt?: string | undefined;
|
|
35
|
-
crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
|
|
36
|
-
decoding?: "async" | "auto" | "sync" | undefined;
|
|
37
|
-
fetchPriority?: "high" | "low" | "auto" | undefined;
|
|
38
|
-
height?: number | string | undefined;
|
|
39
|
-
loading?: "eager" | "lazy" | undefined;
|
|
40
|
-
referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
|
|
41
|
-
sizes?: string | undefined;
|
|
42
|
-
src?: string | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES] | undefined;
|
|
43
|
-
srcSet?: string | undefined;
|
|
44
|
-
useMap?: string | undefined;
|
|
45
|
-
width?: number | string | undefined;
|
|
46
|
-
defaultChecked?: boolean | undefined;
|
|
47
|
-
defaultValue?: string | number | readonly string[] | undefined;
|
|
48
|
-
suppressContentEditableWarning?: boolean | undefined;
|
|
49
|
-
suppressHydrationWarning?: boolean | undefined;
|
|
50
|
-
accessKey?: string | undefined;
|
|
51
|
-
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters" | undefined | (string & {});
|
|
52
|
-
autoFocus?: boolean | undefined;
|
|
53
|
-
className?: string | undefined;
|
|
54
|
-
contentEditable?: (boolean | "true" | "false") | "inherit" | "plaintext-only" | undefined;
|
|
55
|
-
contextMenu?: string | undefined;
|
|
56
|
-
dir?: string | undefined;
|
|
57
|
-
draggable?: (boolean | "true" | "false") | undefined;
|
|
58
|
-
enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined;
|
|
59
|
-
hidden?: boolean | undefined;
|
|
60
|
-
id?: string | undefined;
|
|
61
|
-
lang?: string | undefined;
|
|
62
|
-
nonce?: string | undefined;
|
|
63
|
-
slot?: string | undefined;
|
|
64
|
-
spellCheck?: (boolean | "true" | "false") | undefined;
|
|
65
|
-
style?: import("react").CSSProperties | undefined;
|
|
66
|
-
tabIndex?: number | undefined;
|
|
67
|
-
title?: string | undefined;
|
|
68
|
-
translate?: "yes" | "no" | undefined;
|
|
69
|
-
radioGroup?: string | undefined;
|
|
70
|
-
role?: import("react").AriaRole | undefined;
|
|
71
|
-
about?: string | undefined;
|
|
72
|
-
content?: string | undefined;
|
|
73
|
-
datatype?: string | undefined;
|
|
74
|
-
inlist?: any;
|
|
75
|
-
prefix?: string | undefined;
|
|
76
|
-
property?: string | undefined;
|
|
77
|
-
rel?: string | undefined;
|
|
78
|
-
resource?: string | undefined;
|
|
79
|
-
rev?: string | undefined;
|
|
80
|
-
typeof?: string | undefined;
|
|
81
|
-
vocab?: string | undefined;
|
|
82
|
-
autoCorrect?: string | undefined;
|
|
83
|
-
autoSave?: string | undefined;
|
|
84
|
-
color?: string | undefined;
|
|
85
|
-
itemProp?: string | undefined;
|
|
86
|
-
itemScope?: boolean | undefined;
|
|
87
|
-
itemType?: string | undefined;
|
|
88
|
-
itemID?: string | undefined;
|
|
89
|
-
itemRef?: string | undefined;
|
|
90
|
-
results?: number | undefined;
|
|
91
|
-
security?: string | undefined;
|
|
92
|
-
unselectable?: "on" | "off" | undefined;
|
|
93
|
-
popover?: "" | "auto" | "manual" | "hint" | undefined;
|
|
94
|
-
popoverTargetAction?: "toggle" | "show" | "hide" | undefined;
|
|
95
|
-
popoverTarget?: string | undefined;
|
|
96
|
-
inert?: boolean | undefined;
|
|
97
|
-
inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined;
|
|
98
|
-
is?: string | undefined;
|
|
99
|
-
exportparts?: string | undefined;
|
|
100
|
-
part?: string | undefined;
|
|
101
|
-
"aria-activedescendant"?: string | undefined;
|
|
102
|
-
"aria-atomic"?: (boolean | "true" | "false") | undefined;
|
|
103
|
-
"aria-autocomplete"?: "none" | "inline" | "list" | "both" | undefined;
|
|
104
|
-
"aria-braillelabel"?: string | undefined;
|
|
105
|
-
"aria-brailleroledescription"?: string | undefined;
|
|
106
|
-
"aria-busy"?: (boolean | "true" | "false") | undefined;
|
|
107
|
-
"aria-checked"?: boolean | "false" | "mixed" | "true" | undefined;
|
|
108
|
-
"aria-colcount"?: number | undefined;
|
|
109
|
-
"aria-colindex"?: number | undefined;
|
|
110
|
-
"aria-colindextext"?: string | undefined;
|
|
111
|
-
"aria-colspan"?: number | undefined;
|
|
112
|
-
"aria-controls"?: string | undefined;
|
|
113
|
-
"aria-current"?: boolean | "false" | "true" | "page" | "step" | "location" | "date" | "time" | undefined;
|
|
114
|
-
"aria-describedby"?: string | undefined;
|
|
115
|
-
"aria-description"?: string | undefined;
|
|
116
|
-
"aria-details"?: string | undefined;
|
|
117
|
-
"aria-disabled"?: (boolean | "true" | "false") | undefined;
|
|
118
|
-
"aria-dropeffect"?: "none" | "copy" | "execute" | "link" | "move" | "popup" | undefined;
|
|
119
|
-
"aria-errormessage"?: string | undefined;
|
|
120
|
-
"aria-expanded"?: (boolean | "true" | "false") | undefined;
|
|
121
|
-
"aria-flowto"?: string | undefined;
|
|
122
|
-
"aria-grabbed"?: (boolean | "true" | "false") | undefined;
|
|
123
|
-
"aria-haspopup"?: boolean | "false" | "true" | "menu" | "listbox" | "tree" | "grid" | "dialog" | undefined;
|
|
124
|
-
"aria-hidden"?: (boolean | "true" | "false") | undefined;
|
|
125
|
-
"aria-invalid"?: boolean | "false" | "true" | "grammar" | "spelling" | undefined;
|
|
126
|
-
"aria-keyshortcuts"?: string | undefined;
|
|
127
|
-
"aria-label"?: string | undefined;
|
|
128
|
-
"aria-labelledby"?: string | undefined;
|
|
129
|
-
"aria-level"?: number | undefined;
|
|
130
|
-
"aria-live"?: "off" | "assertive" | "polite" | undefined;
|
|
131
|
-
"aria-modal"?: (boolean | "true" | "false") | undefined;
|
|
132
|
-
"aria-multiline"?: (boolean | "true" | "false") | undefined;
|
|
133
|
-
"aria-multiselectable"?: (boolean | "true" | "false") | undefined;
|
|
134
|
-
"aria-orientation"?: "horizontal" | "vertical" | undefined;
|
|
135
|
-
"aria-owns"?: string | undefined;
|
|
136
|
-
"aria-placeholder"?: string | undefined;
|
|
137
|
-
"aria-posinset"?: number | undefined;
|
|
138
|
-
"aria-pressed"?: boolean | "false" | "mixed" | "true" | undefined;
|
|
139
|
-
"aria-readonly"?: (boolean | "true" | "false") | undefined;
|
|
140
|
-
"aria-relevant"?: "additions" | "additions removals" | "additions text" | "all" | "removals" | "removals additions" | "removals text" | "text" | "text additions" | "text removals" | undefined;
|
|
141
|
-
"aria-required"?: (boolean | "true" | "false") | undefined;
|
|
142
|
-
"aria-roledescription"?: string | undefined;
|
|
143
|
-
"aria-rowcount"?: number | undefined;
|
|
144
|
-
"aria-rowindex"?: number | undefined;
|
|
145
|
-
"aria-rowindextext"?: string | undefined;
|
|
146
|
-
"aria-rowspan"?: number | undefined;
|
|
147
|
-
"aria-selected"?: (boolean | "true" | "false") | undefined;
|
|
148
|
-
"aria-setsize"?: number | undefined;
|
|
149
|
-
"aria-sort"?: "none" | "ascending" | "descending" | "other" | undefined;
|
|
150
|
-
"aria-valuemax"?: number | undefined;
|
|
151
|
-
"aria-valuemin"?: number | undefined;
|
|
152
|
-
"aria-valuenow"?: number | undefined;
|
|
153
|
-
"aria-valuetext"?: string | undefined;
|
|
154
34
|
children?: import("react").ReactNode | undefined;
|
|
155
35
|
dangerouslySetInnerHTML?: {
|
|
156
36
|
__html: string | TrustedHTML;
|
|
@@ -323,6 +203,126 @@ export declare function Image({ priority: _priority, quality: _quality, fill: _f
|
|
|
323
203
|
onTransitionRunCapture?: import("react").TransitionEventHandler<HTMLImageElement> | undefined;
|
|
324
204
|
onTransitionStart?: import("react").TransitionEventHandler<HTMLImageElement> | undefined;
|
|
325
205
|
onTransitionStartCapture?: import("react").TransitionEventHandler<HTMLImageElement> | undefined;
|
|
206
|
+
"aria-activedescendant"?: string | undefined;
|
|
207
|
+
"aria-atomic"?: ("false" | "true" | boolean) | undefined;
|
|
208
|
+
"aria-autocomplete"?: "none" | "inline" | "list" | "both" | undefined;
|
|
209
|
+
"aria-braillelabel"?: string | undefined;
|
|
210
|
+
"aria-brailleroledescription"?: string | undefined;
|
|
211
|
+
"aria-busy"?: ("false" | "true" | boolean) | undefined;
|
|
212
|
+
"aria-checked"?: boolean | "false" | "mixed" | "true" | undefined;
|
|
213
|
+
"aria-colcount"?: number | undefined;
|
|
214
|
+
"aria-colindex"?: number | undefined;
|
|
215
|
+
"aria-colindextext"?: string | undefined;
|
|
216
|
+
"aria-colspan"?: number | undefined;
|
|
217
|
+
"aria-controls"?: string | undefined;
|
|
218
|
+
"aria-current"?: boolean | "false" | "true" | "page" | "step" | "location" | "date" | "time" | undefined;
|
|
219
|
+
"aria-describedby"?: string | undefined;
|
|
220
|
+
"aria-description"?: string | undefined;
|
|
221
|
+
"aria-details"?: string | undefined;
|
|
222
|
+
"aria-disabled"?: ("false" | "true" | boolean) | undefined;
|
|
223
|
+
"aria-dropeffect"?: "none" | "copy" | "execute" | "link" | "move" | "popup" | undefined;
|
|
224
|
+
"aria-errormessage"?: string | undefined;
|
|
225
|
+
"aria-expanded"?: ("false" | "true" | boolean) | undefined;
|
|
226
|
+
"aria-flowto"?: string | undefined;
|
|
227
|
+
"aria-grabbed"?: ("false" | "true" | boolean) | undefined;
|
|
228
|
+
"aria-haspopup"?: boolean | "false" | "true" | "menu" | "listbox" | "tree" | "grid" | "dialog" | undefined;
|
|
229
|
+
"aria-hidden"?: ("false" | "true" | boolean) | undefined;
|
|
230
|
+
"aria-invalid"?: boolean | "false" | "true" | "grammar" | "spelling" | undefined;
|
|
231
|
+
"aria-keyshortcuts"?: string | undefined;
|
|
232
|
+
"aria-label"?: string | undefined;
|
|
233
|
+
"aria-labelledby"?: string | undefined;
|
|
234
|
+
"aria-level"?: number | undefined;
|
|
235
|
+
"aria-live"?: "off" | "assertive" | "polite" | undefined;
|
|
236
|
+
"aria-modal"?: ("false" | "true" | boolean) | undefined;
|
|
237
|
+
"aria-multiline"?: ("false" | "true" | boolean) | undefined;
|
|
238
|
+
"aria-multiselectable"?: ("false" | "true" | boolean) | undefined;
|
|
239
|
+
"aria-orientation"?: "horizontal" | "vertical" | undefined;
|
|
240
|
+
"aria-owns"?: string | undefined;
|
|
241
|
+
"aria-placeholder"?: string | undefined;
|
|
242
|
+
"aria-posinset"?: number | undefined;
|
|
243
|
+
"aria-pressed"?: boolean | "false" | "mixed" | "true" | undefined;
|
|
244
|
+
"aria-readonly"?: ("false" | "true" | boolean) | undefined;
|
|
245
|
+
"aria-relevant"?: "additions" | "additions removals" | "additions text" | "all" | "removals" | "removals additions" | "removals text" | "text" | "text additions" | "text removals" | undefined;
|
|
246
|
+
"aria-required"?: ("false" | "true" | boolean) | undefined;
|
|
247
|
+
"aria-roledescription"?: string | undefined;
|
|
248
|
+
"aria-rowcount"?: number | undefined;
|
|
249
|
+
"aria-rowindex"?: number | undefined;
|
|
250
|
+
"aria-rowindextext"?: string | undefined;
|
|
251
|
+
"aria-rowspan"?: number | undefined;
|
|
252
|
+
"aria-selected"?: ("false" | "true" | boolean) | undefined;
|
|
253
|
+
"aria-setsize"?: number | undefined;
|
|
254
|
+
"aria-sort"?: "none" | "ascending" | "descending" | "other" | undefined;
|
|
255
|
+
"aria-valuemax"?: number | undefined;
|
|
256
|
+
"aria-valuemin"?: number | undefined;
|
|
257
|
+
"aria-valuenow"?: number | undefined;
|
|
258
|
+
"aria-valuetext"?: string | undefined;
|
|
259
|
+
defaultChecked?: boolean | undefined;
|
|
260
|
+
defaultValue?: string | number | readonly string[] | undefined;
|
|
261
|
+
suppressContentEditableWarning?: boolean | undefined;
|
|
262
|
+
suppressHydrationWarning?: boolean | undefined;
|
|
263
|
+
accessKey?: string | undefined;
|
|
264
|
+
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters" | undefined | (string & {});
|
|
265
|
+
autoFocus?: boolean | undefined;
|
|
266
|
+
className?: string | undefined;
|
|
267
|
+
contentEditable?: ("false" | "true" | boolean) | "inherit" | "plaintext-only" | undefined;
|
|
268
|
+
contextMenu?: string | undefined;
|
|
269
|
+
dir?: string | undefined;
|
|
270
|
+
draggable?: ("false" | "true" | boolean) | undefined;
|
|
271
|
+
enterKeyHint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined;
|
|
272
|
+
hidden?: boolean | undefined;
|
|
273
|
+
id?: string | undefined;
|
|
274
|
+
lang?: string | undefined;
|
|
275
|
+
nonce?: string | undefined;
|
|
276
|
+
slot?: string | undefined;
|
|
277
|
+
spellCheck?: ("false" | "true" | boolean) | undefined;
|
|
278
|
+
style?: import("react").CSSProperties | undefined;
|
|
279
|
+
tabIndex?: number | undefined;
|
|
280
|
+
title?: string | undefined;
|
|
281
|
+
translate?: "yes" | "no" | undefined;
|
|
282
|
+
radioGroup?: string | undefined;
|
|
283
|
+
role?: import("react").AriaRole | undefined;
|
|
284
|
+
about?: string | undefined;
|
|
285
|
+
content?: string | undefined;
|
|
286
|
+
datatype?: string | undefined;
|
|
287
|
+
inlist?: any;
|
|
288
|
+
prefix?: string | undefined;
|
|
289
|
+
property?: string | undefined;
|
|
290
|
+
rel?: string | undefined;
|
|
291
|
+
resource?: string | undefined;
|
|
292
|
+
rev?: string | undefined;
|
|
293
|
+
typeof?: string | undefined;
|
|
294
|
+
vocab?: string | undefined;
|
|
295
|
+
autoCorrect?: string | undefined;
|
|
296
|
+
autoSave?: string | undefined;
|
|
297
|
+
color?: string | undefined;
|
|
298
|
+
itemProp?: string | undefined;
|
|
299
|
+
itemScope?: boolean | undefined;
|
|
300
|
+
itemType?: string | undefined;
|
|
301
|
+
itemID?: string | undefined;
|
|
302
|
+
itemRef?: string | undefined;
|
|
303
|
+
results?: number | undefined;
|
|
304
|
+
security?: string | undefined;
|
|
305
|
+
unselectable?: "on" | "off" | undefined;
|
|
306
|
+
popover?: "" | "auto" | "manual" | "hint" | undefined;
|
|
307
|
+
popoverTargetAction?: "toggle" | "show" | "hide" | undefined;
|
|
308
|
+
popoverTarget?: string | undefined;
|
|
309
|
+
inert?: boolean | undefined;
|
|
310
|
+
inputMode?: "none" | "text" | "tel" | "url" | "email" | "numeric" | "decimal" | "search" | undefined;
|
|
311
|
+
is?: string | undefined;
|
|
312
|
+
exportparts?: string | undefined;
|
|
313
|
+
part?: string | undefined;
|
|
314
|
+
alt?: string | undefined;
|
|
315
|
+
crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
|
|
316
|
+
decoding?: "async" | "auto" | "sync" | undefined;
|
|
317
|
+
fetchPriority?: "high" | "low" | "auto" | undefined;
|
|
318
|
+
height?: number | string | undefined;
|
|
319
|
+
loading?: "eager" | "lazy" | undefined;
|
|
320
|
+
referrerPolicy?: import("react").HTMLAttributeReferrerPolicy | undefined;
|
|
321
|
+
sizes?: string | undefined;
|
|
322
|
+
src?: string | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES] | undefined;
|
|
323
|
+
srcSet?: string | undefined;
|
|
324
|
+
useMap?: string | undefined;
|
|
325
|
+
width?: number | string | undefined;
|
|
326
326
|
}, HTMLElement>;
|
|
327
327
|
export default Image;
|
|
328
328
|
//# sourceMappingURL=image.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/shims/image.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG,iBAAiB,CAAC,gBAAgB,CAAC,GAAG;IAC7D,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EAAE,SAAS,EACnB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,EACX,WAAW,EAAE,YAAY,EACzB,WAAW,EAAE,YAAY,EACzB,GAAG,IAAI,EACR,EAAE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAEZ;
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/shims/image.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG,iBAAiB,CAAC,gBAAgB,CAAC,GAAG;IAC7D,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EAAE,SAAS,EACnB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,EACX,WAAW,EAAE,YAAY,EACzB,WAAW,EAAE,YAAY,EACzB,GAAG,IAAI,EACR,EAAE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAEZ;eAEc,KAAK"}
|
|
@@ -56,6 +56,101 @@ cache.invalidate({ tag: 'products' });
|
|
|
56
56
|
cache.invalidate({ key: 'user:abc' });
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## `cache.component(Component, options)`
|
|
60
|
+
|
|
61
|
+
Wrap an async server component to cache its rendered RSC flight payload. The component renders once and subsequent requests splice the cached output into the live stream.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { cache } from '@timber-js/app/cache';
|
|
65
|
+
|
|
66
|
+
const PostBody = cache.component(
|
|
67
|
+
async function PostBody() {
|
|
68
|
+
const post = await getPost();
|
|
69
|
+
return <article>{post.content}</article>;
|
|
70
|
+
},
|
|
71
|
+
{ prerender: true }
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`prerender` controls **when** the cache is populated. `ttl`/`tags` control **how it refreshes at runtime**. They are orthogonal — every combination is a meaningful tier:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Static: rendered at build, changes only on redeploy
|
|
79
|
+
const BlogBody = cache.component(Body, { prerender: true });
|
|
80
|
+
|
|
81
|
+
// Cached: rendered on first request, refreshed by TTL/tags
|
|
82
|
+
const Pricing = cache.component(PricingTable, { ttl: 300, tags: ['pricing'] });
|
|
83
|
+
|
|
84
|
+
// ISR: rendered at build, refreshable without a redeploy
|
|
85
|
+
const DocsPage = cache.component(DocsBody, { prerender: true, ttl: 3600, tags: ['docs'] });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `PrebuiltComponentOptions`
|
|
89
|
+
|
|
90
|
+
| Option | Type | Description |
|
|
91
|
+
| ------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `prerender` | `boolean` | Populate at build time via `generateStaticSegmentParams` |
|
|
93
|
+
| `ttl` | `number` | Runtime freshness window in seconds |
|
|
94
|
+
| `tags` | `string[] \| (props) => string[]` | Tags for invalidation via `revalidateTag` |
|
|
95
|
+
| `staleWhileRevalidate` | `boolean` | Serve stale while a background re-render refreshes |
|
|
96
|
+
| `slots` | `string[]` | Prop names whose values are live React elements composed into the cached shell at request time |
|
|
97
|
+
| `fallbackOnMissingSegmentParams`| `boolean` | When `false`, a cache miss throws instead of rendering live (default: `true`) |
|
|
98
|
+
|
|
99
|
+
Only module-scope declarations are transformed at build time:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// ✅ Transformed — eligible for prebuilding
|
|
103
|
+
const Shell = cache.component(MyComponent, { prerender: true });
|
|
104
|
+
|
|
105
|
+
// ❌ Not transformed — renders dynamically with a warning
|
|
106
|
+
export default function Page() {
|
|
107
|
+
const Cached = cache.component(Inner, { ttl: 60 }); // nested scope
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Tier Rules
|
|
112
|
+
|
|
113
|
+
| Declaration | Enumerated params | Unenumerated miss |
|
|
114
|
+
| ---------------------------- | ----------------------- | ------------------------------------ |
|
|
115
|
+
| `prerender` only | Built at deploy | Live render every time, never stored |
|
|
116
|
+
| `ttl`/`tags` only | — | Rendered + stored on first request |
|
|
117
|
+
| `prerender` + `ttl`/`tags` | Built at deploy | Rendered + stored on first request |
|
|
118
|
+
|
|
119
|
+
### Slots
|
|
120
|
+
|
|
121
|
+
Cache a wrapper component's output as a shell with holes for live children:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
const Shell = cache.component(ExpensiveWrapper, {
|
|
125
|
+
ttl: 3600,
|
|
126
|
+
tags: ['marketing'],
|
|
127
|
+
slots: ['children'],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Request time: the shell is served from cache; children render live.
|
|
131
|
+
<Shell theme="dark"><LiveUserFeed /></Shell>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Slot props are excluded from the cache key — two instances with the same data props share one shell entry. Slots are **runtime tier only**: `prerender` is ignored when `slots` is active because the build cannot enumerate children variants.
|
|
135
|
+
|
|
136
|
+
### Route-Level Sugar
|
|
137
|
+
|
|
138
|
+
For "this whole route is static," export `prerender` from a page or layout instead of wrapping manually:
|
|
139
|
+
|
|
140
|
+
```ts title="Fully static route"
|
|
141
|
+
export const prerender = true;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```ts title="Route-level ISR"
|
|
145
|
+
export const prerender = { ttl: 3600, tags: ['docs'] };
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
This desugars to wrapping the page's default export in `cache.component(...)`. Layout `prerender` cascades to descendant pages unless overridden (most-specific-wins).
|
|
149
|
+
|
|
150
|
+
### Nesting
|
|
151
|
+
|
|
152
|
+
When a cached component renders inside another cached component, the outer entry's lifetime wins. The inner's output is baked into the outer's payload as a flat copy — `revalidateTag` on the inner's tags does **not** refresh the embedded copy. The framework warns at capture time when the inner declares a refresh channel the outer cannot honor.
|
|
153
|
+
|
|
59
154
|
## `CacheHandler` Interface
|
|
60
155
|
|
|
61
156
|
Implement this to plug in a custom cache backend.
|
|
@@ -100,15 +195,19 @@ interface RedisClient {
|
|
|
100
195
|
srem(key: string, ...members: string[]): Promise<unknown>;
|
|
101
196
|
smembers(key: string): Promise<string[]>;
|
|
102
197
|
ttl(key: string): Promise<number>;
|
|
198
|
+
execMulti?(ops: RedisWriteOp[]): Promise<void>; // optional MULTI/EXEC
|
|
103
199
|
}
|
|
104
200
|
```
|
|
105
201
|
|
|
106
202
|
`expireNX` and `expireGT` map to Redis 7.0's `EXPIRE key seconds NX|GT` subcommands. `NX` sets the TTL only if none exists, `GT` extends only if greater. Both are used internally for atomic tag set TTL management. Separate methods ensure adapters cannot silently drop the mode. Requires **Redis 7.0+**.
|
|
107
203
|
|
|
204
|
+
`execMulti` is optional. When provided, `set()` executes tag-index writes and the data write atomically via `MULTI/EXEC`, preventing concurrent `invalidate({tag})` calls from interleaving. When absent, writes are sequential (correct but with a narrow self-healing race window).
|
|
205
|
+
|
|
108
206
|
This is deliberately not the native shape of any client library — ioredis, node-redis, and @upstash/redis disagree on TTL argument shapes and method casing, and a contract that pattern-matches one client lets another silently write keys with no expiry. Wrap your client with one of these adapters:
|
|
109
207
|
|
|
110
208
|
```ts title="ioredis"
|
|
111
209
|
import { RedisCacheHandler } from '@timber-js/app/cache';
|
|
210
|
+
import type { RedisWriteOp } from '@timber-js/app/cache';
|
|
112
211
|
import Redis from 'ioredis';
|
|
113
212
|
|
|
114
213
|
const redis = new Redis(process.env.REDIS_URL!);
|
|
@@ -122,6 +221,19 @@ const handler = new RedisCacheHandler({
|
|
|
122
221
|
srem: (k, ...m) => redis.srem(k, ...m),
|
|
123
222
|
smembers: (k) => redis.smembers(k),
|
|
124
223
|
ttl: (k) => redis.ttl(k),
|
|
224
|
+
execMulti: async (ops) => {
|
|
225
|
+
const p = redis.multi();
|
|
226
|
+
for (const op of ops) {
|
|
227
|
+
switch (op.op) {
|
|
228
|
+
case 'set': p.set(op.key, op.value, 'EX', op.ttlSeconds); break;
|
|
229
|
+
case 'sadd': p.sadd(op.key, ...op.members); break;
|
|
230
|
+
case 'expireNX': p.expire(op.key, op.seconds, 'NX'); break;
|
|
231
|
+
case 'expireGT': p.expire(op.key, op.seconds, 'GT'); break;
|
|
232
|
+
case 'del': p.del(op.key); break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
await p.exec();
|
|
236
|
+
},
|
|
125
237
|
});
|
|
126
238
|
```
|
|
127
239
|
|
|
@@ -17,8 +17,6 @@ interface TimberUserConfig {
|
|
|
17
17
|
buildDir?: string;
|
|
18
18
|
clientJavascript?: boolean | ClientJavascriptConfig;
|
|
19
19
|
adapter?: TimberPlatformAdapter;
|
|
20
|
-
cacheHandler?: CacheHandler;
|
|
21
|
-
cdnPurge?: CdnPurgeHandler;
|
|
22
20
|
serverTiming?: 'detailed' | 'total' | false;
|
|
23
21
|
allowedOrigins?: string[];
|
|
24
22
|
csrf?: boolean;
|
|
@@ -27,9 +25,6 @@ interface TimberUserConfig {
|
|
|
27
25
|
uploadBodySize?: string;
|
|
28
26
|
maxFields?: number;
|
|
29
27
|
};
|
|
30
|
-
actions?: {
|
|
31
|
-
runMiddleware?: boolean;
|
|
32
|
-
};
|
|
33
28
|
forms?: {
|
|
34
29
|
stripSensitiveFields?: boolean | readonly string[];
|
|
35
30
|
};
|
|
@@ -119,13 +114,6 @@ adapter: cloudflare();
|
|
|
119
114
|
adapter: nitro({ preset: 'node-server' });
|
|
120
115
|
```
|
|
121
116
|
|
|
122
|
-
### `cacheHandler`
|
|
123
|
-
|
|
124
|
-
- **Type:** `CacheHandler`
|
|
125
|
-
- **Default:** `MemoryCacheHandler`
|
|
126
|
-
|
|
127
|
-
Cache backend for `timber.cache()`. See [Cache API](/docs/api-cache).
|
|
128
|
-
|
|
129
117
|
### `serverTiming`
|
|
130
118
|
|
|
131
119
|
- **Type:** `'detailed' | 'total' | false`
|
|
@@ -159,13 +147,6 @@ limits: {
|
|
|
159
147
|
}
|
|
160
148
|
```
|
|
161
149
|
|
|
162
|
-
### `actions.runMiddleware`
|
|
163
|
-
|
|
164
|
-
- **Type:** `boolean`
|
|
165
|
-
- **Default:** `true`
|
|
166
|
-
|
|
167
|
-
Run `middleware.ts` on server action requests before dispatching. Keeps authentication, rate limiting, and other cross-cutting checks applied uniformly to page renders, route handlers, and server actions. Set to `false` only if your middleware would corrupt action submissions (e.g. it rewrites POST bodies) — you then own placing those checks inside every action.
|
|
168
|
-
|
|
169
150
|
### `forms.stripSensitiveFields`
|
|
170
151
|
|
|
171
152
|
- **Type:** `boolean | string[]`
|
|
@@ -191,13 +172,6 @@ Two files claiming the same convention in one directory (e.g. `page.tsx` + `page
|
|
|
191
172
|
|
|
192
173
|
Enable timber debug logging in production builds. When `true`, timber's own diagnostics (dev warnings, verbose logging) are active even in production mode. React stays in production mode — only timber's logs are affected. Can also be enabled at runtime via the `TIMBER_DEBUG` environment variable.
|
|
193
174
|
|
|
194
|
-
### `cdnPurge`
|
|
195
|
-
|
|
196
|
-
- **Type:** `CdnPurgeHandler`
|
|
197
|
-
- **Default:** none
|
|
198
|
-
|
|
199
|
-
CDN purge handler called on `revalidateTag` / `cache.invalidate` to purge CDN-cached responses.
|
|
200
|
-
|
|
201
175
|
### `slowRequestMs`
|
|
202
176
|
|
|
203
177
|
- **Type:** `number`
|
|
@@ -117,16 +117,132 @@ On serverless platforms (Cloudflare Workers, Lambda), the background refetch is
|
|
|
117
117
|
|
|
118
118
|
When multiple concurrent requests miss the cache for the same key, only one executes the underlying function. The rest wait for the result. The result is written to the cache exactly once, by the execution that ran the function. This is built in — no opt-in needed.
|
|
119
119
|
|
|
120
|
+
## Caching Components
|
|
121
|
+
|
|
122
|
+
`cache.data` caches a function's return value. `cache.component` caches a component's **rendered output** — the RSC flight payload — so re-renders skip the entire component tree:
|
|
123
|
+
|
|
124
|
+
```tsx title="app/blog/[slug]/page.tsx"
|
|
125
|
+
import { cache } from '@timber-js/app/cache';
|
|
126
|
+
import { getSegmentParams } from '@timber-js/app/server';
|
|
127
|
+
|
|
128
|
+
const PostBody = cache.component(
|
|
129
|
+
async function PostBody() {
|
|
130
|
+
const { slug } = getSegmentParams();
|
|
131
|
+
const post = await getPost(slug);
|
|
132
|
+
return <article><h1>{post.title}</h1><PostContent content={post.content} /></article>;
|
|
133
|
+
},
|
|
134
|
+
{ prerender: true }
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
export async function generateStaticSegmentParams() {
|
|
138
|
+
return allPosts.map((p) => ({ slug: p.slug }));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default async function BlogPost() {
|
|
142
|
+
return <PostBody />;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The same API gives you three tiers of freshness, controlled by two options:
|
|
147
|
+
|
|
148
|
+
### Static (build-time only)
|
|
149
|
+
|
|
150
|
+
Rendered at build for each set of params from `generateStaticSegmentParams`. Changes only on redeploy. Request context (`getCookieJar()`, `getHeaders()`) is a build error.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
const BlogBody = cache.component(Body, { prerender: true });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Cached (runtime only)
|
|
157
|
+
|
|
158
|
+
Rendered on first request, stored with a TTL. Invalidatable by tag. May read request context (with a warning — be careful not to leak per-user data into a shared cache).
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
const Pricing = cache.component(PricingTable, {
|
|
162
|
+
ttl: 300,
|
|
163
|
+
tags: ['pricing'],
|
|
164
|
+
staleWhileRevalidate: true,
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### ISR (build-seeded, runtime-refreshable)
|
|
169
|
+
|
|
170
|
+
Rendered at build, but refreshable via `revalidateTag` without a redeploy. Unenumerated params fill in on first request.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
const DocsPage = cache.component(DocsBody, {
|
|
174
|
+
prerender: true,
|
|
175
|
+
ttl: 3600,
|
|
176
|
+
tags: ['docs'],
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Mixing Tiers on One Page
|
|
181
|
+
|
|
182
|
+
A page can combine all three — a dynamic shell with cached islands:
|
|
183
|
+
|
|
184
|
+
```tsx title="app/blog/[slug]/page.tsx"
|
|
185
|
+
import { getCookieJar } from '@timber-js/app/server';
|
|
186
|
+
|
|
187
|
+
export default async function BlogPost() {
|
|
188
|
+
const user = await getUser(getCookieJar());
|
|
189
|
+
return (
|
|
190
|
+
<>
|
|
191
|
+
<Nav user={user} /> {/* dynamic — personalized */}
|
|
192
|
+
<PostBody /> {/* prerendered — from build artifact */}
|
|
193
|
+
<Suspense fallback={<CommentsSkeleton />}>
|
|
194
|
+
<Comments /> {/* dynamic — streams per-request */}
|
|
195
|
+
</Suspense>
|
|
196
|
+
</>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Route-Level Shorthand
|
|
202
|
+
|
|
203
|
+
For fully static routes, export `prerender` instead of wrapping the component manually:
|
|
204
|
+
|
|
205
|
+
```ts title="app/about/page.tsx"
|
|
206
|
+
export const prerender = true;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This desugars to `cache.component(...)` around the page's default export. For dynamic routes (`[slug]`), pair it with `generateStaticSegmentParams` so the build knows which params to prerender:
|
|
210
|
+
|
|
211
|
+
```ts title="app/docs/[slug]/page.tsx"
|
|
212
|
+
export const prerender = { ttl: 3600, tags: ['docs'] };
|
|
213
|
+
|
|
214
|
+
export async function generateStaticSegmentParams() {
|
|
215
|
+
return allDocs.map((d) => ({ slug: d.slug }));
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Layout `prerender` cascades to descendant pages unless a closer layout or page overrides it.
|
|
220
|
+
|
|
221
|
+
### Slots: Dynamic Children Through a Cached Shell
|
|
222
|
+
|
|
223
|
+
When the expensive part is a wrapper (nav, sidebar, layout chrome) but its children are dynamic, use `slots` to cache the shell with holes for live content:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
const Shell = cache.component(ExpensiveWrapper, {
|
|
227
|
+
ttl: 3600,
|
|
228
|
+
tags: ['marketing'],
|
|
229
|
+
slots: ['children'],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// The shell serves from cache; children render live per request.
|
|
233
|
+
<Shell theme="dark"><LiveUserFeed /></Shell>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Slot props are excluded from the cache key. Slots are runtime tier only — `prerender` is ignored when `slots` is active.
|
|
237
|
+
|
|
120
238
|
## Cache Handlers
|
|
121
239
|
|
|
122
|
-
The default handler is in-memory. Configure a different backend in `timber.
|
|
240
|
+
The default handler is in-memory. Configure a different backend in `timber.cache.ts`:
|
|
123
241
|
|
|
124
|
-
```ts title="timber.
|
|
242
|
+
```ts title="timber.cache.ts"
|
|
125
243
|
import { MemoryCacheHandler } from '@timber-js/app/cache';
|
|
126
244
|
|
|
127
|
-
export default
|
|
128
|
-
cacheHandler: new MemoryCacheHandler(),
|
|
129
|
-
};
|
|
245
|
+
export default new MemoryCacheHandler();
|
|
130
246
|
```
|
|
131
247
|
|
|
132
248
|
Implement the `CacheHandler` interface for Redis, Cloudflare KV, or any other backend. See the [Cache API Reference](/docs/api-cache).
|
|
@@ -108,7 +108,13 @@ export function NavItem({ href, children }: { href: string; children: React.Reac
|
|
|
108
108
|
|
|
109
109
|
## Segment Cache
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Opt-in via `clientSegmentCache: true` in `timber.config.ts`. When enabled, the client maintains a mirror of the server's segment tree. On navigation, only changed segments are re-fetched:
|
|
112
|
+
|
|
113
|
+
```ts title="timber.config.ts"
|
|
114
|
+
export default {
|
|
115
|
+
clientSegmentCache: true,
|
|
116
|
+
};
|
|
117
|
+
```
|
|
112
118
|
|
|
113
119
|
```
|
|
114
120
|
/dashboard/settings → /dashboard/team
|
|
@@ -121,6 +127,8 @@ The client maintains a mirror of the server's segment tree. On navigation, only
|
|
|
121
127
|
|
|
122
128
|
Sync layouts stay cached while mounted. Async layouts always re-render. Pages always re-render. Client component state in shared layouts is preserved — counters, form inputs, scroll positions survive navigation.
|
|
123
129
|
|
|
130
|
+
When disabled (the default), every client navigation gets a full RSC payload from the server. This is simpler and avoids edge cases with stale cached layouts, at the cost of slightly larger payloads on navigation.
|
|
131
|
+
|
|
124
132
|
Back/forward navigation replays cached RSC payloads instantly — no server roundtrip.
|
|
125
133
|
|
|
126
134
|
## Scroll Restoration
|
|
@@ -81,16 +81,15 @@ export default {
|
|
|
81
81
|
|
|
82
82
|
## Cache Handler
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
Cache handler configuration lives in a separate `timber.cache.ts` file (not `timber.config.ts`). This keeps runtime cache instances separate from build-time config, preventing build dependencies from leaking into the server bundle.
|
|
85
|
+
|
|
86
|
+
```ts title="timber.cache.ts"
|
|
85
87
|
import { MemoryCacheHandler } from '@timber-js/app/cache';
|
|
86
88
|
|
|
87
|
-
export default
|
|
88
|
-
output: 'server',
|
|
89
|
-
cacheHandler: new MemoryCacheHandler(),
|
|
90
|
-
};
|
|
89
|
+
export default new MemoryCacheHandler();
|
|
91
90
|
```
|
|
92
91
|
|
|
93
|
-
The default is in-memory. See [Caching](/docs/caching) for Redis, KV, and custom handlers.
|
|
92
|
+
The default export is the `CacheHandler` instance. Export `cdnPurge` as a named export for CDN purge handlers. The default handler is in-memory. See [Caching](/docs/caching) for Redis, KV, and custom handlers.
|
|
94
93
|
|
|
95
94
|
## `clientJavascript`
|
|
96
95
|
|
|
@@ -129,8 +128,6 @@ Server actions still work — HTML forms submit natively via POST without JavaSc
|
|
|
129
128
|
| `buildDir` | `string` | `'.timber/dist'` | Build output directory |
|
|
130
129
|
| `clientJavascript` | `boolean \| object` | `true` | Control client-side JS |
|
|
131
130
|
| `adapter` | `TimberPlatformAdapter` | — | Deployment adapter |
|
|
132
|
-
| `cacheHandler` | `CacheHandler` | `MemoryCacheHandler` | Cache backend |
|
|
133
|
-
| `cdnPurge` | `CdnPurgeHandler` | — | CDN purge on revalidation |
|
|
134
131
|
| `serverTiming` | `'detailed' \| 'total' \| false` | `'detailed'` / `'total'` | Server-Timing header |
|
|
135
132
|
| `allowedOrigins` | `string[]` | — | CORS / CSRF allowed origins |
|
|
136
133
|
| `csrf` | `boolean` | `true` | CSRF protection |
|
|
@@ -148,6 +145,7 @@ Server actions still work — HTML forms submit natively via POST without JavaSc
|
|
|
148
145
|
| `actionEncryption` | `object` | — | Server action bound args encryption |
|
|
149
146
|
| `reactCompiler` | `boolean \| object` | `false` | React Compiler auto-memoization |
|
|
150
147
|
| `sitemap` | `object` | — | Auto-generated sitemap.xml |
|
|
148
|
+
| `clientSegmentCache`| `boolean` | `false` | Opt-in client segment cache for partial nav |
|
|
151
149
|
| `topLoader` | `object` | enabled | Navigation progress bar |
|
|
152
150
|
|
|
153
151
|
For the full type definition, see the [Config API Reference](/docs/api-config).
|