Migrating from v6 to v7
First, execute npx @comet/upgrade@latest v7 in the root of your project.
It automatically installs the new versions of all @comet libraries, runs an ESLint autofix and handles some of the necessary renames.
Changes handled by @comet/upgrade
- Disabling GraphQL field suggestions
- Importing the types of
@comet/admin-themeinvendors.d.ts - Replacing the Roboto font with Roboto Flex
API
Remove unnecessary dependencies
The following dependencies used to be peer dependencies of Comet. They are no longer required. You can remove them if you don't use them in your project:
@aws-sdk/client-s3@azure/storage-blobpg-error-constants
Upgrade @mikro-orm/core, @mikro-orm/migrations, and @mikro-orm/postgresql
The minimum supported version for these packages is now v5.8.4.
Provide strategyName in createStaticCredentialsBasicStrategy
Make sure to use a meaningful strategy name as this name can be used to identify the user when using this strategy more than once. Do not forget to add the strategy to the App Guard.
createStaticCredentialsBasicStrategy({
password: "xxxxx",
+ strategyName: "system-user",
}),
{
provide: APP_GUARD,
- useClass: createCometAuthGuard(["static-credentials-basic", "..."]),
+ useClass: createCometAuthGuard(["system-user", "..."]),
};
UserPermissionsModule.forRootAsync({
useFactory: (...) => ({
+ systemUsers: ["system-user"],
...
}),
...
}),
Remove language field from User
// static-users.ts
export const staticUsers = {
admin: {
id: "3b09cc12-c7e6-4d16-b858-40a822f2c548",
name: "Admin",
email: "admin@customer.com",
- language: "en",
},
// ...
} satisfies Record<string, User>;
Remove @PublicApi() and rename @DisableGlobalGuard()
Replace all usages of @PublicApi() and @DisableGlobalGuard() with @DisableCometGuards().
Use this occasion to check if all operations decorated with this decorator should actually be public and don't return any confidential data.
- @PublicApi()
+ @DisableCometGuards()
- @DisableGlobalGuard()
+ @DisableCometGuards()
Support dependency injection in BlockData#transformToPlain
Remove dynamic registration of
BlocksModule:// In api/src/app.module.ts
- BlocksModule.forRoot({
- imports: [PagesModule],
- useFactory: (pageTreeService: PageTreeService, filesService: FilesService, imagesService: ImagesService) => {
- return {
- transformerDependencies: {
- pageTreeService,
- filesService,
- imagesService,
- },
- };
- },
- inject: [PageTreeService, FilesService, ImagesService],
- }),
+ BlocksModule,Pass
moduleReftoBlocksTransformerMiddlewareFactoryinstead ofdependencies// In api/src/app.module.ts
GraphQLModule.forRootAsync<ApolloDriverConfig>({
...
- useFactory: (dependencies: Record<string, unknown>) => ({
+ useFactory: (moduleRef: ModuleRef) => ({
...
buildSchemaOptions: {
- fieldMiddleware: [BlocksTransformerMiddlewareFactory.create(dependencies)],
+ fieldMiddleware: [BlocksTransformerMiddlewareFactory.create(moduleRef)],
},
}),
- inject: [BLOCKS_MODULE_TRANSFORMER_DEPENDENCIES],
+ inject: [ModuleRef],
}),Remove
dependenciesfromBlockData#transformToPlaincalls:class NewsLinkBlockData {
...
- transformToPlain(dependencies: TransformDependencies, context: BlockContext)
+ transformToPlain(context: BlockContext)
}Convert existing
BlockData#transformToPlaincalls to new technique. This is only necessary if you have blocks that load additional data intransformToPlain:Before
// news-link.block.ts
class NewsLinkBlockData extends BlockData {
@BlockField({ nullable: true })
id?: string;
// Poor man's dependency injection using dependencies object
transformToPlain({ newsRepository }: { newsRepository: EntityRepository<News> }) {
if (!this.id) {
return {};
}
const news = await newsRepository.findOneOrFail(this..id);
return {
news: {
id: news.id,
slug: news.slug,
},
};
}
}After
// news-link.block.ts
class NewsLinkBlockData extends BlockData {
@BlockField({ nullable: true })
id?: string;
transformToPlain() {
// Return service that does the transformation
return NewsLinkBlockTransformerService;
}
}
type TransformResponse = {
news?: {
id: string;
slug: string;
};
};
// news-link-block-transformer.service.ts
@Injectable()
class NewsLinkBlockTransformerService
implements BlockTransformerServiceInterface<NewsLinkBlockData, TransformResponse>
{
// Use dependency injection here
constructor(@InjectRepository(News) private readonly repository: EntityRepository<News>) {}
async transformToPlain(block: NewsLinkBlockData, context: BlockContext) {
if (!block.id) {
return {};
}
const news = await this.repository.findOneOrFail(block.id);
return {
news: {
id: news.id,
slug: news.slug,
},
};
}
}
Remove CDN config from DAM
// app.module.ts
DamModule.register({
damConfig: {
- filesBaseUrl: `${config.apiUrl}/dam/files`,
- imagesBaseUrl: `${config.apiUrl}/dam/images`,
+ apiUrl: config.apiUrl,
// ...
}
})
How to migrate (only required if CDN is used):
Remove the following env vars from the API
//.env
- DAM_CDN_ENABLED=
- DAM_CDN_DOMAIN=
- DAM_CDN_ORIGIN_HEADER=
- DAM_DISABLE_CDN_ORIGIN_HEADER_CHECK=false
If you want to enable the origin check:
Set the following env vars for the API
// .env
+ CDN_ORIGIN_CHECK_SECRET="Use value from DAM_CDN_ORIGIN_HEADER to avoid downtime"environment-variables.ts
// environment-variables.ts
+ @IsString()
+ @ValidateIf(() => process.env.NODE_ENV === "production")
+ CDN_ORIGIN_CHECK_SECRET: string;// config.ts
+ cdn: {
+ originCheckSecret: envVars.CDN_ORIGIN_CHECK_SECRET,
+ },Add CdnGuard
// main.ts
+ // if CDN is enabled, make sure all traffic is either coming from the CDN or internal sources
+ if (config.cdn.originCheckSecret) {
+ app.useGlobalGuards(new CdnGuard({ headerName: "x-cdn-origin-check", headerValue: config.cdn.originCheckSecret }));
+ }Adjust
site/server.js
// site/server.js
- const cdnEnabled = process.env.CDN_ENABLED === "true";
- const disableCdnOriginHeaderCheck = process.env.DISABLE_CDN_ORIGIN_HEADER_CHECK === "true";
- const cdnOriginHeader = process.env.CDN_ORIGIN_HEADER;
+ const cdnOriginCheckSecret = process.env.CDN_ORIGIN_CHECK_SECRET;
// ...
- if (cdnEnabled && !disableCdnOriginHeaderCheck) {
- const incomingCdnOriginHeader = req.headers["x-cdn-origin-check"];
- if (cdnOriginHeader !== incomingCdnOriginHeader) {
+ if (cdnOriginCheckSecret) {
+ if (req.headers["x-cdn-origin-check"] !== cdnOriginCheckSecret) {
- DNS changes might be required.
api.example.comshould point to CDN, CDN should point to internal API domain
API Generator: Remove support for visible boolean, use status enum instead
Replace the visible boolean field with a status enum field.
Recommended enum values are (depending on the use case):
- Published/Unpublished
- Published/Unpublished/Archived
- Published/Unpublished/Deleted
- Active/Deleted
- Active/Archived
The update{Entity}Visibility mutation is also removed.
Use the generic update{Entity} mutation instead.
API Generator: Remove generated services
The API Generator no longer generates the service with getFindCondition.
Remove all previously generated services from the module definitions.
For example:
import { NewsResolver } from "./generated/news.resolver";
- import { NewsService } from "./generated/news.service";
@Module({
imports: [MikroOrmModule.forFeature([News])],
providers: [
NewsResolver,
- NewsService,
],
})
export class NewsModule {}
Rename public uploads
The PublicUploadModule was renamed to FileUploadsModule.
The public uploads module was unintentionally added to the Starter. If you don't use the feature in your application, remove the module instead.
This requires the following changes:
In
comet-config.jsonrenamepublicUploadstofileUploads.{
- "publicUploads": {
+ "fileUploads": {
"maxFileSize": 15
}
}In
app.module.tschange the import fromPublicUploadModuletoFileUploadsModule.- PublicUploadModule.register({
+ FileUploadsModule.register({
- maxFileSize: config.publicUploads.maxFileSize,
- directory: `${config.blob.storageDirectoryPrefix}-public-uploads`,
+ maxFileSize: config.fileUploads.maxFileSize,
+ directory: `${config.blob.storageDirectoryPrefix}-file-uploads`,
})Change all usages of the
PublicUploadentity toFileUpload.Change all usages of the
PublicUploadsServicetoFileUploadsService.In the site or the Admin change the upload URL from
/public-upload/files/uploadto/file-uploads/upload.
Make file uploads upload endpoint public
The /file-uploads/upload endpoint now requires the fileUploads permission by default.
If necessary (e.g., a file upload in the site), make the endpoint public:
FileUploadsModule.register({
/* ... */,
+ upload: {
+ public: true,
+ },
}),
Remove usages of download or FileUploadService
Use createFileUploadInputFromUrl instead:
- import { FileUploadService } from "@comet/cms-api";
+ import { createFileUploadInputFromUrl } from "@comet/cms-api";
@Injectable()
export class SvgImageFileFixtureService {
constructor(
private readonly filesService: FilesService,
- private readonly fileUploadService: FileUploadService,
) {}
async generateImage(scope: DamScope): Promise<FileInterface> {
- const file = await this.fileUploadService.createFileUploadInputFromUrl(
+ const file = await createFileUploadInputFromUrl(
path.resolve(`./src/db/fixtures/generators/images/comet-logo-claim.svg`),
);
}
}
Rename defaultDamAcceptedMimetypes to damDefaultAcceptedMimetypes
- defaultDamAcceptedMimetypes
+ damDefaultAcceptedMimetypes
Replace additionalMimeTypes and overrideAcceptedMimeTypes in DamModule#damConfig with acceptedMimeTypes
Instead of using overrideAcceptedMimeTypes, you can now override mime types like this:
DamModule.register({
damConfig: {
acceptedMimeTypes: ["something-mimetype"],
},
});
Instead of using additionalMimeTypes, you can add additional mime types like this:
DamModule.register({
damConfig: {
acceptedMimeTypes: [...damDefaultAcceptedMimetypes, "something-else"],
},
});
Rename DateFilter to DateTimeFilter
- Change import
- import { DateFilter } from "@comet/cms-api";
+ import { DateTimeFilter } from "@comet/cms-api";
- Re-run API Generator.
Import YouTubeVideoBlock from @comet/cms-api package
- import { YouTubeVideoBlock } from "@comet/blocks-api";
+ import { YouTubeVideoBlock } from "@comet/cms-api";
Replace graphql-type-json with graphql-scalars
Install graphql-scalars:
npm install graphql-scalarsUninstall graphql-type-json:
npm uninstall graphql-type-jsonUpdate imports:
- import { GraphQLJSONObject } from "graphql-type-json";
+ import { GraphQLJSONObject } from "graphql-scalars";
Change arguments of filtersToMikroOrmQuery
The second argument (applyFilter callback) was moved into an options object:
- filtersToMikroOrmQuery(f, (acc, filterValue, filterKey) => {}),
+ filtersToMikroOrmQuery(f, { applyFilter: (acc, filterValue, filterKey) => {} }),
Admin
Remove axios dependency
axios used to be a peer dependency of Comet.
It's no longer required, so you can remove axios if you don't use it in your project.
Remove the @mui/styles package
The legacy @mui/styles package was removed in favor of @mui/material/styles.
You must remove @mui/styles from your project too:
//package.json
- "@mui/styles": "^5.8.6",
This has multiple implications:
- Comet Admin components can now be styled using MUI's
sxprop - Individual elements (slots) of a component can now be styled using the
slotPropsandsxprops - The
$syntax in the theme'sstyleOverridesis no longer supported, see: MUI Docs:
const theme = createCometTheme({
components: {
CometAdminMyComponent: {
styleOverrides: {
- root: {
- "&$hasShadow": {
- boxShadow: "2px 2px 5px 0 rgba(0, 0, 0, 0.25)",
- },
- "& $header": {
- backgroundColor: "lime",
- },
- },
+ hasShadow: {
+ boxShadow: "2px 2px 5px 0 rgba(0, 0, 0, 0.25)",
+ },
+ header: {
+ backgroundColor: "lime",
+ },
},
},
},
});
- Overriding a component's styles using
withStylesis no longer supported. Use thesxandslotPropsprops instead:
-import { withStyles } from "@mui/styles";
-
-const StyledMyComponent = withStyles({
- root: {
- backgroundColor: "lime",
- },
- header: {
- backgroundColor: "fuchsia",
- },
-})(MyComponent);
-
-// ...
-
-<StyledMyComponent title="Hello World" />;
+<MyComponent
+ title="Hello World"
+ sx={{
+ backgroundColor: "lime",
+ }}
+ slotProps={{
+ header: {
+ sx: {
+ backgroundColor: "fuchsia",
+ },
+ },
+ }}
+/>
- The module augmentation for the
DefaultThemetype from@mui/styles/defaultThemeis no longer needed and needs to be removed from the admins theme file, usually located inadmin/src/theme.ts:
-declare module "@mui/styles/defaultTheme" {
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
- export interface DefaultTheme extends Theme {}
-}
- Some props and class keys of certain components were removed or renamed
Expand for details
Alert: Remove themessageclass key (use.MuiAlert-messageinstead)AppHeaderButton: Remove class keysdisabledandfocusVisible(use the:disabledor:focusselectors instead)AppHeaderButton: Rename theinnerclass key tocontentAppHeaderDropdown: Remove thepopoverPaperclass keyAppHeaderDropdown: ReplacepopoverPropswithslotProps.popoverAppHeaderDropdown: Rename thepopoverRootclass key topopoverClearInputButton: Remove thedisabledclass key (use the:disabledselector instead)CopyToClipboardButton: Removecomponentsprop. UsecopyIconandsuccessIconinsteadCopyToClipboardButton: ReplacecomponentPropswithslotPropsFieldSet: ReplacecomponentsPropswithslotPropsFinalFormSelect: Remove theendAdornmentpropInputWithPopper: ReplacecomponentsPropswithslotPropsMenu: ReplacetemporaryDrawerProps,permanentDrawerProps,temporaryDrawerPaperPropsandpermanentDrawerPaperPropsprops (useslotPropsinstead)Menu: Renamepermanentclass key topermanentDrawerandtemporaryclass key totemporaryDrawerMenuCollapsibleItem: Remove thelistItemclass keyMenuCollapsibleItem: ReplaceopenedIconandclosedIconprops withiconMappingMenuItem: No longer supports props ofListItem. Instead supports the props ofListItemButton
Rearrange components in App.tsx
ErrorDialogHandlermust be beneathMuiThemeProviderandIntlProviderCurrentUserProvidermust be beneath or parallel toErrorDialogHandler
The resulting order should look something like this:
// ...
<IntlProvider locale="en" messages={getMessages()}>
// ...
<MuiThemeProvider theme={theme}>
// ...
<ErrorDialogHandler />
<CurrentUserProvider>
// ...
Rename previewUrl prop of SiteConfig
The previewUrl prop of SiteConfig was renamed to blockPreviewBaseUrl.
- previewUrl = `${siteConfig.previewUrl}/page`;
+ previewUrl = `${siteConfig.blockPreviewBaseUrl}/page`;
Change the structure of MasterMenuData
You must add an
iconto all top level menu itemsYou must add a
typeto all items. There are four types available:route{
+ type: "route",
primary: <FormattedMessage id="menu.dashboard" defaultMessage="Dashboard" />,
icon: <DashboardIcon />,
route: {
path: "/dashboard",
component: Dashboard,
},
},externalLink{
+ type: "externalLink",
primary: <FormattedMessage id="menu.cometDxp" defaultMessage="COMET DXP" />,
icon: <Snips />,
href: "https://comet-dxp.com",
},collapsible{
+ type: "collapsible",
primary: <FormattedMessage id="menu.structuredContent" defaultMessage="Structured Content" />,
icon: <Data />,
- submenu: [
+ items: [
// ...
],
},group(new){
+ type: "group",
+ title: <FormattedMessage id="menu.products" defaultMessage="Products" />,
+ items: [
+ // ...
+ ]
},
New Content Scope Picker
The content scope controls were changed to display all available combinations in a single select. This requires a few changes:
Change the
valuesprop ofContentScopeProviderto an array:Before
const values: ContentScopeValues<ContentScope> = {
domain: [
{ label: "Main", value: "main" },
{ label: "Secondary", value: "secondary" },
],
language: [
{ label: "English", value: "en" },
{ label: "German", value: "de" },
],
};Now
const values: ContentScopeValues<ContentScope> = [
{
domain: { label: "Main", value: "main" },
language: { label: "English", value: "en" },
},
{
domain: { label: "Main", value: "main" },
language: { label: "German", value: "de" },
},
{
domain: { label: "Secondary", value: "secondary" },
language: { label: "English", value: "en" },
},
];The
configprop ofContentScopeControlshas been removed. You can use the propssearchable,groupBy, andiconinstead. You may also remove the convenience wrapper defined in the application as it doesn't offer a real benefit anymore:- import { ContentScopeControls as ContentScopeControlsLibrary } from "@comet/cms-admin";
- export const ContentScopeControls: React.FC = () => {
- return <ContentScopeControlsLibrary<ContentScope> config={controlsConfig} />;
- };
+ import { ContentScopeControls } from "@comet/cms-admin";
New Toolbar
The Toolbar was reworked. Now there are three Toolbar components:
ToolbarStackToolbarDataGridToolbar
Following steps are necessary to correctly use the new Toolbar:
If your project has a custom
ContentScopeIndicator, remove it- admin/src/common/ContentScopeIndicator.tsxInstead, the
ContentScopeIndicatorexported by@comet/cms-adminshould be used.Grid: Use the
DataGridToolbarinDataGridsExample:
// NewsGrid.tsx
function NewsToolbar(): React.ReactElement {
// ...
return (
- <Toolbar>
+ <DataGridToolbar>
// ...
- </Toolbar>
+ </DataGridToolbar>
);
}
// ...
return (
<MainContent>
<DataGrid
// ...
components={{
Toolbar: NewsToolbar,
}}
/>
</MainContent>
);Page: Add a
StackToolbarto allStackPages containing aDataGrid:Example:
// NewsPage.tsx
export default function NewsPage(): JSX.Element {
const intl = useIntl();
return (
<Stack topLevelTitle={intl.formatMessage({ id: "news.news", defaultMessage: "News" })}>
<StackSwitch initialPage="grid">
<StackPage name="grid">
+ <StackToolbar scopeIndicator={<ContentScopeIndicator />} />
<NewsGrid />
</StackPage>
// ...
</StackSwitch>
</Stack>
);
}Page: Correctly configure the
ContentScopeIndicatorThere are three cases:
The entity uses the normal
ContentScopeDo nothing. The
ContentScopeIndicatoruses the scope provided byuseContentScope()by default.The entity has a custom
ScopePass the custom scope:
<ContentScopeIndicator scope={customScope} />The entity has no scope
Mark the page as global:
<ContentScopeIndicator global />
Form: Remove the
EditPageLayoutExample:
// NewsForm.tsx
function NewsForm({ id, mode }: NewsFormProps): JSX.Element {
// ...
return (
- <EditPageLayout>
+ <>
// ...
- </EditPageLayout>
+ </>
);
}Form: Add a
ContentScopeIndicatorto theToolbarConfigure the
ContentScopeIndicatorthe same way as the one in the page (see step 3).Example:
// NewsForm.tsx
function NewsForm({ id, mode }: NewsFormProps): JSX.Element {
// ...
return (
<>
// ...
- <Toolbar>
+ <Toolbar scopeIndicator={<ContentScopeIndicator />}>
// ...
</Toolbar>
// ...
</>
);
}
Remove EditPageLayout
You can completely remove EditPageLayout from your application.
Instead, use MainContent to wrap all your page content except the Toolbar.
If needed, wrap MainContent and Toolbar in a fragment.
Example:
- <EditPageLayout>
+ <>
<Toolbar>
// ...
</Toolbar>
- <div>
+ <MainContent>
// ...
- </div>
+ </MainContent>
- </EditPageLayout>
+ </>
Replace additionalMimeTypes and overrideAcceptedMimeTypes in DamConfigProvider with acceptedMimeTypes
Instead of using overrideAcceptedMimeTypes, you can now override mime types like this:
<DamConfigProvider
value={{
acceptedMimeTypes: ["something-mimetype"],
}}
>
{/* ... */}
</DamConfigProvider>
Instead of using additionalMimeTypes, you can add additional mime types like this:
<DamConfigProvider
value={{
acceptedMimeTypes: [...damDefaultAcceptedMimetypes, "something-else"],
}}
>
{/* ... */}
</DamConfigProvider>
Note: The accepted mime types must be identical to the ones passed to DamModule#damConfig in the API
Import YouTubeVideoBlock from @comet/cms-admin package
- import { YouTubeVideoBlock } from "@comet/blocks-admin";
+ import { YouTubeVideoBlock } from "@comet/cms-admin";
Remove aspectRatio from YouTubeVideoBlock
Previously, the YouTubeVideoBlock had a built-in aspect ratio select.
This proved to be too inflexible and was removed.
Instead, the aspect ratio should be defined in the application, e.g. in a surrounding MediaBlock.
Remove SplitButton and FinalFormSaveSplitButton
We decided to retire the SplitButton pattern.
Therefore, SplitButton and FinalFormSaveSplitButton are deprecated and should be removed from the project.
Use a regular SaveButton or FinalFormSaveButton instead.
@comet/admin-theme
Chip theme rework
If you use MUI's Chip anywhere in your project, check if the styling still looks as intended.
Typography theme rework
All variants of Typography were reworked.
Check if the styling still looks as intended in your application.
Colors
Colors in all palettes were changed. The most notable changes are
- The grey palette (neutrals) was completely reworked. Almost all color values changed
- The secondary palette is now grey instead of green
Check if the styling still looks as intended in your application.
@comet/admin-color-picker
Prop renames and removals
Expand for details
ColorPicker: ReplacecomponentsPropswithslotPropsColorPicker: Remove theclearableprop. The clear button will be shown automatically for optional fields
@comet/admin-date-time
Change the value type
The value returned by DatePicker and DateRangePicker is now a string (previously it was a Date).
The code that handles values from these components needs to be adjusted. This may include how the values are stored in or sent to the database.
Required Admin Changes:
- const [date, setDate] = useState<Date | undefined>(new Date("2024-03-10"));
+ const [date, setDate] = useState<string | undefined>("2024-03-10");
return <DatePicker value={date} onChange={setDate} />;
const [dateRange, setDateRange] = useState<DateRange | undefined>({
- start: new Date("2024-03-10"),
- end: new Date("2024-03-16"),
+ start: "2024-03-10",
+ end: "2024-03-16",
});
return <DateRangePicker value={dateRange} onChange={setDateRange} />;
Prop renames and removals
Expand for details
DatePicker:- Replace the
componentsPropsprop withslotProps - Remove the
DatePickerComponentsPropstype - Remove the
clearableprop. The clear button will be shown automatically for all optional fields.
- Replace the
DateRangePicker:- Replace the
componentsPropsprop withslotProps - Remove the
DateRangePickerComponentsPropstype - Rename the
calendarclass-key todateRange - Remove the
clearableprop. The clear button will be shown automatically for all optional fields.
- Replace the
DateTimePicker:- Replace the
componentsPropsprop withslotProps - Remove the
DateTimePickerComponentsPropstype - Replace the
formControlclass-key with two separate class-keys:dateFormControlandtimeFormControl - Remove the
clearableprop. The clear button will be shown automatically for all optional fields.
- Replace the
TimePicker:- Remove the
clearableprop. The clear button will be shown automatically for all optional fields.
- Remove the
TimeRangePicker:- Replace the
componentsPropsprop withslotProps - Remove the
TimeRangePickerComponentsPropsandTimeRangePickerIndividualPickerPropstypes - Replace the
formControlclass-key with two separate class-keys:startFormControlandendFormControl - Replace the
timePickerclass-key with two separate class-keys:startTimePickerandendTimePicker - Remove the
clearableprop. The clear button will be shown automatically for all optional fields.
- Replace the
Site
Major dependency upgrades
You must upgrade
- Next.js to v14 (Migration Guides: 12 -> 13, 13 -> 14)
- React to v18 (Migration Guide: 17 -> 18)
- Styled Components to v6 (Migration Guide: 5 -> 6)
Make sure to upgrade to Next 14.2.0 or later.
Enable optimizePackageImports for @comet/cms-site in next.config.js:
const nextConfig = {
/* ... */
+ experimental: {
+ optimizePackageImports: ["@comet/cms-site"],
+ },
};
module.exports = withBundleAnalyzer(nextConfig);
Add a custom InternalLinkBlock
The InternalLinkBlock provided by @comet/cms-site is deprecated.
Instead, implement your own InternalLinkBlock.
This is needed for more flexibility, e.g., support for internationalized routing.
Add legacyBehavior to all link block usages
All link blocks in @comet/cms-site now render a child <a> tag by default to align with the new behavior of the Next Link component, which is used by InternalLinkBlock.
For existing projects, add the legacyBehavior prop to all library link block usages to use the old behavior, where the <a> tag is defined in the application. For example:
const supportedBlocks: SupportedBlocks = {
internal: ({ children, title, ...props }) => (
<InternalLinkBlock
data={props}
title={title}
+ legacyBehavior
>
{children}
</InternalLinkBlock>
),
external: ({ children, title, ...props }) => (
<ExternalLinkBlock
data={props}
title={title}
+ legacyBehavior
>
{children}
</ExternalLinkBlock>
),
/* Other link blocks */
};
export const LinkBlock = withPreview(
({ data, children }: LinkBlockProps) => {
return (
<OneOfBlock data={data} supportedBlocks={supportedBlocks}>
{children}
</OneOfBlock>
);
},
{ label: "Link" },
);
New projects shouldn't use the legacy behavior. Instead, add support to pass the className prop through to the LinkBlock an its child blocks. See this PR for an example.
Add aspectRatio to PixelImageBlock, Image and YouTubeVideoBlock
Previously, there was a default aspect ratio of 16x9.
This has repeatedly led to incorrectly displayed images.
Now aspectRatio is required and must be added to PixelImageBlock, Image and YouTubeVideoBlock.
Consider which aspect ratio should be used.
Example:
<PixelImageBlock
data={teaser}
layout="fill"
+ aspectRatio="16x9"
/>
Remove layout prop from PixelImageBlock
Remove the layout prop from the block as it can lead to errors with the default implementation (layout="responsive" is not compatible with the new fill prop).
layout={"responsive" | "inherit"}can safely be removed<PixelImageBlock
data={block.props}
aspectRatio={aspectRatio}
- layout={"responsive"} // line is marked as deprecated, but "responsive" must be removed
{...imageProps}
/>layout={"fill"}can be replaced withfill={true}<PixelImageBlock
data={block.props}
aspectRatio={aspectRatio}
- layout={"fill"}
+ fill
{...imageProps}
/>
Notes:
The PixelImageBlock is usually wrapped in a DamImageBlock in the application. The layout prop should be removed from it as well.
You can use the newly added fill prop of the next/image component by embedding the PixelImageBlock in a parent element that assigns the position style. See the docs for more information.
Switch to Next.js Preview Mode
Requires following changes to site:
Import useRouter from next/router (not exported from @comet/cms-site anymore)
- import { useRouter } from "@comet/cms-site";
+ import { useRouter } from "next/router";
Import Link from next/link (not exported from @comet/cms-site anymore)
- import { Link } from "@comet/cms-site";
+ import Link from "next/link";
Remove the preview pages (pages in src/pages/preview/ directory which call createGetUniversalProps with preview parameters).
rm -rf src/pages/preview/
Remove createGetUniversalProps from the "normal" pages:
- export function createGetUniversalProps({
- includeInvisibleBlocks = false,
- includeInvisiblePages = false,
- previewDamUrls = false,
- }: CreateGetUniversalPropsOptions = {}) {
- /* ... */
- }
Instead, implement getStaticProps (Preview Mode will automatically switch to SSR). Use previewData from context to configure the GraphQL Client:
+ import { ParsedUrlQuery } from "querystring";
+ import { SitePreviewParams } from "@comet/cms-site";
export const getStaticProps: GetStaticProps<
PageProps,
+ ParsedUrlQuery,
+ SitePreviewParams
> = async (
context,
) => {
+ const { scope, previewData } = context.previewData ?? {
+ scope: { domain, language: context.locale ?? defaultLanguage },
+ previewData: undefined,
+ };
const client = createGraphQLClient({
+ includeInvisiblePages: context.preview,
+ includeInvisibleBlocks: previewData?.includeInvisible,
+ previewDamUrls: context.preview,
});
/* ... */
};
Add the SitePreviewProvider to App (typically in src/pages/_app.page.tsx):
function CustomApp({ Component, pageProps }: AppProps) {
const router = useRouter();
return (
<>
{/* ... */}
- <Component {...pageProps} />
+ {router.isPreview ? (
+ <SitePreviewProvider>
+ <Component {...pageProps} />
+ </SitePreviewProvider>
+ ) : (
+ <Component {...pageProps} />
+ )}
</>
);
}
Add an API Route to enable the preview mode. Must be the same as in siteConfig.sitePreviewApiUrl (default: ${siteConfig.url}/api/site-preview):
// In pages/api/site-preview.page.ts
import { legacyPagesRouterSitePreviewApiHandler } from "@comet/cms-site";
import createGraphQLClient from "@src/util/createGraphQLClient";
import { NextApiHandler } from "next";
const SitePreviewApiHandler: NextApiHandler = async (req, res) => {
await legacyPagesRouterSitePreviewApiHandler(req, res, createGraphQLClient());
};
export default SitePreviewApiHandler;
Implement previewImage for YouTubeVideoBlock and DamVideoBlock
YouTubeVideoBlock and DamVideoBlock now support a preview image.
If you are not using the YouTubeVideoBlock and DamVideoBlock provided by @comet/cms-site, you should
- either switch to the
@comet/cms-siteimplementation - or implement the preview image in your project
Otherwise, the admin interface will confuse users.
ESLint
Ban icon imports from @mui/icons-material
Icons used in Comet DXP applications should match the Comet CI.
Use icons from @comet/admin-icons instead.
react/jsx-no-useless-fragment
Unnecessary fragments in JSX are now banned.
@typescript-eslint/prefer-enum-initializers
It's now mandatory to initialize enums:
enum ExampleEnum {
- One,
- Two,
+ One = "One",
+ Two = "Two",
}