From 8a82517a58f70cc3b6bad8dd380055520c9034dd Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Mon, 5 Jan 2026 09:18:44 -0800 Subject: [PATCH 1/7] initial implementation of handling for dynamic imports --- .changeset/funny-ravens-run.md | 47 +++++++++++++++++++ docs/how-to/basic-setup/index.md | 10 ++-- .../wp-templates/index.js | 36 ++++++++++---- packages/faustwp-core/src/getTemplate.ts | 15 +++++- .../faustwp-core/src/getWordPressProps.tsx | 15 +++++- 5 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 .changeset/funny-ravens-run.md diff --git a/.changeset/funny-ravens-run.md b/.changeset/funny-ravens-run.md new file mode 100644 index 000000000..d79104d79 --- /dev/null +++ b/.changeset/funny-ravens-run.md @@ -0,0 +1,47 @@ +--- +'@faustwp/core': minor +--- + +Feat: Added support `next/dynamic` imports for templates to reduce initial bundle size in a way that's backwards compatible with static imports. + +This solves a known issue in Faust where all defined templates are bundled together and loaded on every WordPress page. by enabling the use of dynamic importing of templates this issue is resolved. Now templates are only loaded as needed per route. + +It's recommended you migrate to dynamic imports by updating your template file. Here's an example: + +```js title=src/wp-templates/index.js +// Old Static Templates +import category from './category'; +import tag from './tag'; +import frontPage from './front-page'; +import page from './page'; +import single from './single'; + +export default { + category, + tag, + 'front-page': frontPage, + page, + single, +}; + +// New Dynamic Templates +import dynamic from 'next/dynamic'; + +const category = dynamic(() => import('./category.js')); +const tag = dynamic(() => import('./tag.js')); +const frontPage = dynamic(() => import('./front-page.js')); +const page = dynamic(() => import('./page.js')); + +// The above examples assume use of default exports. If you are using named exports you'll need to handle that: +const single = dynamic(() => import('./single.js').then(mod => mod.Single)); + +export default { + category, + tag, + 'front-page': frontPage, + page, + single, +}; +``` + +For further info see the Next.js docs on the use of [`next/dynamic`](https://nextjs.org/docs/pages/guides/lazy-loading#nextdynamic-1). diff --git a/docs/how-to/basic-setup/index.md b/docs/how-to/basic-setup/index.md index 611a174be..81449ce44 100644 --- a/docs/how-to/basic-setup/index.md +++ b/docs/how-to/basic-setup/index.md @@ -175,13 +175,13 @@ In the `SingleTemplate` component, we receive the props, destructure the `title` Finally, we have to make Faust.js aware that this template exists. To do that, create an `index.js` file inside the `wp-templates` folder with this code inside: ```js title="wp-templates/index.js" -import SingleTemplate from "./single"; +import dynamic from 'next/dynamic'; -const templates = { - single: SingleTemplate, -}; +const category = dynamic(() => import('./category.js')); -export default templates; +export default { + single, +}; ``` ### C. Create a catch-all route diff --git a/examples/next/faustwp-getting-started/wp-templates/index.js b/examples/next/faustwp-getting-started/wp-templates/index.js index 3cabd3e14..0e8e6b51e 100644 --- a/examples/next/faustwp-getting-started/wp-templates/index.js +++ b/examples/next/faustwp-getting-started/wp-templates/index.js @@ -1,13 +1,29 @@ -import category from './category'; -import tag from './tag'; -import frontPage from './front-page'; -import page from './page'; -import single from './single'; +import dynamic from 'next/dynamic'; + +const category = dynamic(() => import('./category.js'), { + loading: () =>

Loading Category Template...

, +}); + +const tag = dynamic(() => import('./tag.js'), { + loading: () =>

Loading Tag Template...

, +}); + +const frontPage = dynamic(() => import('./front-page.js'), { + loading: () =>

Loading Front Page Template...

, +}); + +const page = dynamic(() => import('./page.js'), { + loading: () =>

Loading Page Template...

, +}); + +const single = dynamic(() => import('./single.js'), { + loading: () =>

Loading Single Post Template...

, +}); export default { - category, - tag, - 'front-page': frontPage, - page, - single, + category, + tag, + 'front-page': frontPage, + page, + single, }; diff --git a/packages/faustwp-core/src/getTemplate.ts b/packages/faustwp-core/src/getTemplate.ts index d08ec3ca7..1c5850445 100644 --- a/packages/faustwp-core/src/getTemplate.ts +++ b/packages/faustwp-core/src/getTemplate.ts @@ -146,10 +146,23 @@ export function getPossibleTemplates(node: SeedNode) { return possibleTemplates; } +type DynamicComponent = { + render: { + preload: () => Promise<{ default: C }>; + displayName?: string; + }; +}; + +export function isDynamicComponent( + component: C | DynamicComponent, +): component is DynamicComponent { + return (component as DynamicComponent).render?.preload !== undefined; +} + export function getTemplate( seedNode: SeedNode | null | undefined, templates: { [key: string]: WordPressTemplate }, -): WordPressTemplate | null { +): WordPressTemplate | DynamicComponent | null { if (!seedNode) { return null; } diff --git a/packages/faustwp-core/src/getWordPressProps.tsx b/packages/faustwp-core/src/getWordPressProps.tsx index 6128ccaf6..de81b8fcf 100644 --- a/packages/faustwp-core/src/getWordPressProps.tsx +++ b/packages/faustwp-core/src/getWordPressProps.tsx @@ -6,7 +6,11 @@ import { GetServerSidePropsContext, GetStaticPropsContext } from 'next'; import { addApolloState, getApolloClient } from './client.js'; import { FaustTemplateProps } from './components/WordPressTemplate.js'; import { getConfig } from './config/index.js'; -import { getPossibleTemplates, getTemplate } from './getTemplate.js'; +import { + getPossibleTemplates, + getTemplate, + isDynamicComponent, +} from './getTemplate.js'; import { SEED_QUERY, SeedNode } from './queries/seedQuery.js'; import { debugLog, infoLog } from './utils/log.js'; import { hooks } from './wpHooks/index.js'; @@ -142,12 +146,19 @@ export async function getWordPressProps( getPossibleTemplates(seedNode), ); - const template = getTemplate(seedNode, templates); + let template = getTemplate(seedNode, templates); if (!template) { return createNotFound(ctx, revalidate); } + if (isDynamicComponent(template)) { + + const dynamicTemplate = await template.render.preload(); + + template = dynamicTemplate.default ?? dynamicTemplate; + } + if (template.query && template.queries) { throw new Error( '`Only either `Component.query` or `Component.queries` can be provided, but not both.', From 7d2e095cf5d137b45e61f49e92b91e504c14c123 Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Wed, 21 Jan 2026 15:21:27 -0800 Subject: [PATCH 2/7] fix: example image issue --- .../components/FeaturedImage/FeaturedImage.js | 105 +++++++++--------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/examples/next/faustwp-getting-started/components/FeaturedImage/FeaturedImage.js b/examples/next/faustwp-getting-started/components/FeaturedImage/FeaturedImage.js index 40a4a78cb..55721c734 100644 --- a/examples/next/faustwp-getting-started/components/FeaturedImage/FeaturedImage.js +++ b/examples/next/faustwp-getting-started/components/FeaturedImage/FeaturedImage.js @@ -1,58 +1,61 @@ import { gql } from '@apollo/client'; -import Image from 'next/image'; +import Image from 'next/legacy/image'; export default function FeaturedImage({ - image, - width, - height, - className, - priority, - layout, - ...props + image, + width, + height, + className, + priority, + layout, + ...props }) { - const src = image?.sourceUrl; - - if (!src) return null; - - const { altText = '', mediaDetails = {} } = image ?? {}; - - layout = layout ?? 'fill'; - - const dimensions = { - width: layout === 'fill' ? undefined : width ?? mediaDetails?.width, - height: layout === 'fill' ? undefined : height ?? mediaDetails?.height - }; - - if (layout !== 'fill' && (!dimensions.width || !dimensions.height)) return null; - - return ( -
- {altText} -
- ); + const src = image?.sourceUrl; + + if (!src) return null; + + const { altText = '', mediaDetails = {} } = image ?? {}; + + layout = layout ?? 'fill'; + + const dimensions = { + width: layout === 'fill' ? undefined : width ?? mediaDetails?.width, + height: layout === 'fill' ? undefined : height ?? mediaDetails?.height, + }; + + if (layout !== 'fill' && (!dimensions.width || !dimensions.height)) + return null; + + console.log(layout, mediaDetails, dimensions); + + return ( +
+ {altText} +
+ ); } FeaturedImage.fragments = { - entry: gql` - fragment FeaturedImageFragment on NodeWithFeaturedImage { - featuredImage { - node { - id - sourceUrl - altText - mediaDetails { - width - height - } - } - } - } - `, + entry: gql` + fragment FeaturedImageFragment on NodeWithFeaturedImage { + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + } + `, }; From e0a9c00741210540852e57428b1ccd2294ad35ee Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Wed, 21 Jan 2026 15:24:58 -0800 Subject: [PATCH 3/7] fix: add missing dynamic handling for queries and resolve ts errors. --- .../src/components/WordPressTemplate.tsx | 46 +++++++++++++++---- packages/faustwp-core/src/getTemplate.ts | 6 +++ .../faustwp-core/src/getWordPressProps.tsx | 6 +-- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/packages/faustwp-core/src/components/WordPressTemplate.tsx b/packages/faustwp-core/src/components/WordPressTemplate.tsx index 28fc7421d..6d5ed5c12 100644 --- a/packages/faustwp-core/src/components/WordPressTemplate.tsx +++ b/packages/faustwp-core/src/components/WordPressTemplate.tsx @@ -10,12 +10,17 @@ import React, { } from 'react'; import { getApolloAuthClient, getApolloClient } from '../client.js'; import { getConfig } from '../config/index.js'; -import { getTemplate } from '../getTemplate.js'; +import { + getTemplate, + isDynamicComponent, + loadDynamicComponent, +} from '../getTemplate.js'; import { useAuth } from '../hooks/useAuth.js'; import { SEED_QUERY, SeedNode } from '../queries/seedQuery.js'; import { FaustContext, FaustQueries } from '../store/FaustContext.js'; import { getQueryParam } from '../utils/convert.js'; import { isWordPressPreview } from '../utils/isWordPressPreview.js'; +import type { WordPressTemplate } from '../getWordPressProps.js'; export type FaustProps = { __SEED_NODE__?: SeedNode | null; @@ -38,6 +43,14 @@ export type FaustTemplateProps> = Props & { __TEMPLATE_VARIABLES__?: { [key: string]: any }; }; +function checkDuplicateQueryQueries(template: WordPressTemplate): void { + if (template.query && template.queries) { + throw new Error( + '`Only either `Component.query` or `Component.queries` can be provided, but not both.', + ); + } +} + export function WordPressTemplateInternal( props: WordPressTemplateProps & { seedNode: SeedNode; @@ -62,27 +75,29 @@ export function WordPressTemplateInternal( setLoading, ...wordpressTemplateProps } = props; - const template = getTemplate(seedNode, templates); + let template = getTemplate(seedNode, templates); const [data, setData] = useState(templateQueryDataProp); const { setQueries } = useContext(FaustContext) || {}; - if (template && template.queries && template.query) { - throw new Error( - '`Only either `Component.query` or `Component.queries` can be provided, but not both.', - ); - } - /** * Fetch the template's queries if defined. */ useEffect(() => { void (async () => { - const client = isPreview ? getApolloAuthClient() : getApolloClient(); if (!template) { return; } + if (isDynamicComponent(template)) { + template = await loadDynamicComponent(template); + } + + checkDuplicateQueryQueries(template); + + const client = isPreview ? getApolloAuthClient() : getApolloClient(); + + if (template.query) { return; } @@ -128,9 +143,20 @@ export function WordPressTemplateInternal( */ useEffect(() => { void (async () => { + + if(!template) { + return; + } + + if (isDynamicComponent(template)) { + template = await loadDynamicComponent(template); + } + + checkDuplicateQueryQueries(template); + const client = isPreview ? getApolloAuthClient() : getApolloClient(); - if (!template || !template?.query || template?.queries || !seedNode) { + if (!template.query || template.queries || !seedNode) { return; } diff --git a/packages/faustwp-core/src/getTemplate.ts b/packages/faustwp-core/src/getTemplate.ts index 1c5850445..ab70cf733 100644 --- a/packages/faustwp-core/src/getTemplate.ts +++ b/packages/faustwp-core/src/getTemplate.ts @@ -159,6 +159,12 @@ export function isDynamicComponent( return (component as DynamicComponent).render?.preload !== undefined; } +export function loadDynamicComponent( + component: DynamicComponent, +): Promise { + return component.render.preload().then((mod) => mod.default); +} + export function getTemplate( seedNode: SeedNode | null | undefined, templates: { [key: string]: WordPressTemplate }, diff --git a/packages/faustwp-core/src/getWordPressProps.tsx b/packages/faustwp-core/src/getWordPressProps.tsx index de81b8fcf..962a72969 100644 --- a/packages/faustwp-core/src/getWordPressProps.tsx +++ b/packages/faustwp-core/src/getWordPressProps.tsx @@ -10,6 +10,7 @@ import { getPossibleTemplates, getTemplate, isDynamicComponent, + loadDynamicComponent, } from './getTemplate.js'; import { SEED_QUERY, SeedNode } from './queries/seedQuery.js'; import { debugLog, infoLog } from './utils/log.js'; @@ -153,10 +154,7 @@ export async function getWordPressProps( } if (isDynamicComponent(template)) { - - const dynamicTemplate = await template.render.preload(); - - template = dynamicTemplate.default ?? dynamicTemplate; + template = await loadDynamicComponent(template); } if (template.query && template.queries) { From fc9b159499a1c1aa3b449a0f54718b1798137cdd Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Wed, 21 Jan 2026 15:32:43 -0800 Subject: [PATCH 4/7] fix: type errors in wordpress template --- .../src/components/WordPressTemplate.tsx | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/faustwp-core/src/components/WordPressTemplate.tsx b/packages/faustwp-core/src/components/WordPressTemplate.tsx index 6d5ed5c12..7d5a292c4 100644 --- a/packages/faustwp-core/src/components/WordPressTemplate.tsx +++ b/packages/faustwp-core/src/components/WordPressTemplate.tsx @@ -75,7 +75,7 @@ export function WordPressTemplateInternal( setLoading, ...wordpressTemplateProps } = props; - let template = getTemplate(seedNode, templates); + const unknownTemplate = getTemplate(seedNode, templates); const [data, setData] = useState(templateQueryDataProp); const { setQueries } = useContext(FaustContext) || {}; @@ -85,13 +85,13 @@ export function WordPressTemplateInternal( useEffect(() => { void (async () => { - if (!template) { + if (!unknownTemplate) { return; } - if (isDynamicComponent(template)) { - template = await loadDynamicComponent(template); - } + const template = isDynamicComponent(unknownTemplate) + ? await loadDynamicComponent(unknownTemplate) + : unknownTemplate; checkDuplicateQueryQueries(template); @@ -136,7 +136,7 @@ export function WordPressTemplateInternal( setLoading(false); })(); - }, [isAuthenticated, isPreview, seedNode, template, setQueries, setLoading]); + }, [isAuthenticated, isPreview, seedNode, unknownTemplate, setQueries, setLoading]); /** * Fetch the template's query if defined. @@ -144,13 +144,11 @@ export function WordPressTemplateInternal( useEffect(() => { void (async () => { - if(!template) { + if(!unknownTemplate) { return; } - if (isDynamicComponent(template)) { - template = await loadDynamicComponent(template); - } + const template = isDynamicComponent(unknownTemplate) ? await loadDynamicComponent(unknownTemplate) : unknownTemplate; checkDuplicateQueryQueries(template); @@ -179,14 +177,13 @@ export function WordPressTemplateInternal( setLoading(false); })(); - }, [data, template, seedNode, isPreview, isAuthenticated, setLoading]); + }, [data, unknownTemplate, seedNode, isPreview, isAuthenticated, setLoading]); - if (!template) { + if (!unknownTemplate) { return null; } - const Component = template as React.FC<{ [key: string]: any }>; - + const Component = unknownTemplate as React.FC<{ [key: string]: any }>; const newProps = { ...wordpressTemplateProps, __TEMPLATE_QUERY_DATA__: templateQueryDataProp, @@ -212,8 +209,8 @@ export function WordPressTemplate(props: WordPressTemplateProps) { const [seedNode, setSeedNode] = useState( seedNodeProp ?? null, ); - const template = getTemplate(seedNode, templates); - const [loading, setLoading] = useState(template === null); + const unknownTemplate = getTemplate(seedNode, templates); + const [loading, setLoading] = useState(unknownTemplate === null); const [isPreview, setIsPreview] = useState( templateQueryDataProp ? false : null, ); From fce978c6ad06c58ac94ab5545a72ebce31403cb9 Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Wed, 21 Jan 2026 15:34:40 -0800 Subject: [PATCH 5/7] fix: type errors in geWPProps --- packages/faustwp-core/src/getWordPressProps.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/faustwp-core/src/getWordPressProps.tsx b/packages/faustwp-core/src/getWordPressProps.tsx index 962a72969..153a2ff47 100644 --- a/packages/faustwp-core/src/getWordPressProps.tsx +++ b/packages/faustwp-core/src/getWordPressProps.tsx @@ -147,15 +147,15 @@ export async function getWordPressProps( getPossibleTemplates(seedNode), ); - let template = getTemplate(seedNode, templates); + const unknownTemplate = getTemplate(seedNode, templates); - if (!template) { + if (!unknownTemplate) { return createNotFound(ctx, revalidate); } - if (isDynamicComponent(template)) { - template = await loadDynamicComponent(template); - } + const template = isDynamicComponent(unknownTemplate) + ? await loadDynamicComponent(unknownTemplate) + : unknownTemplate; if (template.query && template.queries) { throw new Error( From 2e4356a64f5e913f0390bdd7ae88b09e8117301d Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Wed, 21 Jan 2026 15:36:43 -0800 Subject: [PATCH 6/7] formatting --- .../src/components/WordPressTemplate.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/faustwp-core/src/components/WordPressTemplate.tsx b/packages/faustwp-core/src/components/WordPressTemplate.tsx index 7d5a292c4..d52988b23 100644 --- a/packages/faustwp-core/src/components/WordPressTemplate.tsx +++ b/packages/faustwp-core/src/components/WordPressTemplate.tsx @@ -84,7 +84,6 @@ export function WordPressTemplateInternal( */ useEffect(() => { void (async () => { - if (!unknownTemplate) { return; } @@ -97,7 +96,6 @@ export function WordPressTemplateInternal( const client = isPreview ? getApolloAuthClient() : getApolloClient(); - if (template.query) { return; } @@ -136,19 +134,27 @@ export function WordPressTemplateInternal( setLoading(false); })(); - }, [isAuthenticated, isPreview, seedNode, unknownTemplate, setQueries, setLoading]); + }, [ + isAuthenticated, + isPreview, + seedNode, + unknownTemplate, + setQueries, + setLoading, + ]); /** * Fetch the template's query if defined. */ useEffect(() => { void (async () => { - - if(!unknownTemplate) { + if (!unknownTemplate) { return; } - const template = isDynamicComponent(unknownTemplate) ? await loadDynamicComponent(unknownTemplate) : unknownTemplate; + const template = isDynamicComponent(unknownTemplate) + ? await loadDynamicComponent(unknownTemplate) + : unknownTemplate; checkDuplicateQueryQueries(template); From d2a0ce03b2040c7e53eb914ae7e12aafe905f5a8 Mon Sep 17 00:00:00 2001 From: Alex Moon Date: Wed, 21 Jan 2026 15:38:59 -0800 Subject: [PATCH 7/7] fix: name overlap --- packages/faustwp-core/src/components/WordPressTemplate.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/faustwp-core/src/components/WordPressTemplate.tsx b/packages/faustwp-core/src/components/WordPressTemplate.tsx index d52988b23..12b83f2c3 100644 --- a/packages/faustwp-core/src/components/WordPressTemplate.tsx +++ b/packages/faustwp-core/src/components/WordPressTemplate.tsx @@ -20,7 +20,7 @@ import { SEED_QUERY, SeedNode } from '../queries/seedQuery.js'; import { FaustContext, FaustQueries } from '../store/FaustContext.js'; import { getQueryParam } from '../utils/convert.js'; import { isWordPressPreview } from '../utils/isWordPressPreview.js'; -import type { WordPressTemplate } from '../getWordPressProps.js'; +import type { WordPressTemplate as WordPressTemplateType } from '../getWordPressProps.js'; export type FaustProps = { __SEED_NODE__?: SeedNode | null; @@ -43,7 +43,7 @@ export type FaustTemplateProps> = Props & { __TEMPLATE_VARIABLES__?: { [key: string]: any }; }; -function checkDuplicateQueryQueries(template: WordPressTemplate): void { +function checkDuplicateQueryQueries(template: WordPressTemplateType): void { if (template.query && template.queries) { throw new Error( '`Only either `Component.query` or `Component.queries` can be provided, but not both.',