Skip to content

Named layouts thingy #12928

@rChaoz

Description

@rChaoz

Describe the problem

This has been discussed a lot in the past (for example #627), but now with Svelte 5 released, I think we can take a look at this from a different angle, considering all the new Svelte 5 toys.

Describe the proposed solution

Let's consider a rather simple usecase - header that changes based on page. Most pages would want to use the same (default) header, while some can customize it. This could be achieved with snippets or with additional +page-<name>.svelte files, for example +page-appbar.svelte.

With snippets (demo)

Allow pages to export snippets:

<script>
  export const snippets = { appbar }
</script>

{#snippet appbar()} ... {/snippet}

that can then be used in layouts:

<script>
  const { children, appbar } = $props()
</script>

<aside> {@render appbar?.()} </aside>
<main> {@render children()} </main>

Pros: Code sharing between app bar and page, easy to use
Cons: Not SSR-friendly. The page needs to first render before the snippet is created, which then needs to be sent to the layout for re-rendering.

With additional files (demo)

Allow placing additional +page files that can be used in layouts:

  • +page.svelte
  • +page-appbar.svelte
<script>
	const { children, appbar } = $props()
</script>

<aside> {@render appbar()} </aside>
<main> {@render children()} </main>

Pros: SSR-friendly, intuitive (+page goes to children, +page-x goes to x)
Cons: Need a communication mechanism to synchronize the page and named slots, as they don't share code.

Both of these method can sort-of be achieved in user land, but neither of them is great.

With magic (not demo)

Syntactically I think this is great, but it would need a bit (more) help from the Svelte compiler to work.

<!-- +page.svelte -->
<script>
	const { data, Header, Footer } = $props()
</script>

<Header> This is a header </Header>
Main content
<Footer> This is a footer </Footer>


<!-- +layout.svelte -->
<script>
	const { children, header, footer } = $props()
</script>

<header> {@render header()} </header>
<main> {@render children()} </main>
<footer> {@render footer()} </footer>

For this to work, a div with display: contents would need to be created that would hold the header/footer content, which is rendered inside the snippet, and a virtual component with this div as its target would be passed to the page:

<script>
  import Layout from "./+layout.svelte"
  import Page from "./+page.svelte"

  const headerDiv = createDivDisplayContents()
  const footerDiv = createDivDisplayContents()
  const Header = createComponent({ target: headerDiv })
  const Footer = createComponent({ target: footerDiv })
</script>

<Layout>
  {#snippet header()}
    {@insert headerDiv}
  {/snipper}
  {#snippet footer()}
    {@insert footerDiv}
  {/snipper}
  <Page {Header} {Footer} />
</Layout>

Conclusions

This is pretty unhinged, but would it be doable?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions