Auth Route Architecture#
Dify's authentication pages (/signin, /signup, /forgot-password, /activate) live outside the (commonLayout) route group. They have no access to the main nav, session-aware context providers, or the hydration-boundary that drives profile prefetching for logged-in users. Each auth route is entirely self-contained and responsible for its own data fetching and layout rendering.
Route Structure#
web/app/
βββ (commonLayout)/ β main app (requires session)
β βββ layout.tsx β nav, providers, hydration boundary
β βββ ...
βββ signin/
β βββ layout.tsx β auth shell (client component)
β βββ page.tsx
βββ signup/
β βββ layout.tsx β auth shell (client component)
β βββ page.tsx
βββ forgot-password/
β βββ page.tsx β layout inlined in page
βββ activate/
βββ page.tsx β layout inlined in page
/signin and /signup use dedicated layout.tsx files . /forgot-password and /activate inline their shell directly in page.tsx β there is no separate layout.tsx for those routes.
Separation from (commonLayout)#
The (commonLayout) layout wraps every authenticated page with ConsoleRuntimeProviders, ConsoleContextProviders, MainNavLayout, and CommonLayoutGlobalMounts. None of these are present in the auth routes. This separation is intentional: auth pages run before any session is established and must not depend on user-profile or workspace context.
The hydration-boundary.tsx in (commonLayout) handles 401 β /auth/refresh redirects for the main app . Auth routes are excluded from this boundary by their position outside the route group.
Data Fetching Pattern#
Every auth layout and page fetches system features via useSuspenseQuery(systemFeaturesQueryOptions()) . This is the only server data fetched at auth boot β it drives two things:
- Branding: whether to show a custom
login_page_logoor the default Dify logo, and whether to render the copyright footer. TheHeadercomponent and all four auth pages/layouts gate these onsystemFeatures.branding. - Stale-time:
systemFeaturesQueryOptionssetsstaleTime: Infinity, so the query is fetched once per session and never re-fetched.
All auth layouts are marked 'use client' β there is no server-side data prefetch; the client suspends on first render until the system-features query resolves .
Shared Header Component#
All four auth routes reuse the same _header component from web/app/signin/_header.tsx . It renders:
- A branding-aware logo (custom image or
DifyLogo) - A locale switcher (
LocaleMenu) - A dynamically-imported
ThemeSelector(SSR-disabled)
The /signup layout imports the header from the /signin directory directly .
Visual Shell#
All four routes share the same full-screen shell structure:
- Outer div:
min-h-screen, centered,bg-background-default-burn - Inner card: rounded-2xl border,
bg-background-default-subtle - Content area: vertically/horizontally centered, max-width constrained on
md:breakpoint - Optional copyright footer: shown only when
systemFeatures.branding.enabled === false
Auth Boot Refactor Context#
This architecture reflects a refactor in PR #36818 that removed a global "app initializer" (app-initializer.tsx) and moved boot logic to route boundaries. Before this change, a provider-level useSuspenseQuery in app-context-provider.tsx served as a global boot point. After the refactor, the (commonLayout) hydration boundary handles boot for authenticated routes, while auth routes independently fetch only what they need (systemFeaturesQueryOptions).
Key Files#
| File | Role |
|---|---|
web/app/signin/layout.tsx | Sign-in shell layout |
web/app/signup/layout.tsx | Sign-up shell layout |
web/app/forgot-password/page.tsx | Forgot-password (layout inlined) |
web/app/activate/page.tsx | Activate (layout inlined) |
web/app/signin/_header.tsx | Shared auth header (logo, locale, theme) |
web/features/system-features/client.ts | systemFeaturesQueryOptions β only data fetched at auth boot |
web/app/(commonLayout)/layout.tsx | Main app layout (auth routes excluded) |
web/app/(commonLayout)/hydration-boundary.tsx | 401 redirect logic for authenticated routes only |