Skip to content

Commit d368986

Browse files
author
Daniel Sullivan
committed
fix: resolve PWA static file serving and button visibility issues
Frontend changes: - Update PWA button to show on production HTTPS domains - Add timeout fallback for production domains when beforeinstallprompt doesn't fire - Add modern mobile-web-app-capable meta tag (fixes deprecation warning) - Improve console logging for debugging PWA conditions Backend changes: - Add proper static file serving for PWA files (favicon.svg, manifest.json, og-image.svg) - Add NoRoute handler to serve static files from dist directory - Ensure all PWA assets are accessible at production URLs This fixes the 404 errors for PWA files and makes the install button appear on production domains like babelbridge.world
1 parent c3ca3c4 commit d368986

4 files changed

Lines changed: 53 additions & 12 deletions

File tree

api/server.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,41 @@ func NewServerWithTTLs(svc service.TranslationService, sessTTL, ctxTTL time.Dura
8282
// serve static assets for the frontend
8383
r.Static("/assets", "frontend/dist/assets")
8484

85-
// Root: ensure session and serve SPA
85+
// serve PWA files and other static assets from dist root
86+
r.StaticFile("/favicon.svg", "frontend/dist/favicon.svg")
87+
r.StaticFile("/manifest.json", "frontend/dist/manifest.json")
88+
r.StaticFile("/og-image.svg", "frontend/dist/og-image.svg")
89+
90+
// Root: first try to serve static files, then ensure session and serve SPA
8691
r.GET("/", func(c *gin.Context) {
92+
// Try to serve static files from dist root first
93+
path := c.Request.URL.Path
94+
if path != "/" {
95+
if f, err := http.Dir("frontend/dist").Open(path); err == nil {
96+
_ = f.Close()
97+
c.File("frontend/dist" + path)
98+
return
99+
}
100+
}
101+
102+
// Default to SPA index with session
103+
s.issueSessionHandler(c)
104+
if c.IsAborted() {
105+
return
106+
}
107+
serveSPAIndex(c)
108+
})
109+
110+
// Handle any other static files that weren't caught above
111+
r.NoRoute(func(c *gin.Context) {
112+
path := c.Request.URL.Path
113+
// Try to serve from dist directory
114+
if f, err := http.Dir("frontend/dist").Open(path); err == nil {
115+
_ = f.Close()
116+
c.File("frontend/dist" + path)
117+
return
118+
}
119+
// Fall back to SPA index for client-side routing
87120
s.issueSessionHandler(c)
88121
if c.IsAborted() {
89122
return

frontend/dist/index.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
1010
<link rel="manifest" href="/manifest.json" />
1111
<meta name="theme-color" content="#6366F1" />
12+
<meta name="mobile-web-app-capable" content="yes" />
1213
<meta name="apple-mobile-web-app-capable" content="yes" />
1314
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
1415
<meta name="apple-mobile-web-app-title" content="BabelBridge" />
@@ -18,7 +19,7 @@
1819
<meta property="og:title" content="BabelBridge - Modern Translation Tool" />
1920
<meta property="og:description" content="A modern web-based translation tool with live language identification and pluggable AI backends. Translate text seamlessly with intelligent context preservation." />
2021
<meta property="og:type" content="website" />
21-
<meta property="og:url" content="https://babel-bridge.com" />
22+
<meta property="og:url" content="https://babelbridge.world" />
2223
<meta property="og:image" content="/og-image.svg" />
2324
<meta property="og:image:alt" content="BabelBridge - Modern Translation Tool with AI-powered features" />
2425
<meta property="og:image:width" content="1200" />
@@ -37,8 +38,8 @@
3738
<meta name="keywords" content="translation, AI, language, translate, babel, bridge, multilingual, context, preservation" />
3839
<meta name="author" content="Daniel Sullivan" />
3940
<meta name="robots" content="index, follow" />
40-
<link rel="canonical" href="https://babel-bridge.com" />
41-
<script type="module" crossorigin src="/assets/index-CD4TOx2g.js"></script>
41+
<link rel="canonical" href="https://babelbridge.world" />
42+
<script type="module" crossorigin src="/assets/index-Dwa_SCcC.js"></script>
4243
</head>
4344
<body>
4445
<div id="root"></div>

frontend/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
1010
<link rel="manifest" href="/manifest.json" />
1111
<meta name="theme-color" content="#6366F1" />
12+
<meta name="mobile-web-app-capable" content="yes" />
1213
<meta name="apple-mobile-web-app-capable" content="yes" />
1314
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
1415
<meta name="apple-mobile-web-app-title" content="BabelBridge" />

frontend/src/components/AddToAppButton.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,18 @@ export function AddToAppButton() {
3737
console.log('PWA: hasBeforeInstallPrompt =', 'onbeforeinstallprompt' in window)
3838
console.log('PWA: hostname =', window.location.hostname)
3939

40-
// For localhost development: if no beforeinstallprompt after 2 seconds,
41-
// assume it's available for testing (but only on localhost)
40+
// For development localhost OR production HTTPS: if no beforeinstallprompt after 2 seconds,
41+
// assume it's available for testing/installation
4242
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
4343
const isDev = process.env.NODE_ENV === 'development'
44+
const isHTTPS = window.location.protocol === 'https:'
45+
const isProductionDomain = !isLocalhost && isHTTPS
4446

4547
let timeoutId: NodeJS.Timeout | null = null
46-
if (isDev && isLocalhost) {
48+
if ((isDev && isLocalhost) || isProductionDomain) {
4749
timeoutId = setTimeout(() => {
4850
if (!isInstallable && !isIOS()) {
49-
console.log('PWA: No beforeinstallprompt after 2s, enabling for localhost testing')
51+
console.log('PWA: No beforeinstallprompt after 2s, enabling for production/localhost')
5052
setIsInstallable(true)
5153
}
5254
}, 2000)
@@ -99,17 +101,21 @@ export function AddToAppButton() {
99101
// Show button if:
100102
// 1. Actually installable (beforeinstallprompt fired), OR
101103
// 2. iOS device (can always install via Safari), OR
102-
// 3. Development mode AND localhost (for testing)
104+
// 3. Development mode AND localhost (for testing), OR
105+
// 4. Production domain with HTTPS (assume PWA is available)
103106
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
104107
const isDev = process.env.NODE_ENV === 'development'
105-
const shouldShowButton = isInstallable || isIOS() || (isDev && isLocalhost)
108+
const isHTTPS = window.location.protocol === 'https:'
109+
const isProductionDomain = !isLocalhost && isHTTPS
110+
111+
const shouldShowButton = isInstallable || isIOS() || (isDev && isLocalhost) || isProductionDomain
106112

107113
if (!shouldShowButton) {
108-
console.log('PWA: Button hidden - not installable, not iOS, not dev+localhost')
114+
console.log('PWA: Button hidden - not installable, not iOS, not dev+localhost, not production HTTPS')
109115
return null
110116
}
111117

112-
console.log('PWA: Button will show - installable:', isInstallable, 'iOS:', isIOS(), 'dev+localhost:', isDev && isLocalhost)
118+
console.log('PWA: Button will show - installable:', isInstallable, 'iOS:', isIOS(), 'dev+localhost:', isDev && isLocalhost, 'production:', isProductionDomain)
113119

114120
return (
115121
<>

0 commit comments

Comments
 (0)