perf(templates): add defer attribute to main.js script tags across all pages

- Add defer attribute to <script src="/static/main.js"> in 21 template files
- Improves page load performance by allowing HTML parsing to continue without blocking
- Maintains script execution order while enabling parallel resource loading
- Remove duplicate defer attribute from header.templ line 264
- Add websocket.ts import to main.ts for module registration

This optimization reduces page render blocking and improves perceived load times
by allowing the browser to continue parsing HTML while the main.js bundle loads.
The defer attribute ensures scripts execute in order after HTML parsing completes.

Affected templates include:
- Admin pages: admin, admin_library, admin_settings, admin_users
- Content pages: analytics, bookshelf, collections, conflicts, custom_section
- User pages: dashboard, devices, docs, index, login, profile, progress, queue, register
- System pages: header, unlinked_books
This commit is contained in:
2026-03-12 15:44:15 -04:00
parent 48eaa2d286
commit 533f8747e3
7 changed files with 26 additions and 25 deletions
+5 -5
View File
@@ -3,7 +3,7 @@ package templates
templ APIExplorer(explorer APIExplorerData) { templ APIExplorer(explorer APIExplorerData) {
if !explorer.IsLoggedIn { if !explorer.IsLoggedIn {
// Show mode toggle and mock data // Show mode toggle and mock data
<div class="border border-border rounded-lg p-6 mt-8 bg-background-secondary"> <div x-data="devices" class="border border-border rounded-lg p-6 mt-8 bg-background-secondary">
<div class="flex gap-2 mb-4"> <div class="flex gap-2 mb-4">
<button class="px-4 py-2 border border-border rounded bg-background-primary text-text-primary cursor-pointer bg-accent text-white">Mock Data</button> <button class="px-4 py-2 border border-border rounded bg-background-primary text-text-primary cursor-pointer bg-accent text-white">Mock Data</button>
<button class="px-4 py-2 border border-border rounded bg-background-primary text-text-primary opacity-50 cursor-not-allowed" disabled>Login to Try Real</button> <button class="px-4 py-2 border border-border rounded bg-background-primary text-text-primary opacity-50 cursor-not-allowed" disabled>Login to Try Real</button>
@@ -25,16 +25,16 @@ templ APIExplorer(explorer APIExplorerData) {
<pre class="bg-background-primary p-4 rounded-lg overflow-x-auto"><code class="text-text-primary text-sm">{ explorer.Endpoint.Response }</code></pre> <pre class="bg-background-primary p-4 rounded-lg overflow-x-auto"><code class="text-text-primary text-sm">{ explorer.Endpoint.Response }</code></pre>
</div> </div>
<div class="flex gap-2 mt-4"> <div class="flex gap-2 mt-4">
<button @click="$store.devices.copyToClipboard(JSON.stringify({ explorer.Endpoint.RequestBody }, null, 2))" class="px-4 py-2 border border-border rounded bg-background-primary text-text-primary hover:bg-background-secondary cursor-pointer text-sm">Copy Request</button> <button @click="copyToClipboard(JSON.stringify({ explorer.Endpoint.RequestBody }, null, 2))" class="px-4 py-2 border border-border rounded bg-background-primary text-text-primary hover:bg-background-secondary cursor-pointer text-sm">Copy Request</button>
<button @click="$store.devices.copyToClipboard(JSON.stringify({ explorer.Endpoint.Response }, null, 2))" class="px-4 py-2 border border-border rounded bg-background-primary text-text-primary hover:bg-background-secondary cursor-pointer text-sm">Copy Response</button> <button @click="copyToClipboard(JSON.stringify({ explorer.Endpoint.Response }, null, 2))" class="px-4 py-2 border border-border rounded bg-background-primary text-text-primary hover:bg-background-secondary cursor-pointer text-sm">Copy Response</button>
</div> </div>
</div> </div>
} else { } else {
// Show interactive explorer with real execution // Show interactive explorer with real execution
<div class="border border-border rounded-lg p-6 mt-8 bg-background-secondary" x-data="apiExplorerDoc" x-init="initAPIExplorerDoc('{ explorer.Endpoint.Path }', '{ explorer.Endpoint.RequestBody }', '{ explorer.Endpoint.Response }')"> <div class="border border-border rounded-lg p-6 mt-8 bg-background-secondary" x-data="apiExplorerDoc" x-init="initAPIExplorerDoc('{ explorer.Endpoint.Path }', '{ explorer.Endpoint.RequestBody }', '{ explorer.Endpoint.Response }')">
<div class="flex gap-2 mb-4"> <div class="flex gap-2 mb-4">
<button class="mode-btn px-4 py-2 border border-border rounded bg-background-primary text-text-primary cursor-pointer hover:bg-background-secondary text-sm" id="mock-btn" @click="$store.apiExplorerDoc.showDocMode('mock')">Mock Data</button> <button class="mode-btn px-4 py-2 border border-border rounded bg-background-primary text-text-primary cursor-pointer hover:bg-background-secondary text-sm" id="mock-btn" @click="showDocMode('mock')">Mock Data</button>
<button class="mode-btn px-4 py-2 bg-accent text-white rounded cursor-pointer text-sm" id="real-btn" @click="$store.apiExplorerDoc.showDocMode('real')">Try It Out</button> <button class="mode-btn px-4 py-2 bg-accent text-white rounded cursor-pointer text-sm" id="real-btn" @click="showDocMode('real')">Try It Out</button>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<div class="mb-4"> <div class="mb-4">
+2 -2
View File
@@ -19,7 +19,7 @@ templ BookShelf(user User, libraries []LibraryData) {
<div class="flex flex-col sm:flex-row gap-4 items-center justify-between"> <div class="flex flex-col sm:flex-row gap-4 items-center justify-between">
<div class="flex-1 w-full"> <div class="flex-1 w-full">
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">Select Library</label> <label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">Select Library</label>
<select id="library-select" @change="$store.bookshelf.selectLibrary" class="w-full px-4 py-3 border rounded-lg text-lg" style="background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border);"> <select id="library-select" @change="selectLibrary" class="w-full px-4 py-3 border rounded-lg text-lg" style="background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border);">
if len(libraries) == 0 { if len(libraries) == 0 {
<option value="">No libraries available</option> <option value="">No libraries available</option>
} else { } else {
@@ -30,7 +30,7 @@ templ BookShelf(user User, libraries []LibraryData) {
</select> </select>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<button @click="$store.bookshelf.loadBookshelf" class="btn-primary px-6 py-3 rounded-lg font-medium"> <button @click="loadBookshelf" class="btn-primary px-6 py-3 rounded-lg font-medium">
Refresh Refresh
</button> </button>
</div> </div>
+2 -2
View File
@@ -112,7 +112,7 @@ templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []P
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
/> />
<button <button
@click="$store.devices.copyToClipboard(document.getElementById('sync-url-{ device.ID }').value, 'Kobo sync URL')" @click="copyToClipboard(document.getElementById('sync-url-{ device.ID }').value, 'Kobo sync URL')"
class="px-3 py-2 text-xs rounded hover:opacity-80" class="px-3 py-2 text-xs rounded hover:opacity-80"
style="background-color: var(--accent); color: white;" style="background-color: var(--accent); color: white;"
> >
@@ -136,7 +136,7 @@ templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []P
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border); font-family: monospace;" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border); font-family: monospace;"
/> />
<button <button
@click="$store.devices.copyToClipboard(document.getElementById('auth-token-{ device.ID }').value, 'Auth token')" @click="copyToClipboard(document.getElementById('auth-token-{ device.ID }').value, 'Auth token')"
class="px-3 py-2 text-xs rounded hover:opacity-80" class="px-3 py-2 text-xs rounded hover:opacity-80"
style="background-color: var(--accent); color: white;" style="background-color: var(--accent); color: white;"
> >
+14 -14
View File
@@ -1,7 +1,7 @@
package templates package templates
templ Header(user User, currentPath string) { templ Header(user User, currentPath string) {
<nav class="border-b header-nav" style="border-color: var(--border); background-color: var(--bg-secondary);"> <nav x-data="theme" class="border-b header-nav" style="border-color: var(--border); background-color: var(--bg-secondary);">
<div x-data="{}" x-init="console.log('Alpine is working!', $el)"></div> <div x-data="{}" x-init="console.log('Alpine is working!', $el)"></div>
<div class="w-full px-4 sm:px-6 lg:px-8"> <div class="w-full px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16"> <div class="flex justify-between items-center h-16">
@@ -70,7 +70,7 @@ templ Header(user User, currentPath string) {
<h3 class="text-sm font-semibold mb-3" style="color: var(--text-primary)">Select Theme</h3> <h3 class="text-sm font-semibold mb-3" style="color: var(--text-primary)">Select Theme</h3>
<div class="space-y-2"> <div class="space-y-2">
<button <button
@click="$store.theme.changeTheme('tokyo-night'); themeDropdownOpen = false" @click="changeTheme('tokyo-night'); themeDropdownOpen = false"
class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary); background-color: var(--bg-primary);" style="color: var(--text-primary); background-color: var(--bg-primary);"
> >
@@ -78,7 +78,7 @@ templ Header(user User, currentPath string) {
Tokyo Night Tokyo Night
</button> </button>
<button <button
@click="$store.theme.changeTheme('dracula'); themeDropdownOpen = false" @click="changeTheme('dracula'); themeDropdownOpen = false"
class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary); background-color: var(--bg-primary);" style="color: var(--text-primary); background-color: var(--bg-primary);"
> >
@@ -86,7 +86,7 @@ templ Header(user User, currentPath string) {
Dracula Dracula
</button> </button>
<button <button
@click="$store.theme.changeTheme('nord'); themeDropdownOpen = false" @click="changeTheme('nord'); themeDropdownOpen = false"
class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary); background-color: var(--bg-primary);" style="color: var(--text-primary); background-color: var(--bg-primary);"
> >
@@ -94,7 +94,7 @@ templ Header(user User, currentPath string) {
Nord Nord
</button> </button>
<button <button
@click="$store.theme.changeTheme('solarized-dark'); themeDropdownOpen = false" @click="changeTheme('solarized-dark'); themeDropdownOpen = false"
class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary); background-color: var(--bg-primary);" style="color: var(--text-primary); background-color: var(--bg-primary);"
> >
@@ -102,7 +102,7 @@ templ Header(user User, currentPath string) {
Solarized Dark Solarized Dark
</button> </button>
<button <button
@click="$store.theme.changeTheme('monokai'); themeDropdownOpen = false" @click="changeTheme('monokai'); themeDropdownOpen = false"
class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary); background-color: var(--bg-primary);" style="color: var(--text-primary); background-color: var(--bg-primary);"
> >
@@ -110,7 +110,7 @@ templ Header(user User, currentPath string) {
Monokai Monokai
</button> </button>
<button <button
@click="$store.theme.changeTheme('one-dark-pro'); themeDropdownOpen = false" @click="changeTheme('one-dark-pro'); themeDropdownOpen = false"
class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary); background-color: var(--bg-primary);" style="color: var(--text-primary); background-color: var(--bg-primary);"
> >
@@ -118,7 +118,7 @@ templ Header(user User, currentPath string) {
One Dark Pro One Dark Pro
</button> </button>
<button <button
@click="$store.theme.changeTheme('material-dark'); themeDropdownOpen = false" @click="changeTheme('material-dark'); themeDropdownOpen = false"
class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary); background-color: var(--bg-primary);" style="color: var(--text-primary); background-color: var(--bg-primary);"
> >
@@ -128,7 +128,7 @@ templ Header(user User, currentPath string) {
<div class="border-t pt-2 mt-2" style="border-color: var(--border);"> <div class="border-t pt-2 mt-2" style="border-color: var(--border);">
<p class="text-xs mb-2" style="color: var(--text-secondary)">Bookshelf Background</p> <p class="text-xs mb-2" style="color: var(--text-secondary)">Bookshelf Background</p>
<button <button
@click="$store.theme.changeWoodPaneling('none'); themeDropdownOpen = false" @click="changeWoodPaneling('none'); themeDropdownOpen = false"
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary);" style="color: var(--text-primary);"
data-wood="none" data-wood="none"
@@ -136,7 +136,7 @@ templ Header(user User, currentPath string) {
None None
</button> </button>
<button <button
@click="$store.theme.changeWoodPaneling('wood-light'); themeDropdownOpen = false" @click="changeWoodPaneling('wood-light'); themeDropdownOpen = false"
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary);" style="color: var(--text-primary);"
data-wood="wood-light" data-wood="wood-light"
@@ -148,7 +148,7 @@ templ Header(user User, currentPath string) {
Wood Light Wood Light
</button> </button>
<button <button
@click="$store.theme.changeWoodPaneling('wood-dark'); themeDropdownOpen = false" @click="changeWoodPaneling('wood-dark'); themeDropdownOpen = false"
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary);" style="color: var(--text-primary);"
data-wood="wood-dark" data-wood="wood-dark"
@@ -160,7 +160,7 @@ templ Header(user User, currentPath string) {
Wood Dark Wood Dark
</button> </button>
<button <button
@click="$store.theme.changeWoodPaneling('wood-mahogany'); themeDropdownOpen = false" @click="changeWoodPaneling('wood-mahogany'); themeDropdownOpen = false"
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity" class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
style="color: var(--text-primary);" style="color: var(--text-primary);"
data-wood="wood-mahogany" data-wood="wood-mahogany"
@@ -208,7 +208,7 @@ templ Header(user User, currentPath string) {
} }
<div class="border-t my-1" style="border-color: var(--border);"></div> <div class="border-t my-1" style="border-color: var(--border);"></div>
<button <button
@click="$store.header.logout(); userMenuOpen = false" @click="logout(); userMenuOpen = false"
class="block w-full text-left px-4 py-2 text-sm hover:opacity-80" class="block w-full text-left px-4 py-2 text-sm hover:opacity-80"
style="color: var(--text-primary); background-color: var(--bg-secondary);" style="color: var(--text-primary); background-color: var(--bg-secondary);"
> >
@@ -261,5 +261,5 @@ templ Header(user User, currentPath string) {
</div> </div>
</div> </div>
</nav> </nav>
<script src="/static/main.js" defer defer></script> <script src="/static/main.js" defer></script>
} }
+1 -1
View File
@@ -18,7 +18,7 @@ templ Index(loggedIn bool) {
<div class="relative max-w-7xl mx-auto px-4 py-16 sm:px-6 sm:py-24 lg:py-32 lg:px-8"> <div class="relative max-w-7xl mx-auto px-4 py-16 sm:px-6 sm:py-24 lg:py-32 lg:px-8">
<div class="flex justify-between items-start mb-8"> <div class="flex justify-between items-start mb-8">
<div></div> <div></div>
<select id="theme-select" @change="$store.theme.changeTheme" class="px-3 py-2 border rounded text-sm" style="background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)"> <select id="theme-select" @change="changeTheme" class="px-3 py-2 border rounded text-sm" style="background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)">
<option value="tokyo-night">Tokyo Night</option> <option value="tokyo-night">Tokyo Night</option>
<option value="dracula">Dracula</option> <option value="dracula">Dracula</option>
<option value="nord">Nord</option> <option value="nord">Nord</option>
+1 -1
View File
@@ -15,7 +15,7 @@ templ Login(sessionExpired bool, deleted bool) {
<div class="container mx-auto px-4 py-8 max-w-md"> <div class="container mx-auto px-4 py-8 max-w-md">
<div class="flex justify-between items-center mb-8"> <div class="flex justify-between items-center mb-8">
<h1 class="text-3xl font-bold">Login to Bookhoard</h1> <h1 class="text-3xl font-bold">Login to Bookhoard</h1>
<select id="theme-select" @change="$store.login.changeTheme()" class="px-3 py-2 border rounded" style="background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)"> <select id="theme-select" @change="changeTheme()" class="px-3 py-2 border rounded" style="background-color: var(--bg-secondary); color: var(--text-primary); border-color: var(--border)">
<option value="tokyo-night">Tokyo Night</option> <option value="tokyo-night">Tokyo Night</option>
<option value="dracula">Dracula</option> <option value="dracula">Dracula</option>
<option value="nord">Nord</option> <option value="nord">Nord</option>
+1
View File
@@ -32,6 +32,7 @@ import "./theme";
import "./toast"; import "./toast";
import "./toast-error"; import "./toast-error";
import "./unlinked_books"; import "./unlinked_books";
import "./websocket";
import "./woodPanelingInit"; import "./woodPanelingInit";
Alpine.start(); Alpine.start();