diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5faaa8cb21..f44e46a0dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@ Our versioning strategy is as follows:
* `[templates/nextjs]` `[sitecore-jss-nextjs]` Better error handling for component-level data fetching ([#1586](https://github.com/Sitecore/jss/pull/1586))
* `[sitecore-jss-react]` Fetch Data for FEaaS Components as part of Component SSR/SSG ([#1586](https://github.com/Sitecore/jss/pull/1590))
* `[sitecore-jss-dev-tools]` `[templates/nextjs]` `[templates/react]` Introduce "components" configuration for ComponentBuilder ([#1598](https://github.com/Sitecore/jss/pull/1598))
+* `[sitecore-jss-react]` `[sitecore-jss-nextjs]` Component level data fetching(SSR/SSG) for BYOC ([#1610](https://github.com/Sitecore/jss/pull/1610))
### 🧹 Chores
diff --git a/packages/sitecore-jss-nextjs/src/components/BYOCWrapper.tsx b/packages/sitecore-jss-nextjs/src/components/BYOCWrapper.tsx
new file mode 100644
index 0000000000..d297378b1a
--- /dev/null
+++ b/packages/sitecore-jss-nextjs/src/components/BYOCWrapper.tsx
@@ -0,0 +1,45 @@
+import {
+ BYOCWrapper,
+ BYOCComponentParams,
+ fetchBYOCComponentServerProps,
+} from '@sitecore-jss/sitecore-jss-react';
+import {
+ GetStaticComponentProps,
+ GetServerSideComponentProps,
+} from '../sharedTypes/component-props';
+import { constants } from '@sitecore-jss/sitecore-jss';
+
+/**
+ * This is a repackaged version of the React BYOCWrapper component with support for
+ * server rendering in Next.js (using component-level data-fetching feature of JSS).
+ */
+
+/**
+ * Will be called during SSG
+ * @param {ComponentRendering} rendering
+ * @returns {GetStaticPropsContext} context
+ */
+export const getStaticProps: GetStaticComponentProps = async (rendering) => {
+ if (process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED) {
+ return null;
+ }
+ const params: BYOCComponentParams = rendering.params || {};
+ const result = await fetchBYOCComponentServerProps(params);
+ return result;
+};
+
+/**
+ * Will be called during SSR
+ * @param {ComponentRendering} rendering
+ * @returns {GetStaticPropsContext} context
+ */
+export const getServerSideProps: GetServerSideComponentProps = async (rendering) => {
+ if (process.env.JSS_MODE === constants.JSS_MODE.DISCONNECTED) {
+ return null;
+ }
+ const params: BYOCComponentParams = rendering.params || {};
+ const result = await fetchBYOCComponentServerProps(params);
+ return result;
+};
+
+export default BYOCWrapper;
diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts
index b34d35bf37..44a5ac7ef1 100644
--- a/packages/sitecore-jss-nextjs/src/index.ts
+++ b/packages/sitecore-jss-nextjs/src/index.ts
@@ -153,7 +153,9 @@ export { Placeholder } from './components/Placeholder';
export { EditingComponentPlaceholder } from './components/EditingComponentPlaceholder';
export { NextImage } from './components/NextImage';
import * as FEaaSWrapper from './components/FEaaSWrapper';
+import * as BYOCWrapper from './components/BYOCWrapper';
export { FEaaSWrapper };
+export { BYOCWrapper };
export { ComponentBuilder, ComponentBuilderConfig } from './ComponentBuilder';
@@ -177,7 +179,6 @@ export {
BYOCComponent,
BYOCComponentProps,
getFEAASLibraryStylesheetLinks,
- BYOCWrapper,
File,
FileField,
RichTextField,
diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx
index 791a6e9570..d80beee5f4 100644
--- a/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx
+++ b/packages/sitecore-jss-react/src/components/BYOCComponent.test.tsx
@@ -12,6 +12,7 @@ describe('BYOCComponent', () => {
ComponentName: 'Foo',
ComponentProps: JSON.stringify({ prop1: 'value1' }),
},
+ fetchedData: {},
};
const Foo = () =>
Test
;
FEAAS.External.registerComponent(Foo, {
@@ -29,6 +30,34 @@ describe('BYOCComponent', () => {
expect(fooComponent.prop('data-external-id')).to.equal('Foo');
expect(fooComponent.find('#foo-content')).to.have.length(1);
});
+
+ it('should render when props are prefetched', () => {
+ const fetchedData = {
+ prop1: 'prefetched_value1',
+ };
+ const mockProps = {
+ params: {
+ ComponentName: 'Foo',
+ ComponentProps: JSON.stringify({ prop1: 'value1' }),
+ },
+ fetchedData,
+ };
+ const Foo = () => Test
;
+ FEAAS.External.registerComponent(Foo, {
+ name: 'Foo',
+ properties: {
+ prop1: {
+ type: 'string',
+ },
+ },
+ });
+ const wrapper = mount();
+ const fooComponent = wrapper.find('feaas-external');
+ expect(fooComponent).to.have.lengthOf(1);
+ expect(fooComponent.prop('prop1')).to.equal('prefetched_value1');
+ expect(fooComponent.prop('data-external-id')).to.equal('Foo');
+ expect(fooComponent.find('#foo-content')).to.have.length(1);
+ });
});
describe('Error handling', () => {
@@ -38,6 +67,7 @@ describe('Error handling', () => {
ComponentName: 'ExampleComponent',
ComponentProps: 'invalid-json',
},
+ fetchedData: {},
};
const wrapper = mount();
const errorComponent = wrapper.find('DefaultErrorComponent');
@@ -52,6 +82,7 @@ describe('Error handling', () => {
ComponentName: 'ExampleComponent',
ComponentProps: 'invalid-json',
},
+ fetchedData: {},
};
const wrapper = mount();
@@ -96,7 +127,11 @@ describe('Error handling', () => {
});
it('should render missing component frame when component is not registered', () => {
- const props = { params: { ComponentName: 'NonExistentComponent' }, components: {} };
+ const props = {
+ params: { ComponentName: 'NonExistentComponent' },
+ components: {},
+ fetchedData: {},
+ };
const wrapper = mount();
@@ -115,6 +150,7 @@ describe('Error handling', () => {
missingComponentComponent: missingComponent,
params: { ComponentName: 'NonExistentComponent' },
components: {},
+ fetchedData: {},
};
const wrapper = mount();
diff --git a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx
index afcdf7268b..056d34ed43 100644
--- a/packages/sitecore-jss-react/src/components/BYOCComponent.tsx
+++ b/packages/sitecore-jss-react/src/components/BYOCComponent.tsx
@@ -6,6 +6,16 @@ import * as FEAAS from '@sitecore-feaas/clientside/react';
export const BYOC_COMPONENT_RENDERING_NAME = 'BYOCComponent';
+/**
+ * FEaaS props for server rendering.
+ */
+type BYOCServerProps = {
+ /**
+ * Fetched data from server component props for server rendering, based on rendering params.
+ */
+ fetchedData?: FEAAS.DataScopes;
+};
+
/**
* Data from rendering params on Sitecore's BYOC rendering
*/
@@ -13,7 +23,7 @@ export type BYOCComponentParams = {
/**
* Name of the component to render
*/
- ComponentName: string;
+ ComponentName?: string;
/**
* JSON props to pass into rendered component
*/
@@ -28,7 +38,7 @@ export type BYOCComponentParams = {
/**
* Props for BYOCComponent. Includes components list to load external components from.
*/
-export type BYOCComponentProps = {
+export type BYOCComponentClientProps = {
/**
* rendering params
*/
@@ -50,6 +60,8 @@ export type BYOCComponentProps = {
| React.FC;
};
+export type BYOCComponentProps = BYOCComponentClientProps & BYOCServerProps;
+
type ErrorComponentProps = {
[prop: string]: unknown;
error?: Error;
@@ -117,26 +129,30 @@ export class BYOCComponent extends React.Component {
);
- let componentProps: { [key: string]: unknown } = undefined;
-
- if (props.params?.ComponentProps) {
- try {
- componentProps = JSON.parse(props.params.ComponentProps) ?? {};
- } catch (e) {
- console.warn(
- `Parsing props for ${componentName} component from rendering params failed. Error: ${e}`
- );
- return this.props.errorComponent ? (
-
- ) : (
-
- );
- }
- }
+ const ErrorComponent = this.props.errorComponent;
+
+ const isNull = Object.keys(props.fetchedData).length === 0;
+
+ let componentProps: { [key: string]: any } = isNull ? null : props.fetchedData;
+
if (!componentProps) {
- componentProps = props.fields ? getDataFromFields(props.fields) : {};
+ if (props.params?.ComponentProps) {
+ try {
+ componentProps = JSON.parse(props.params.ComponentProps) ?? {};
+ } catch (e) {
+ console.error(
+ `Parsing props for ${componentName} component from rendering params failed. Error: ${e}`
+ );
+ return ErrorComponent ? (
+
+ ) : (
+
+ );
+ }
+ } else {
+ componentProps = props.fields ? getDataFromFields(props.fields) : {};
+ }
}
-
return (
{
);
}
}
+
+/**
+ * Fetches server component props required for server rendering, based on rendering params.
+ * @param {BYOCComponentParams} params component params
+ */
+export async function fetchBYOCComponentServerProps(
+ params: BYOCComponentParams
+): Promise {
+ const fetchDataOptions: FEAAS.DataOptions = params.ComponentProps
+ ? JSON.parse(params.ComponentProps)
+ : {};
+
+ const fetchedData: FEAAS.DataScopes = await FEAAS.DataSettings.fetch(fetchDataOptions || {});
+
+ return {
+ fetchedData,
+ };
+}
diff --git a/packages/sitecore-jss-react/src/index.ts b/packages/sitecore-jss-react/src/index.ts
index e3d3dbaffe..ccdeb5fb1e 100644
--- a/packages/sitecore-jss-react/src/index.ts
+++ b/packages/sitecore-jss-react/src/index.ts
@@ -64,7 +64,12 @@ export {
fetchFEaaSComponentServerProps,
} from './components/FEaaSComponent';
export { FEaaSWrapper } from './components/FEaaSWrapper';
-export { BYOCComponent, BYOCComponentParams, BYOCComponentProps } from './components/BYOCComponent';
+export {
+ BYOCComponent,
+ BYOCComponentParams,
+ BYOCComponentProps,
+ fetchBYOCComponentServerProps,
+} from './components/BYOCComponent';
export { BYOCWrapper } from './components/BYOCWrapper';
export { Link, LinkField, LinkFieldValue, LinkProps, LinkPropTypes } from './components/Link';
export { File, FileField } from './components/File';