Appearance
Layouts ​
All layouts live in app/layouts/. Pages declare their layout with definePageMeta({ layout: 'layout-name' }).
Layout Reference ​
| File | Layout name | Used by |
|---|---|---|
default.vue | default | Passthrough — renders <NuxtPage /> directly with no shell |
login-layout.vue | login-layout | Auth pages (login, register, forgot-password, reset-password, social-login, external-verify) |
collage-layout.vue | collage-layout | Main authenticated DAM pages |
general-settings-layout.vue | general-settings-layout | Workspace settings section |
profile-layout.vue | profile-layout | Account/profile settings section |
error-auth.vue | error-auth | Error pages shown to authenticated users |
error-guest.vue | error-guest | Error pages shown to unauthenticated (guest) users |
error-settings.vue | error-settings | Error pages shown within the settings context |
default Layout ​
A bare passthrough — the template is just <NuxtPage />. Used as a fallback when no other layout is declared. Pages that need a shell declare their layout explicitly; those that genuinely need no frame (e.g. certain embedded or redirect pages) can rely on default.
login-layout Layout ​
The unauthenticated shell for all auth flows. Behaviour varies by context:
- Standard mode — renders a centered card with the workspace logo (or the Collage logo if unbranded), decorative illustrated elements on login and password-reset pages, and a "Powered by Collage" footer on the
external-verifyroute. - Support platform mode — when
IS_SUPPORT_PLATFORM=true, a two-column layout is rendered: the login card on the left, an<SupportIcon>illustration on the right.
Branding is resolved by useBrand() / the branding.server.ts + branding.client.ts plugins. The layout waits up to 4 seconds for branding to resolve before rendering the form, preventing a flash of the default Collage logo on white-label hosts. The decorative left/right illustrations are gated the same way (loginElement && !contentLoading && !brand.is_branded): before 72cc6d0f they rendered from SSR's Collage defaults and flashed for a moment on white-label hosts after a refresh.
The layout exposes a redirecting state via provide('redirecting', redirecting) so child auth components can suppress content during a post-login redirect.
collage-layout Layout ​
The primary shell for all authenticated DAM pages. Contains:
Sidebar— left navigation drawer (workspace logo, nav links, workspace switcher, mini/expanded state viauseSidebarMiniState)Header— top bar with alert strip and global search, shown conditionally (showCollageHeader,hideLayoutHeader,isEmbeddedHeaderPage)NuxtPage— page contentUploadBackdrop— drag-to-upload overlay, shown while files are dragged into the windowMiniUploadDialog— global upload progress widget driven bydamStore.uploadsFolderDialog— global create/move-copy folder modal (toggled viauseCollageLayout)ProcessingRequestOverlay— full-screen overlay for long-running API calls (>5 s)
On every route change, the layout:
- Runs a workspace access check via
$api('check-workspace-access')and redirects if the user has lost access. - Calls
maybeFetchWorkspaceBranding()to lazy-load white-label branding if it was not resolved server-side. - Resets the
hideLayoutHeaderstate flag.
Dynamic Vuetify theming from workspace branding is applied by useBrandingTheme().
general-settings-layout Layout ​
Workspace settings shell. Contains:
Sidebar— same sidebar ascollage-layout, collapsed to mini width on settings pages (theminiclass is applied to.collage-main)NuxtPage— settings page contentFolderDialog— shared global folder modalProcessingRequestOverlay— shared long-running API overlay
Workspace access is verified on mount and on each route change (same logic as collage-layout). Uses useCollageLayout() for shared state (folder dialog, move-copy flag, workspace access check).
profile-layout Layout ​
Account/profile settings shell. Contains:
Sidebar— same sidebar component; sidebar mini state is tracked viauseSidebarMiniStateNuxtPage— profile page content (only rendered whenisAuthenticatedis true)FolderDialog— shared global folder modal
Listens to the mitt bus event open-add-folder-dailog to open the folder dialog from any child page. Recovers missing workspace branding on mount if damStore.brandingDetails was not populated by SSR.
error-auth Layout ​
Minimal shell for error pages presented to authenticated users. Renders the Sidebar alongside a .collage-main content area. Used for errors that occur within a workspace context where the user is logged in.
error-guest Layout ​
Bare shell for error pages presented to unauthenticated (guest) users. Template is a single .main.full-404 wrapper around <slot /> — no sidebar, no nav. Used for 404s and other errors hit before login.
error-settings Layout ​
Shell for errors within the settings section. Renders the Sidebar with an .account-settings content card. Wraps the slot in a <v-card> with top padding.
How app/error.vue picks and renders an error layout ​
error.vue wraps the Error component in one of the three error layouts, checked in this order:
| Condition | Layout |
|---|---|
error path contains /shared-assets/ | error-guest (no Collage chrome on a partner share link) |
authenticated, workspace set, route path contains workspace-settings | error-settings |
| authenticated, workspace set | error-auth |
| anything else | error-guest |
Hydration guard. Route guards skip on the server when the auth cookie is present and run on the client. If a guard calls showError during hydration, error.vue would hydrate onto the page's SSR markup; Vue reuses the mismatched nodes and the error renders inside the zero-width loading indicator, leaving a white page. To avoid that, error.vue computes once:
typescript
const hydratingOverPageMarkup = import.meta.client
&& nuxtApp.isHydrating
&& !document.querySelector('#__nuxt .error-text')and renders <NuxtLayout v-if="canRender">, where canRender is !hydratingOverPageMarkup || mounted. When the server already rendered the error page (.error-text exists), it hydrates normally; otherwise the first render is skipped and the error page is rendered fresh after onMounted.
Redirect countdown for signed-out users. error.vue calls useCollageErrorLayout({ error, isSharePage }) and passes the returned redirectSecondsLeft to <Error :redirect-seconds-left>. On mount, when the visitor is not authenticated and the page is not a share page, the composable decides whether to send them to /:
| Error | Redirect? | Why |
|---|---|---|
404 on path /, or a 404 whose message contains domain, url not found, not found or access denied | no | / would raise the same domain/branding error again and loop |
| 402 or 403 | no | same domain/branding reason |
401 with SESSION_EXPIRED_MESSAGE on path / | no | already on the login page; a redirect would reload it in a loop |
| anything else, including 401 session expired on any other path | yes, after 5 s |
The redirect is a countdown, not an immediate window.location.replace('/'): REDIRECT_DELAY_SECONDS = 5, decremented every second, and Error.vue shows Redirecting to login in Ns… while the Back to Home button stays available. At 0 it calls window.location.replace('/'); the interval is cleared on unmount. The sidebar's logout (useCollageSidebar().logoutApp) does not use this countdown: since 38752451 it calls window.location.replace('/') immediately in its finally block (it used to wait 5 seconds).
Declaring a Layout ​
vue
<script setup lang="ts">
definePageMeta({
layout: 'collage-layout',
middleware: ['auth'],
})
</script>