@uniweb/core 0.7.10 → 0.7.11

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/page.js +63 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.7.10",
3
+ "version": "0.7.11",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
package/src/page.js CHANGED
@@ -426,11 +426,34 @@ export default class Page {
426
426
  this._loadingContent = (async () => {
427
427
  try {
428
428
  const base = this.website.basePath || ''
429
- // Locale-aware URL: non-default locale pages live under /{locale}/_pages/
430
- const localePrefix = this.website.activeLocale !== this.website.defaultLocale
431
- ? `/${this.website.activeLocale}` : ''
432
- const routePath = this.route === '/' ? '/index' : this.route
433
- const res = await fetch(`${base}${localePrefix}/_pages${routePath}.json`)
429
+ const lang = this.website.activeLocale
430
+ const defaultLang = this.website.defaultLocale
431
+
432
+ // Phase 5 of the CDN migration: prefer the content-addressed URL from
433
+ // __DATA__.config.pageHashes (the publish handler emits it). Falls
434
+ // back to the legacy /{lang}/_pages/{route}.json shape when the map
435
+ // isn't present (older sites that haven't republished yet).
436
+ const hashes = this.website.config?.pageHashes
437
+ const hashedUrl = hashes?.[lang]?.[this.route]
438
+
439
+ let url
440
+ if (hashedUrl) {
441
+ url = `${base}${hashedUrl}`
442
+ } else {
443
+ const localePrefix = lang !== defaultLang ? `/${lang}` : ''
444
+ const routePath = this.route === '/' ? '/index' : this.route
445
+ url = `${base}${localePrefix}/_pages${routePath}.json`
446
+ }
447
+
448
+ const res = await fetch(url)
449
+ if (res.status === 404 && _shouldReloadOnHashed404(hashedUrl)) {
450
+ // Stale tab: this tab's __DATA__ map references a publish that
451
+ // no longer exists in R2. One reload usually picks up the fresh
452
+ // HTML (and a fresh map). Capped at 2 attempts to defend against
453
+ // a malformed publish where new HTML and new R2 also disagree.
454
+ window.location.reload()
455
+ return
456
+ }
434
457
  if (!res.ok) {
435
458
  console.warn(`[Page] Failed to load content for ${this.route}: ${res.status}`)
436
459
  this._bodySections = [] // Mark as loaded (empty) to prevent retries
@@ -439,6 +462,11 @@ export default class Page {
439
462
  const data = await res.json()
440
463
  this._bodySections = data.sections || []
441
464
  this._bodyBlocks = null // Reset lazy cache so getter rebuilds
465
+ // Reset the reload counter on a successful fetch so future stale
466
+ // tabs (across many publishes in one session) get fresh attempts.
467
+ if (typeof sessionStorage !== 'undefined') {
468
+ sessionStorage.removeItem('uniwebReloadCount')
469
+ }
442
470
  } finally {
443
471
  this._loadingContent = null
444
472
  }
@@ -609,3 +637,33 @@ export default class Page {
609
637
  return this.website.getVersionUrl(targetVersion, this.route)
610
638
  }
611
639
  }
640
+
641
+ /**
642
+ * Helper for the stale-tab 404→reload guard in Page.loadContent.
643
+ *
644
+ * Phase 5 of the CDN migration writes content-addressed `_pages/...-{hash}.json`
645
+ * files. Each publish wipes the prior hashes from R2 and writes fresh ones.
646
+ * A browser tab loaded against a previous publish carries a stale __DATA__
647
+ * map; on SPA navigation it'll request a hash that no longer exists.
648
+ * Reloading the tab pulls fresh HTML (and a fresh map). The session-scoped
649
+ * counter caps reloads at 2 so a malformed publish (where new HTML and new
650
+ * R2 disagree) doesn't trigger an infinite loop — after 2 failed reloads
651
+ * we render the synthetic 404 path instead.
652
+ *
653
+ * Only fires when the request was a hashed URL: legacy non-hashed 404s
654
+ * keep the prior "mark empty, log warning" behavior (they're not symptomatic
655
+ * of a stale-tab race; just a missing route).
656
+ */
657
+ function _shouldReloadOnHashed404(hashedUrl) {
658
+ if (!hashedUrl) return false
659
+ if (typeof sessionStorage === 'undefined') return false
660
+ if (typeof window === 'undefined' || !window.location) return false
661
+ try {
662
+ const count = parseInt(sessionStorage.getItem('uniwebReloadCount') || '0', 10) || 0
663
+ if (count >= 2) return false
664
+ sessionStorage.setItem('uniwebReloadCount', String(count + 1))
665
+ return true
666
+ } catch {
667
+ return false
668
+ }
669
+ }