kit icon indicating copy to clipboard operation
kit copied to clipboard

`route.segment` as a way to get the specific path to the current `+layout.*.js` file

Open ivanhofer opened this issue 2 years ago • 5 comments

Describe the problem

Sometimes it would be useful to know "why" a certain load function within a +layout.*.js file gets executed.

A specific use-case is: breadcrumb navigation, where each +layout.*.js file can contribute its own metadata (url, title, etc.) to an array. In this case each layout would need to know it's specific part of the url. Currently this requires manual parsing of the url.pathname. in each file

Describe the proposed solution

It would be great to have a way to get the information for which part of the url a specific layout file was called. I think a new segment (or better name) property inside the route object for all load functions would make sense.

e.g. with this folder structure

 /products
	/[id]
		/edit
			/+layout.server.js
			/+page.js
		/+layout.js
	/+layout.server.js

when accessing the url /products/123/edit

following route.segment schould be defined in those files:

  • /products/+layout.server.js: /products
  • /products/[id]/+layout.js: /products/123
  • /products/[id]/edit/+layout.server.js: /products/123/edit

For all +page.*.js the segment would be equal to url.pathname.

Alternatives considered

Leave it as it is.

Importance

would make my life easier

Additional Information

No response

ivanhofer avatar Dec 26 '22 10:12 ivanhofer

I've been looking for this very thing, I'll paraphrase the discord thread:

Can each +layout.js access their own route id? I want to use it for some breadcrumb logic. ie: assuming I am accessing the URL /users/david/edit I would do:

/users/+layout.js
  $breadcrumbs.add(theRouteIdForThisLayout, 'Users')
  ie:
  $breadcrumbs.add('/users', 'Users')

/users/[username]/+layout.js
  $breadcrumbs.add(theRouteIdForThisLayout, 'Users')
  ie:
  $breadcrumbs.add('/users/[username]', 'David King')

/users/[username]/edit/+layout.js
  $breadcrumbs.add(theRouteIdForThisLayout, 'Users')
  ie:
  $breadcrumbs.add('/users/[username]/edit, '✏️ Edit')

This ^ logic would populate my breadcrumb data:

[
  { path: `/users`, title: 'Users' },
  { path: `/users/david`, title: 'David King' },
  { path: `/users/david/edit`, title: '✏️ Edit' },
]

It feels similar to $types in that it's context aware / specific to the file.

Although, for this specific case, I'd not be interested in the route.id, but rather the resolved part of the route, in order to be used in a link <a href={segment}>...

  • /users/[username] - not useful here
  • /users/david - better

oodavid avatar Jan 13 '23 11:01 oodavid

A layout has no id, only pages have it. Something breadcrumb-y built in would help you with that better probably, something like "the current page has the following upper pages in its tree" - although that's probably not a solution for some cases since the folder tree and the perceived route hierarchy wouldn't necessarily correlate.

dummdidumm avatar Jan 13 '23 12:01 dummdidumm

@dummdidumm that would be ideal. Having access to "parents" could be a great solution. Not sure how typing would work though.

Thinking out loud, a Breadcrumbs component may look like:

<script>
  import parents from '$app/stores';
</script>

{#each parents as parent}
  <a href={parent.url.pathname}>
    {parent.data.title}
  </a>
{/each}

As you can see, the title param is problematic as each parent can have different data structures.

oodavid avatar Jan 13 '23 14:01 oodavid

A layout has no id, only pages have it.

I thought routes had ids? And they're made up of pages and layouts...

oodavid avatar Jan 13 '23 14:01 oodavid

The route.id to the current layout/load function would be pretty nice. However I don´t think it´s very obvious how to implement this. Statically replacing a string like CURRENT_ROUTE_ID in files within routes could be possible... With that route id it should be possible to extract the actual path slice using sveltekits router.

david-plugge avatar Jan 13 '23 16:01 david-plugge

This feels like something that could be done in userland, no?

const _depth = 3;

export function load({ parent, url }) {
  const { breadcrumbs } = await parent();

  const breadcrumb = url.pathname.split('/').slice(0, _depth).join('/');

  return {
    breadcrumbs: [...breadcrumbs, breadcrumb]
  };
}

The one bit that's missing is knowing the depth without having to hard-code it. How important would that be? (I prefer depth to segments since it feels more general — could be used with route.id as well as url.pathname, etc.)

Rich-Harris avatar Feb 03 '23 19:02 Rich-Harris

This feels like something that could be done in userland, no?

Almost everything can be done in userland ^^. But like you ready mentioned, you need to hardcode the depth and that is not ideal for refactoring reasons. And you will end up with a line of code that get's repeated in every singe load function, where you need to manually parse the segment.

So if SvelteKit would provide that functionality, it would eliminate 2 lines of code (which have some potential to mess them up) per load function.

-const _depth = 3;

-export function load({ parent, url }) {
+export function load({ parent, route }) {
  const { breadcrumbs } = await parent();

-  const breadcrumb = url.pathname.split('/').slice(0, _depth).join('/');

  return {
-    breadcrumbs: [...breadcrumbs, breadcrumb]
+    breadcrumbs: [...breadcrumbs, route.segment]
  };
}

I just experimented a bit and additional to segment I would also wish that SvelteKit would provide a depth property on the route object. With the depth property it would be possible to keep the parallel data loading feature (no await parent()) intact and still be able to implement a breadcrumb array with the correct order.

https://stackblitz.com/edit/sveltejs-kit-template-default-gbdmhv?file=src/routes/a/+layout.server.ts

open the appplication and navigate to /a, /a/b or /a/b/c

Already totally possible in userland, but requires additional effort to maintain and could cause some issues when refactoring or copy and pasting code from another route.

ivanhofer avatar Feb 04 '23 08:02 ivanhofer

It doesn't feel like it belongs on route, to be honest — the route doesn't change between load functions. There is some precedent for event changing between load functions, because of the data property, so if it belongs anywhere it's there.

On reflection, 'depth' is a rather ambiguous concept though — what is its value inside src/routes/foo/[...bar]/+layout.js when you visit /foo/x/y/z?

  • is it 0, because there are no parent layouts?
  • is it 2, because there are two directories between it and src/routes? (Useful for manipulating route.id)
  • is it 4, because the path has four segments? (Useful for manipulating url.pathname)

If it's based on path segments, in +page.js is the depth affected by the value of trailingSlash?

I'm asking these questions less to figure out the correct answer, more to point out that it's inherently confusing to have such a thing in the framework.

The concept hitherto referred to as 'segment' is less confusing, though 'segment' is a bad name (that usually refers to a part of a pathname that doesn't include a / character). Not sure what a better one would be.

Rich-Harris avatar Feb 09 '23 00:02 Rich-Harris

It doesn't feel like it belongs on route, to be honest — the route doesn't change between load functions.

Right, this would not rerun unless someone uses await parent(). Similar to #6315 something is needed that rereuns each time a +page load get's executed.

I'm asking these questions less to figure out the correct answer, more to point out that it's inherently confusing to have such a thing in the framework.

I see. Thanks for your detailed response. The depth concept certainly does not belong into the framework.

ivanhofer avatar Feb 09 '23 06:02 ivanhofer