Skip to content

Conversation

openscript
Copy link
Contributor

@openscript openscript commented Mar 30, 2025

Changes

getStaticPaths() is run in another scope, that makes it impossible to access Astro render context. For some use cases of implementing getStaticPaths(), it becomes necessary to know the routePattern to calculate the params and props for each page route.

For example imagine you have a route [...locale]/[files]/[slug].astro and you want to implement a getStaticPaths() that,

  • provides the current [...locale]
  • provides [files] according to the locale, so you can have a path translation
  • provides a slugified version of the pages title as [slug]
  • calculate all translations of a page so you can offer a language switcher that directly links the user to the other language.

To lookup what [files] should be substituted with, you need to parse the routePattern as well as for calculating the translations.

This change provides routePattern via GetStaticPathsOptions. It isn't a breaking change as whoever wants to consume it can, but doesn't need to.

A workaround would be calculating this props during rendering of the actual page. For each render getCollection() needs to be invoked again. Then I would also wonder why props are returned from getStaticPaths() after all.

Example code

[...locale]/[files]/[slug].astro
import { C } from "../../../site.config";

export const getStaticPaths: GetStaticPaths = async ({ routePattern }) => {
  const filesCollection = await getCollection("files");

  return translationPaths(filesCollection, {
    routePattern: routePattern,
    defaultLocale: C.DEFAULT_LOCALE,
    segmentTranslations: C.SEGMENT_TRANSLATIONS,
  });
};
site.config.ts
export const C = {
  LOCALES: ["de-CH", "zh-CN"],
  DEFAULT_LOCALE: "de-CH" as const,
  SEGMENT_TRANSLATIONS: {
    "de-CH": {
      files: "dateien",
    },
    "zh-CN": {
      files: "files",
    },
  },
};
translation-path.ts
import { GetStaticPathsResult } from "astro";
import limax from "limax";
import { checkI18nLoaderCollection } from "../schemas/i18n-loader-schema";
import { buildPath, parseRoutePattern, SegmentTranslations } from "../utils/route";

type Config = {
  routePattern: string;
  segmentTranslations: SegmentTranslations;
  defaultLocale: string;
  localeParamName?: string;
  slugParamName?: string;
  titleDataKey?: string;
};

const defaultConfig = {
  localeParamName: "locale",
  slugParamName: "slug",
  titleDataKey: "title",
};

function getSegmentTranslations(segments: SegmentTranslations, locale: string) {
  if (!segments[locale]) throw new Error(`No slugs found for locale ${locale}`);
  return segments[locale];
}

export function translationPaths(collection: unknown[], config: Config): GetStaticPathsResult {
  checkI18nLoaderCollection(collection);
  const { routePattern, segmentTranslations, defaultLocale, localeParamName, slugParamName, titleDataKey } = { ...defaultConfig, ...config };
  const route = parseRoutePattern(routePattern);

  route.forEach((segment, index) => {
    if (
      segment.param &&
      segment.value !== localeParamName &&
      index !== route.length - 1 &&
      !Object.values(segmentTranslations).every((translation) => translation[segment.value])
    ) {
      throw new Error(`No slugs found for route segment ${segment.value}`);
    }
  });

  return collection.map((entry) => {
    const segments = getSegmentTranslations(segmentTranslations, entry.data.locale);
    const translationId = entry.data.translationId;

    const entryTranslations = collection.filter((entry) => entry.data.translationId === translationId);

    const translations = entryTranslations.reduce(
      (previous, current) => {
        const segmentValues = getSegmentTranslations(segmentTranslations, current.data.locale);
        segmentValues[localeParamName] = defaultLocale === current.data.locale ? "" : current.data.locale;
        const slugValue = titleDataKey ? (current.data as Record<string, string | undefined>)[titleDataKey] : undefined;
        if (slugValue) {
          segmentValues[slugParamName] = limax(slugValue);
        }
        return {
          ...previous,
          [current.data.locale]: buildPath(route, segmentValues),
        };
      },
      {} as Record<string, string>
    );

    return {
      params: {
        ...segments,
      },
      props: {
        translationId,
        translations,
      },
    };
  });
}

Testing

I don't know how to test this. I'm very happy to add tests, if you can point me in the right direction.

Docs

withastro/docs#12316

/cc @withastro/maintainers-docs for feedback!

The new options should be mentioned in https://docs.astro.build/en/reference/routing-reference/#getstaticpaths.

Copy link

changeset-bot bot commented Mar 30, 2025

🦋 Changeset detected

Latest commit: 7777fb1

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added pkg: astro Related to the core `astro` package (scope) docs pr labels Mar 30, 2025
Copy link

