Support for multiple queries per template
I would like to be able to have my templates execute multiple root queries instead of just one query per template.
For example, take the following template structure:
<body>
<head>// seo stuff, etc</head>
<Container>
<Header />
<Content >
<TitleBar />
<PageContent />
<RelatedPosts />
<AuthorBox />
</Content>
<Footer />
</Container>
</body>
Each of these components have their own data needs.
<head>: requires SEO information<Header>: requires Nav Menu / Nav Menu Items<Content>: requires page title, content, author information, etc. . .things directly connected to the current node represented by the URI<Footer>: probably requires another Nav Menu, etc
Right now, I can get all of this information from one RootQuery in my template, like so:
import Header from '@components/Header';
import Footer from '@components/Footer';
import Content from '@components/Content';
export default const MyTemplate = () => {}
MyTemplate.query = gql`
{
...HeaderFragment
...ContentFragment
...FooterFragment
}
${Header.fragment}
${Content.fragment}
${Footer.fragment}
`
And each of those components could define their fragments accordingly:
Header.js
Header.fragment = gql`
fragment HeaderFragment on RootQuery {
menu( id: "PrimaryNav" ) {
menuItems {
...
}
}
}
`
Content.js
Content.fragment = gql`
fragment ContentFragment on RootQuery {
page( id: $id ) {
id
title
content
...
}
}
`
Footer.js
Footer.fragment = gql`
fragment FooterFragment on RootQuery {
menu( id: "FooterNav" ) {
menuItems {
...
}
}
}
`
This is nice, as I can component-ize my layouts and define the data that's needed for each component at the component level using Fragments.
The downside, however, is that each query is now fetching a lot of un-related resources, and this can lead to significant over-purging from WPGrapQL Smart Cache.
For example, the query above will now contain:
- Menu Items from the Primary Nav
- Menu Items from the Footer Nav
- Content for a particular node
This means that the query will be purged from caches whenever either Menu is updated or when the content node is updated.
This means every time a post is updated, we'd need to re-fetch the nav menu, because they're part of the same query and the entire query would be a cache miss.
If I was able to write these queries independently, like so:
Header.js
Header.query = gql`
query GetPrimaryNav {
menu( id: "PrimaryNav" ) {
menuItems {
...
}
}
}
`
Content.js
Content.query = gql`
query GetPageContent( $id: ID! ) {
page( id: $id ) {
id
title
content
...
}
}
`
Footer.js
Footer.query = gql`
query GetFooterNav {
menu( id: "FooterNav" ) {
menuItems {
...
}
}
}
`
Then I could import them and use them like so:
import Header from '@components/Header';
import Footer from '@components/Footer';
import Content from '@components/Content';
export default const MyTemplate = (props) => {
<Container>
<Header {props.howeverWeDetermineHeaderQueryData } />
<Content {props.howeverWeDetermineContentQueryData } >
<TitleBar />
<PageContent />
<RelatedPosts />
<AuthorBox />
</Content>
<Footer {props.howeverWeDetermineFooterQueryData } />
</Container>
}
MyTemplate.queries = ( $seedNode ) => {
return [
{
query: Header.query,
},
{
query: Content.query,
variables: { id: $seedNode.id }
},
{
query: Footer.query
}
]
Now each of these queries is cached independently by WPGraphQL Smart Cache.
Updating the post will lead to a cache miss of the Content.query but will still leave Header.query and Footer.query as cache hits, leading to less processing by the WP server.
This also leads to an improved DX, as the root components (Header/Footer) that aren't directly connected to the Content Node can be queried independently. They shouldn't need to be imported to the template query as fragments on RootQuery.
The direct connections to the content node (i.e. the Title bar, page content and meta, related posts, Author bio, etc) can be imported to the Content as components and imported to the Content.Query via fragments.
I was experiencing a similar need with grabbing global things like menus, SEO fields, redirects, etc without having to add each one to each wp-templates/ file: https://github.com/wpengine/faustjs/discussions/1498
I posted a hacky but working solve to append things to the seed query by merging together ASTs but ultimately I like this approach more because of the emphasis on caching individual globals. I'd find this to be a great feature for Faust as a lot of us will just share these types of globals setup across multiple projects and this would allow us to bootstrap projects more quickly.