Skip to content
ExponaBLOG
SVELTE & SVELTEKIT · DAY 090 / 100

Layout groups and resets: a hands-on guide for engineers

Different layouts for different sections.

Expona Admin7 min read

The Problem with Global Layouts

As your SvelteKit application grows, you'll inevitably reach a point where different sections need radically different layouts. Your marketing homepage shouldn't share the same sidebar navigation as your authenticated dashboard. Your admin panel needs a completely different header than your public blog. Without proper layout isolation, you end up with deeply nested conditionals and fragile template logic that becomes a maintenance nightmare.

Layout groups and resets give you a powerful way to organize your routing structure so that different sections of your application can have entirely separate layout hierarchies. This guide walks you through layout groups and resets: a hands-on guide for engineers who need to structure medium to large SvelteKit applications with confidence.

Understanding Layout Groups with (app) and (marketing)

Layout groups use parentheses in your directory structure to create logical groupings without affecting the URL. A route under (app) and a route under (marketing) can both live at the root level of your URL structure, but each uses its own layout chain.

Here's a practical scenario: you're building a SaaS platform with a public marketing site and an authenticated application dashboard. Your directory structure might look like this:

typescript
src/routes/
├── (marketing)/
│   ├── +layout.svelte
│   ├── +layout.server.ts
│   ├── +page.svelte          // /
│   ├── pricing/
│   │   └── +page.svelte      // /pricing
│   └── blog/
│       └── [slug]/
│           └── +page.svelte  // /blog/[slug]
├── (app)/
│   ├── +layout.svelte
│   ├── +layout.server.ts
│   ├── dashboard/
│   │   └── +page.svelte      // /dashboard
│   ├── settings/
│   │   └── +page.svelte      // /settings
│   └── projects/
│       └── [id]/
│           └── +page.svelte  // /projects/[id]
└── +layout.svelte            // Root layout (shared by both groups)

The marketing layout handles branding, footer newsletter signup, and unauthenticated user experience:

typescript
// src/routes/(marketing)/+layout.svelte
<script>
  import Header from '$lib/components/marketing/Header.svelte';
  import Footer from '$lib/components/marketing/Footer.svelte';
</script>

<div class="marketing-wrapper">
  <Header />
  <main class="prose prose-lg">
    <slot />
  </main>
  <Footer />
</div>

<style>
  .marketing-wrapper {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
  }
  main {
    flex: 1;
  }
</style>

The app layout uses a completely different structure with sidebar navigation and protected content:

typescript
// src/routes/(app)/+layout.svelte
<script>
  import Sidebar from '$lib/components/app/Sidebar.svelte';
  import TopNav from '$lib/components/app/TopNav.svelte';
  import { page } from '$app/stores';
</script>

<div class="app-wrapper">
  <Sidebar >
    <nav>
      <a href="/dashboard" class:active={$page.url.pathname === '/dashboard'}>
        Dashboard
      </a>
      <a href="/projects" class:active={$page.url.pathname === '/projects'}>
        Projects
      </a>
    </nav>
  </Sidebar>
  <div class="main-content">
    <TopNav />
    <main>
      <slot />
    </main>
  </div>
</div>

<style>
  .app-wrapper {
    display: grid;
    grid-template-columns: 250px 1fr;
    min-height: 100vh;
  }
</style>

Notice that routes under (marketing) and (app) don't share any layout code. The grouping ensures complete isolation between your public and authenticated experiences.

Using +layout@reset to Break Inheritance

Sometimes you need to break out of the layout hierarchy entirely. This is where the @ reset syntax becomes invaluable. The pattern +layout@name tells SvelteKit which layout to use as the parent, allowing you to skip intermediate layout files.

Consider a modal or full-screen onboarding flow within your authenticated app. You might have routes like:

typescript
src/routes/
├── (app)/
│   ├── +layout.svelte
│   ├── dashboard/
│   │   └── +page.svelte
│   └── onboarding/
│       ├── +layout@(app).svelte    // Resets to (app) layout
│       ├── +page.svelte             // /onboarding
│       ├── company-details/
│       │   └── +page.svelte         // /onboarding/company-details
│       └── team-setup/
│           └── +page.svelte         // /onboarding/team-setup

The onboarding layout can be full-screen without the sidebar, while still being part of the app group:

typescript
// src/routes/(app)/onboarding/+layout@(app).svelte
<script>
  import ProgressBar from '$lib/components/ProgressBar.svelte';
  import { page } from '$app/stores';
</script>

<div class="onboarding-container">
  <ProgressBar 
    steps={['Company', 'Team', 'Workspace']} 
    currentStep={getCurrentStep($page.url.pathname)}
  />
  <div class="content">
    <slot />
  </div>
</div>

<style>
  .onboarding-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  }
  .content {
    width: 100%;
    max-width: 600px;
    padding: 2rem;
    background: white;
    border-radius: 8px;
  }
</style>

The onboarding pages skip the regular app layout (with sidebar and top nav) and go straight to the (app) group parent, enabling full-screen presentation while keeping user context.

Separate Auth Layouts for Different User States

Real-world applications need entirely different layouts for unauthenticated versus authenticated states. You might create an (auth) layout group for login, signup, and password reset flows:

typescript
src/routes/
├── (auth)/
│   ├── +layout.svelte
│   ├── login/
│   │   └── +page.svelte      // /login
│   ├── signup/
│   │   └── +page.svelte      // /signup
│   └── reset-password/
│       └── +page.svelte      // /reset-password
├── (marketing)/
│   └── ...
└── (app)/
    └── ...

The auth layout centers a form and keeps branding minimal:

typescript
// src/routes/(auth)/+layout.svelte
<script>
  import Logo from '$lib/components/Logo.svelte';
</script>

<div class="auth-page">
  <div class="auth-container">
    <div class="auth-header">
      <Logo size="md" />
    </div>
    <div class="auth-form">
      <slot />
    </div>
    <div class="auth-footer">
      <p>By signing in, you agree to our Terms of Service</p>
    </div>
  </div>
</div>

<style>
  .auth-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    background: #f5f5f5;
  }
  .auth-container {
    width: 100%;
    max-width: 400px;
    padding: 2rem;
    background: white;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  }
</style>

You can add server-side logic to prevent authenticated users from accessing these routes:

typescript
// src/routes/(auth)/+layout.server.ts
import { redirect } from '@sveltejs/kit';

export function load({ locals }) {
  if (locals.user) {
    throw redirect(302, '/dashboard');
  }
}

Sharing Root Layout Data Across Groups

Despite layout isolation, you often need shared data available everywhere. The root +layout.server.ts executes for every route and can provide global context:

typescript
// src/routes/+layout.server.ts
import { getUser } from '$lib/server/auth';
import { getSettings } from '$lib/server/db';

export async function load({ locals, cookies }) {
  const user = await getUser(locals.sessionId);
  const settings = await getSettings();

  return {
    user,
    settings: {
      siteTitle: settings.name,
      supportEmail: settings.supportEmail,
      maintenanceMode: settings.maintenanceMode
    }
  };
}

Every layout and page has access to this data through the data prop:

typescript
// src/routes/(app)/dashboard/+page.svelte
<script>
  export let data;
</script>

<h1>Welcome back, {data.user.name}!</h1>

Child layout groups can extend root data without losing parent-level context:

typescript
// src/routes/(app)/+layout.server.ts
import { getBillingInfo } from '$lib/server/billing';

export async function load({ locals, parent }) {
  const parentData = await parent();
  const billing = await getBillingInfo(locals.user.id);

  return {
    ...parentData,
    billing
  };
}

Common Mistakes

Forgetting that reset points break hierarchy completely. When you use +layout@root.svelte, you lose access to all intermediate layout data. Always check what data your component needs before resetting.

Mixing logic between group layouts. Each layout group should be self-contained. Avoid checking for user roles or different states within a layout—use hooks in +layout.server.ts to redirect instead.

Not loading parent data properly. When extending a layout with export async function load({ parent }), always call await parent() first to preserve inherited data. Overwriting without spreading causes data loss.

Creating too many layout groups. Three groups (auth, marketing, app) cover most use cases. More than five usually indicates over-engineering. Evaluate whether you truly need layout separation or just conditional rendering.

Key Takeaways

  • Layout groups with parentheses ((name)) create isolated layout hierarchies for different sections of your app without affecting URLs
  • Use +layout@name syntax to reset layout inheritance and skip intermediate layouts when needed
  • Common groups are (auth) for login flows, (marketing) for public pages, and (app) for authenticated user experiences
  • Root-level +layout.server.ts provides shared data to all groups; child layouts access it via await parent()
  • Keep layout logic simple—use server-side redirects to enforce access control rather than complex conditional rendering
  • Plan your layout structure early; refactoring groups across a large application is friction-heavy
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles