Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add routePattern to GetStaticPathsOptions #13520

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

openscript
Copy link

@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

/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: 20c8125

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 (20c8125) with main (b8645c1)

Summary

✅ 6 untouched benchmarks

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
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.

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)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants