npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@q42/sanity-plugin-page-tree

v1.2.2

Published

A Sanity plugin to make managing nested pages in a tree easier

Downloads

1,842

Readme

sanity-plugin-page-tree

This is a Sanity Studio v3 plugin.

sanity-plugin-page-tree-studio

Why?

In many example projects for headless CMSs in general, they typically create a post content type with a property like "slug" and serve the post on a route such as /posts/:slug. This becomes more complex when dealing with multiple page types and a desire to establish a dynamic page tree.

Consider having three different content types: a home page, an overview page, and a content page, and aiming to create the following URL structure:

  • / Homepage
  • /about-us Overview Page
  • /about-us/team Content Page
  • /what-we-do Content Page
  • /what-we-do/cases Overview Page
  • /what-we-do/cases/:slug Content Page

Achieving this can be challenging, especially if all the slugs need to be dynamic and editable in the CMS. This package aims to make this easy by providing a page tree view for creating and editing pages. It also includes a page tree input for creating internal page links and methods designed for use on the frontend, helping you effectively resolve urls to ids (in order to retrieve the right content for a route) and vice versa (to resolve internal page links to urls).

Installation

npm i @q42/sanity-plugin-page-tree

Usage in Sanity Studio

Create a page tree config

Create a shared page tree config file and constant to use in your page types and desk structure.

// page-tree-config.ts
import { PageTreeConfig } from '@q42/sanity-plugin-page-tree';

export const pageTreeConfig: PageTreeConfig = {
  /* Root page schema type name */
  rootSchemaType: 'homePage',
  /* Array of all page schema type names */
  pageSchemaTypes: ['homePage', 'contentPage', 'contentChildPage'],
  /* Optionally specify which document types can be the parent of a document type.
  If no allowed parents are specified for a type, all document types are allowed as a parent for that type.
  This config can also be used to prevent certain document types from having any children.*/
  allowedParents: {
    contentChildPage: ['contentPage'],
  },
  /* Api version to be used in all underlying Sanity client use */
  apiVersion: '2023-12-08',
  /* Optionally provide the field name of the title field of your page documents, to be used to generate a slug automatically for example. */
  titleFieldName: 'title',
  /* Used for showing the full url for a document and linking to it. */
  /* optional, otherwise the path is shown */
  baseUrl: 'https://example.com',
};

Create a page type

The definePageType function can be used to wrap your page schema types with the necessary fields (parent (page reference) and slug) for the page tree.

Root page (e.g. home page)

Provide the isRoot option to the definePageType function to mark the page as a root page. This page won't have a parent and slug field.

// schemas/home-page.ts
import { pageTreeConfig } from './page-tree-config';

const _homePageType = defineType({
  name: 'homePage',
  type: 'document',
  title: 'Page',
  fields: [
    // ...
  ],
});

export const homePageType = definePageType(_homePageType, pageTreeConfig, {
  isRoot: true,
});

Other pages (e.g. content page)

// schemas/content-page.ts
import { pageTreeConfig } from './pageTreeConfig';

const _contentPageType = defineType({
  name: 'contentPage',
  type: 'document',
  title: 'Page',
  fields: [
    // ...
  ],
});

export const contentPageType = definePageType(_contentPageType, pageTreeConfig);

Add page tree to desk structure

Instead of using the default document list for creating and editing pages, you can use the createPageTreeDocumentList function to create a custom page tree document list view and add it to your desk structure.

// desk-structure.ts
import { pageTreeConfig } from './page-tree-config';

export const structure = (S: StructureBuilder) =>
  S.list()
    .title('Website')
    .items([
      S.listItem()
        .title('Pages')
        .child(
          createPageTreeDocumentList(S, {
            config: pageTreeConfig,
            extendDocumentList: builder => builder.id('pages').title('Pages').apiVersion(pageTreeConfig.apiVersion),
          }),
        ),
    ]);

Create internal page links

A link to an internal page is a reference to a page document. The PageTreeField component can be used to render a custom page tree input in the reference field.

import { pageTreeConfig } from './page-tree-config';

const linkField = defineField({
  name: 'link',
  title: 'Link',
  type: 'reference',
  to: pageTreeConfig.pageSchemaTypes.map(type => ({ type })),
  components: {
    field: props => PageTreeField({ ...props, config: pageTreeConfig }),
  },
});

Document internationalization

This plugin supports the @sanity/document-internationalization plugin. To enable this, do the setup as documented in the plugin and additionally provide the documentInternationalization option to the page tree configuration file.

// page-tree-config.ts
import { PageTreeConfig } from '@q42/sanity-plugin-page-tree';

export const pageTreeConfig: PageTreeConfig = {
  ...,
  /* Configuration to make this plugin work with the @sanity/document-internationalization plugin */
  documentInternationalization: {
    /* Supported languages. Note that, the order is used in the page tree view. */
    supportedLanguages: ['en', 'nl'],
    /* Optionally change the languageField, like you can do in the original plugin */
    languageField: 'locale', // Defaults to 'language'
  }
};

Usage on the frontend

In order to retrieve the right content for a specifc route, you need to make "catch all" route. How this is implemented depends on the framework you are using. Below are some examples for a Next.JS and React single page appication using react router.

Regular client

In order to get the page data for the requested path you have to creat a client. Afterwards you can retrieve a list of all the pages with the resolved path and find the correct page metadata. With this metadata you can retrieve the data of the document yourself.

import { createPageTreeClient } from '@q42/sanity-plugin-page-tree/client';

const pageTreeClient = createPageTreeClient({
  config: pageTreeConfig,
  client: sanityClient,
});

async function getPageForPath(path: string) {
  // You might want to cache or save this metadata in memory. For example for looking up urls for linked pages
  const allPagesMetadata = await pageTreeClient.getAllPageMetadata();
  const page = allPagesMetadata.find(page => page.path === path);
  if (!page) {
    // here you want to handle a 404
    return;
  }

  return page;
}

Next.JS Client

For users using the App Router of Next.JS with Server Components, we can benefit from the "Request Memoization" feature. You can import the dedicated next client:

import { createNextPageTreeClient } from '@q42/sanity-plugin-page-tree/next';

This client provides you with some additional helper methods:

  • getPageMetadataById useful for retrieving the url when you have a reference to a page
  • getPageMetadataByUrl useful for retrieving the id when you have the path

Examples

For full examples, see the following projects:

License

MIT © Q42

Develop & test

For local development and testing you need to link and watch the library and link it to any of the studio example projects. The basic studio example project, located in examples/studio, is a good starting point.

Link & watch

  • Run npm install in the root directory to install the dependencies.
  • Run npm run link-watch in the root directory.

Example studio

  • To run the example studio, go to the examples/studio directory.
  • Run npx yalc add @q42/sanity-plugin-page-tree && npx yalc link @q42/sanity-plugin-page-tree && npm install
  • Run npm run dev

This plugin uses @sanity/plugin-kit with default configuration for build & watch scripts.

See Testing a plugin in Sanity Studio on how to run this plugin with hotreload in the studio.