Skip to content

Layouts ​

All layouts live in app/layouts/. Pages declare their layout with definePageMeta({ layout: 'layout-name' }).

Layout Reference ​

FileLayout nameUsed by
default.vuedefaultPassthrough — renders <NuxtPage /> directly with no shell
login-layout.vuelogin-layoutAuth pages (login, register, forgot-password, reset-password, social-login, external-verify)
collage-layout.vuecollage-layoutMain authenticated DAM pages
general-settings-layout.vuegeneral-settings-layoutWorkspace settings section
profile-layout.vueprofile-layoutAccount/profile settings section
error-auth.vueerror-authError pages shown to authenticated users
error-guest.vueerror-guestError pages shown to unauthenticated (guest) users
error-settings.vueerror-settingsError 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-verify route.
  • 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 via useSidebarMiniState)
  • Header — top bar with alert strip and global search, shown conditionally (showCollageHeader, hideLayoutHeader, isEmbeddedHeaderPage)
  • NuxtPage — page content
  • UploadBackdrop — drag-to-upload overlay, shown while files are dragged into the window
  • MiniUploadDialog — global upload progress widget driven by damStore.uploads
  • FolderDialog — global create/move-copy folder modal (toggled via useCollageLayout)
  • ProcessingRequestOverlay — full-screen overlay for long-running API calls (>5 s)

On every route change, the layout:

  1. Runs a workspace access check via $api('check-workspace-access') and redirects if the user has lost access.
  2. Calls maybeFetchWorkspaceBranding() to lazy-load white-label branding if it was not resolved server-side.
  3. Resets the hideLayoutHeader state flag.

Dynamic Vuetify theming from workspace branding is applied by useBrandingTheme().

general-settings-layout Layout ​

Workspace settings shell. Contains:

  • Sidebar — same sidebar as collage-layout, collapsed to mini width on settings pages (the mini class is applied to .collage-main)
  • NuxtPage — settings page content
  • FolderDialog — shared global folder modal
  • ProcessingRequestOverlay — 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 via useSidebarMiniState
  • NuxtPage — profile page content (only rendered when isAuthenticated is 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:

ConditionLayout
error path contains /shared-assets/error-guest (no Collage chrome on a partner share link)
authenticated, workspace set, route path contains workspace-settingserror-settings
authenticated, workspace seterror-auth
anything elseerror-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 /:

ErrorRedirect?Why
404 on path /, or a 404 whose message contains domain, url not found, not found or access deniedno/ would raise the same domain/branding error again and loop
402 or 403nosame domain/branding reason
401 with SESSION_EXPIRED_MESSAGE on path /noalready on the login page; a redirect would reload it in a loop
anything else, including 401 session expired on any other pathyes, 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>