next-graphql-static-export

A minimal package which provides the functionality required to perform a static HTML export of dynamic pages from Next.js when using GraphQL as a data source.

Usage no npm install needed!

<script type="module">
  import nextGraphqlStaticExport from 'https://cdn.skypack.dev/next-graphql-static-export';
</script>

README

next-graphql-static-export

A minimal package which provides the functionality required to perform a static HTML export of dynamic pages from Next.js when using GraphQL as a data source.

Documentation

About this package

It is suggested that you begin by reading about Next.js Static HTML Exports. Upon doing so you will quickly realize that dynamic exports will require additional work.

This package should help you get up an running with minimal configuration, assuming you use GraphQL as a data source. The package is an early work in progress, and contributors / feedback are definitely welcome.

Usage

Create a config file in your Next.js project. If you already have a config.js that you use for ES6 exports imports within your Next.js project, you'll want to create a separate config-export.js for this purpose, as the next.config.js does not support imports.

In your config file, add something similar to the following information in your config file:

const postsQuery = require("./queries/posts-query");
const pagesQuery = require("./queries/pages-query");
const productsQuery = require("./queries/products-query");
const endpoint = `https://www.website.com/graphql`;
const typeParams = [
  {
    pageComponent: "page",
    contentType: "pages",
    query: pagesQuery,
    urlBase: "pages",
    perPage: 100,
    endpoint
  },
  {
    pageComponent: "article",
    contentType: "posts",
    query: postsQuery,
    urlBase: "articles",
    perPage: 100,
    endpoint
  },
  {
    pageComponent: "product",
    contentType: "products",
    query: productsQuery,
    urlBase: "products",
    perPage: 100,
    endpoint
  }
];

module.exports = {
  typeParams
};

With the lines below we require the files containing the GraphQL files we'll use to pull the data we need to populate our dynamic pages upon static export. For example in postsQuery.js:

module.exports = `
query GET_POSTS($first:Int $after:String){
  posts(
    first: $first
    after:$after
    where: { status:PUBLISH }
  ) {
    pageInfo {
      endCursor
      hasNextPage
    }
    nodes {
      id
      uri
      title
    }
  }
}
`;

Here we simply establish the location of our GraphQL endpoint:

const endpoint = `https://www.website.com/graphql`;

Next we setup the parameters of the content types we pass to the application. The objects in the typeParams array are the parameters that describe the data we want to pull (via the query) and the way the data should be structured for output to the Next.js exportPathMap function.

pageComponent is the component in ./pages in our Next.js project that this object should relate to. contentTypes is interpolated within this library's fetchPosts function to destructure data from the response. A future version of this library (PRs welcome) could take a function as input to perform the destructuring of the response from the query.

query is the GraphQL query we pass to this library.

urlBase is the base of the eventual URL created by the Next.js export process, i.e. the URL where our static HTML exports for this content type should live.

perPage will determine how many pieces of content per page this library will pull

endpoint is the GraphQL endpoint to query.

parseQueryResults (optional) is a custom query result parsing function we can pass along with typeParams to allow for schemas which vary from the built-in "standard" assumption. This function takes in the query response and content type, and returns an object containing nodes, hasNextPage, and endCursor. Your custom parsing function can take any route you like to arriving at those values, but they are required.

// An example of the optional query result parsing function. This example just reimplements the standard used by the library.
const parseQueryResults = (input, contentType) => {
  const {
    [contentType]: {
      nodes,
      pageInfo: { hasNextPage, endCursor }
    }
  } = input;
  return { nodes, hasNextPage, endCursor };
};

const typeParams = [
  {
    pageComponent: "page",
    contentType: "pages",
    query: pagesQuery,
    urlBase: "pages",
    perPage: 100,
    endpoint,
    parseQueryResults // optional
  },
  {
    pageComponent: "article",
    contentType: "posts",
    query: postsQuery,
    urlBase: "articles",
    perPage: 100,
    endpoint
  },
  {
    pageComponent: "product",
    contentType: "products",
    query: productsQuery,
    urlBase: "products",
    perPage: 100,
    endpoint
  }
];

module.exports = {
  typeParams
};

Note that we can have different endpoints and parseQueryResults for each content type.

Next, import this config in to next.config.js, and add the following to your exportPathMap function:


const processContent = require("next-graphql-static-export");
const { typeParams } = require("./config-export");

exportPathMap: async function() {
  // We don't want to do all of this export work if were just developing another part of the app.
  // But we don't want to hook this to NODE_ENV=dev, because we might be developing
  // but still want to evaluate the export, thus a new env var
  if (process.env.EXPORT === "false") return {};

  // Create our static export data
  const [pages, products, posts] = await processContent(typeParams);

  // Create the static pages with Next
  return {
    "/": { page: "/" },
    ...pages,
    ...products
    ...posts
  };
}

To Do

  1. Replace perPage with a general purpose filter, so as to not assume the schema of the GraphQL endpoint to be hit.

Credits

Hat tip to @jasonbahl for the inspiration for the orignal fetchPosts function.

NPM

https://www.npmjs.com/package/next-graphql-static-export

Pull Requests

PRs welcome.