codspeed-hq bot commented Mar 30, 2025

CodSpeed Performance Report

Merging #13520 will not alter performance

Comparing openscript:feat/add-route-pattern-to-get-static-paths-options (7777fb1) with main (691e5b8)1

Summary

✅ 6 untouched

Footnotes

  1. No successful run was found on main (cd0fac8) during the generation of this report, so 691e5b8 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

Blocking because this isn't a patch, but a feature

@openscript openscript changed the title feat(core): add route pattern to get static paths options feat(core): add routePattern to GetStaticPathsOptions Apr 1, 2025
@ematipico
Copy link
Member

ematipico commented Apr 1, 2025

Thank you @openscript for the PR. It's not very clear what this new feature is for. I read the description, but unfortunately, it doesn't provide a good enough example of how users should consume the new parameter.

As for the tests, there's a contribution guide that should help. As for where, you could create a new file or extend the existing one.

You will have to create new fixtures or files where you use the new feature, and execute certain assertions based on what these new files do. Tests are beneficial to us maintainers too, so we see how the new features are used.

Also, you will be in charge of creating docs, if the PR is accepted.

@openscript
Copy link
Contributor Author

Thank you @ematipico for guiding me.

Let's proceed like this:

  1. Clarify my intend
  2. Write tests
  3. Write documentation

I've already updated the PR description and I kindly ask you to reread it.

With this PR a helper function for writing getStaticPaths() can be offered by https://github.com/openscript/astro-loader-i18n.

@manuelmeister
Copy link

This would be helpful! How can I help to bring this along?

@florian-lefebvre
Copy link
Member

For this specific case, I wonder if we could instead make sure Astro.routePattern is available in getStaticPaths. It's probably possible!

@ematipico
Copy link
Member

ematipico commented May 27, 2025

For this specific case, I wonder if we could instead make sure Astro.routePattern is available in getStaticPaths. It's probably possible!

Isn't it possible to do something like this?

const routePattern = Astro.routePattern;

export function getStaticPaths() {
	// do something with routePattern
	return []
}

@florian-lefebvre
Copy link
Member

No you can't reference stuff from the frontmatter outside of getStaticPaths

@ematipico
Copy link
Member

Ah, yeah, I misremembered how I used the collections inside the function.

@florian-lefebvre
Copy link
Member

florian-lefebvre commented May 27, 2025

Related function:

site: site ? new URL(site) : undefined,

Happens too early, that would require a compiler change + currying:

