Skip to content

Conversation

@DustinFischer
Copy link
Contributor

Add new permalink tokens allowing slugified sections in the URL path, with fallback behavior and slice syntax support.

  • :sectionslug: the content's section using the section's slug frontmatter parameter, else falling back to its title parameter.
  • :sectionslugs: the content's sections hierarchy, using each section's respective slug frontmatter parameter, else falling back to its title parameter.

Includes integration tests and documentation updates.

Closes #13788

@CLAassistant
Copy link

CLAassistant commented Aug 14, 2025

CLA assistant check
All committers have signed the CLA.

Add slugified section permalink tokens with fallback behavior and slice syntax support.

Includes integration tests and documentation updates.

 Fixes gohugoio#13788

gofmt

refactor

comments

errs

gofmt
@DustinFischer DustinFischer force-pushed the feat/sectionslugs-permalink-13788 branch from 938aa30 to df23ab5 Compare August 14, 2025 17:37
return "", nil
}

sectionPage, err := p.GetPage("/" + p.Section())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p.Section() returns the first level (that method was added before we had a concept of nested section), which is, I'm guessing, not what we want here. Also, I don't see why this is needed -- isn't p the section you want the title from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention with this change was to keep the :sectionslug token symmetrical with the :section token which puts the first level section in the path (here's a related discussion from the original forum post).

For example, if we had content structure:

  • content/a/_index.md with slug: section-a-slug
  • content/a/b/myarticle.md with my-article-slug

If p is the Page for a/b/myarticle.md then the expected output for the following url formats would be :

  • /:section/:slug -> /a/my-article-slug
  • /:sectionslug/:slug -> /section-a-slug/my-article-slug
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, OK, i read this a little fast then. But then I suggest we do sectionPage := p.FirstSection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's a good point. I'll make the change.

var entries []string
sectionEntries := p.CurrentSection().SectionsEntries()

for i := range sectionEntries {
Copy link
Member

@bep bep Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this would work, it's much more effective (and simpler) to just do for _, section := range p.Ancestors().Reverse() {}

Copy link
Contributor Author

@DustinFischer DustinFischer Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had considered and tried this approach initially. The issue was that p.Ancestors() includes the home page if present, so the path element for the home page would be included in the published site structure as well.

I tried to keep the behaviour of :sectionslugsas similar as possible to :sections which never includes the home page in the path. I suppose I could add a check on p.IsSection() to filter out the home page as well? In that scenario would it be necessary to look at performance considerations? It seemed to me like the GetPage() approach would require fewer tree lookup operations compared to using Ancestors() combined with IsSection().

Copy link
Member

@bep bep Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty p.Ancestors() is at least as performant as using GetPage, and it has the upside of making the code much simpler, which is always a good thing. I suggest you check for .IsHome and break.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'm happy to make this change. I agree it will simplify code and improve readability.

…lementation

Improves the permalink token implementation based on PR feedback:
- Use FirstSection() instead of GetPage lookup for :sectionslug
- Use Ancestors().Reverse() for efficient section hierarchy building in :sectionslugs
- Update tests to use proper page structure with ancestors instead of mock functions
- Implement missing IsHome(), IsSection(), Ancestors() methods in test helpers
@DustinFischer
Copy link
Contributor Author

@bep I've made the changes you suggested:

  • Use FirstSection() instead of GetPage lookup for :sectionslug
  • Use Ancestors().Reverse() instead of GetPage lookup for :sectionslugs
@DustinFischer DustinFischer requested a review from bep August 22, 2025 09:52
@bep
Copy link
Member

bep commented Aug 23, 2025

This looks great, thanks! I will merge once the test goes green. A side note: I plan on doing a Hugo release on Monday, mostly to get a Hugo release with Go 1.25 out there, but this new feature will also be very useful to many.

@bep bep merged commit 12ace3a into gohugoio:master Aug 23, 2025
6 checks passed
@DustinFischer
Copy link
Contributor Author

Thanks @bep, that's great news! And thanks for the considerate review and feedback. Hoping that it will be a useful enhancement to many users. I'll keep an eye out for the release today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

4 participants