-staticPaths = await mod.getStaticPaths({ paginate })
+staticPaths = await mod.getStaticPaths({ ...astroStaticPartial, routePattern: route.route })({ paginate })
-const $$Astro = $$createAstro();
-const Astro = $$Astro;
-export async function getStaticPaths({ paginate }) {
+export const getStaticPaths = (Astro) => async function getStaticPaths({ paginate }) {
	const posts = await getCollection('blog');
	console.log(Astro)
	return posts.map((post) => ({
		params: { slug: post.id },
		props: post,
	}));
}

@florian-lefebvre
Copy link
Member

if we deprecate Astro usage in getStaticPaths, then I'm fine with exposing routePattern on getStaticPaths directly

@florian-lefebvre
Copy link
Member

Hey again! Sorry it took so much time to get to this, but the core team agreed to deprecate Astro usage in getStaticPaths so we'd love to land this! Before we can do so, can you do a couple of things:

  1. Update the changeset to be a minor
  2. Open a docs PR. @sarah11918 where do you think this should go? Should it go alongside https://docs.astro.build/en/reference/routing-reference/#paginate?

@github-actions github-actions bot added feat: markdown Related to Markdown (scope) pkg: svelte Related to Svelte (scope) pkg: vue Related to Vue (scope) pkg: example Related to an example package (scope) 🚨 action Modifies GitHub Actions pkg: react Related to React (scope) pkg: preact Related to Preact (scope) pkg: solid Related to Solid (scope) pkg: integration Related to any renderer integration (scope) pkg: create-astro Related to the `create-astro` package (scope) labels Sep 12, 2025
@openscript openscript force-pushed the feat/add-route-pattern-to-get-static-paths-options branch from a4dc5f5 to fad5d46 Compare September 12, 2025 09:21
@github-actions github-actions bot removed feat: markdown Related to Markdown (scope) pkg: svelte Related to Svelte (scope) labels Sep 12, 2025
@github-actions github-actions bot added the semver: minor Change triggers a `minor` release label Sep 12, 2025
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

This PR is blocked because it contains a minor changeset. A reviewer will merge this at the next release if approved.

@sarah11918
Copy link
Member

As for docs content, yes, I think another section after paginate sounds like a good idea on that page!

Looking at the existing routing reference page and seeing that it's not all exactly formated as we've now started to form our reference pages, so I think we can be a little flexible about what we put on this page until we (probably @ArmandPhilippot !) decide how to standardize it so that it doesn't have to hold up this feature documentation. 😅

I will note that we have existing docs for what Astro.routePattern is here: https://docs.astro.build/en/reference/api-reference/#routepattern . So assuming that what is made available to getStaticPaths() is identical to that, we don't have to redocument too many details of it itself (we can say that it makes the same data available inside getStaticPaths() and link).

I think it might be helpful if this section can describe a specific case of accessing and using the route data inside getStaticPaths(), maybe following the model of "Passing data with props" and have a section like:

### Accessing route data

That's useful docs content to have either way, even if we eventually move content like that to the Routing Guide page (instead of reference) and make this a more traditional API reference entry. So, I think getting that written now is helpful, while we want to show people how to use the new feature, in addition to just providing a strict reference entry.

Armand, what are your thoughts on this?

@ArmandPhilippot
Copy link
Member

I agree with your suggestion and moving "Data passing with props" and the new "Accessing route data" section to Routing! This looks more like guidance than reference to me. But this doesn't have to be done with this PR, so updating https://docs.astro.build/en/reference/routing-reference/#getstaticpaths as Florian suggested sounds like the right place!

Re:

Looking at the existing routing reference page and seeing that it's not all exactly formated as we've now started to form our reference pages

Yeah, this is a bit tricky here because the undocumented types rely on Typescript generics (and user's project). I thought I've added a comment regarding paginate() in withastro/docs#8943 but I can only see one for props and params. Looking back, I think we could format them properly simplifying a little the type for docs and, with the description, this shouldn't be confusing!

@florian-lefebvre
Copy link
Member

Alright then @openscript feel free to ping me once you open that docs PR! Doesn't have to be perfect, even something minimal is perfectly fine to get things started

@openscript
Copy link
Contributor Author

openscript commented Sep 15, 2025

Hey @florian-lefebvre

I tried my best to document the routePattern in getStaticPaths() with this PR: withastro/docs#12316

Let me know what you think about it. I'm happy to tailor it further.

Copy link
Member

@sarah11918 sarah11918 left a comment

Choose a reason for hiding this comment

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

Just noting that I'm working on the docs, then will be in here for beefy, exciting, changeset goodness! 🙌

@florian-lefebvre
Copy link
Member

Could you add a test for this? I think what you can do is:

@openscript
Copy link
Contributor Author

Thank you for all the feedback! I'll work on the documentation and also add some tests in the next few days.

Copy link
Member

@florian-lefebvre florian-lefebvre left a comment

Choose a reason for hiding this comment

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

Approving code wise! Thanks a lot

@sarah11918
Copy link
Member

Noting that we're getting very close to finalizing the docs on this one, which means we will be close to knowing what we want to put in the changeset! 🥳

@openscript
Copy link
Contributor Author

openscript commented Sep 19, 2025

Hello @florian-lefebvre @sarah11918 @ArmandPhilippot

I just wanted to express that I enjoyed to work the last few days on this issue. Especially participating in writing the the documentation and reflecting on how to express things in a way the reader catches the idea, was fun.

Hopefully we have the chance to work together on another task someday soon.

Cheers 🥂

@florian-lefebvre
Copy link
Member

Glad you enjoyed! If you're interested, we're working towards Astro 6 and there are tasks to pick: #14383

@florian-lefebvre florian-lefebvre added this to the v5.14.0 milestone Sep 20, 2025
@florian-lefebvre florian-lefebvre removed their assignment Sep 22, 2025
@openscript
Copy link
Contributor Author

This issue was now added to the v5.14.0 milestone, whereas the documentation was prepared to release this for v5.15.0. Should we update the documentation to v5.14.0?

@florian-lefebvre
Copy link
Member

Yes, good catch! This is indeed for 5.14 to be released this week

@openscript
Copy link
Contributor Author

openscript commented Sep 22, 2025

I've updated the docs PR. 😄

Copy link
Member

@sarah11918 sarah11918 left a comment

Choose a reason for hiding this comment

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

Approving for docs! 🚀

@ascorbic ascorbic merged commit a31edb8 into withastro:main Sep 24, 2025
21 of 22 checks passed
@astrobot-houston astrobot-houston mentioned this pull request Sep 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs pr pkg: astro Related to the core `astro` package (scope) semver: minor Change triggers a `minor` release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants