v1.4.0
CMS pages default to static generation with on-demand revalidation, and Payload gains a form builder with an email system and automatic upload optimization.
On this page
⚠️ Breaking & Behavioral ChangesLink to this section
Minimum Dependency VersionsLink to this section
@systhemaui/payload now requires Next.js 15.4.11 or higher on version 15 (or 16.2.0+ once it exits canary). The minimum PayloadCMS version is bumped to 3.79.0 across all @payloadcms/* peer dependencies.
Static Generation & Revalidation OverhaulLink to this section
CMS pages now default to static site generation (SSG) instead of server-side rendering. This is a significant performance improvement but requires changes to your project's route files.
The sitemap route switches from force-dynamic to hourly ISR. A new /sys/revalidate POST route with Bearer auth enables on-demand revalidation with wildcard support. The home page now also generates proper metadata via generatePageMetadata.
The revalidation endpoint requires the SYSTHEMA_API_SECRET environment variable to be set — it's used as the Bearer token for authentication.
Required project changesLink to this section
src/app/(site)/(systhema)/[...path]/page.tsx — Remove export const dynamic = 'auto' and add a long revalidate value:
- export const dynamic = 'auto'
export const revalidate = 2629746 // 1 monthsrc/app/(site)/(systhema)/page.tsx — Add revalidate, import generatePageMetadata, and export a generateMetadata function so the home page gets proper SEO metadata:
+ import type { Metadata } from 'next'
- import { RootPage } from '@systhemaui/payload/next'
+ import { RootPage, generatePageMetadata } from '@systhemaui/payload/next'
+ export const revalidate = 2629746 // 1 month
export default async function Page({ params }: { params: Promise<{ path: string }> }) {
const payload = await getPayload({ config: configPromise })
return <RootPage params={params} payloadInstance={Promise.resolve(payload)} />
}
+ export async function generateMetadata(): Promise<Metadata | null> {
+ return generatePageMetadata({
+ params: Promise.resolve({ path: undefined }),
+ payloadInstance: getPayload({ config: configPromise }),
+ })
+ }src/app/(site)/(systhema)/(sitemaps)/pages-sitemap.xml/route.ts — Replace force-dynamic with hourly ISR:
- export const dynamic = 'force-dynamic'
export const revalidate = 3600 // Revalidate every hoursrc/app/(site)/(systhema)/sys/[route]/route.ts — Add a POST handler for the new revalidation API:
+ export const POST = (
+ request: NextRequest,
+ { params }: { params: Promise<{ route: string }> },
+ ): Promise<Response> =>
+ systhemaApiRoutes(request, { params }, getPayload({ config: configPromise }))src/app/robots.ts — Disallow the /api/ route:
- disallow: ['/sys/', '/admin/'],
+ disallow: ['/api/', '/sys/', '/admin/'],✨ New Feature HighlightsLink to this section
Form Builder & Email SystemLink to this section
A full-featured form builder powered by @payloadcms/plugin-form-builder is now integrated into Systhema. Create forms directly in the admin panel with a drawer-based UX, unique field IDs, customizable labels, and multi-column layouts via <FormRow>. New field types include file upload with split-button design, date/datetime with validation presets, and hidden fields with IP/geo template variables. Forms can also be rendered standalone via the new <RenderForm> component.
Hidden fields support template variables like {{ip}}, {{geo}}, {{url}}, {{timestamp}}, URL query parameters via {{param:utm_source}}, and browser metadata. These are resolved automatically on form load and included in submissions. Existing form handler integrations may need to account for the new hidden field values.
All outgoing emails are centralized through a single processing pipeline with support for file attachments, Lexical rich text editing, inline CSS styles resolved from design tokens, and humanized wildcard field names. A built-in branded email template handles layout and dark mode out of the box, but can be fully replaced with a custom react-email template.
ConfigurationLink to this section
Forms are enabled by default in withSysthema(). To send form notification emails, a PayloadCMS email adapter must be configured in the base Payload config. This example uses @payloadcms/email-resend, but any PayloadCMS-compatible email adapter works (e.g. @payloadcms/email-nodemailer). If no email adapter is configured, forms still work but email features are automatically disabled.
// payload.config.ts
import { buildConfig } from 'payload'
import { withSysthema, type SysthemaPayloadPluginOptions } from '@systhemaui/payload'
import { resendAdapter } from '@payloadcms/email-resend'
const userSysthemaConfig: SysthemaPayloadPluginOptions = {
forms: {
enabled: true,
redirectRelationships: ['pages'],
fields: {
text: true, // text input with placeholder and max length
email: true, // email input with validation
number: true, // number input with min/max/step
textarea: true, // multi-line text with placeholder and max length
select: true, // dropdown select
radio: true, // radio button group
checkbox: true, // multi-option checkbox group
date: true, // date or datetime-local input with validation presets
file: true, // file upload with type/size restrictions
message: true, // instructional rich text content (not a form input)
country: false, // country selector (disabled by default)
state: false, // state/province selector (disabled by default)
},
formOverrides: {
// standard PayloadCMS collection overrides
},
formSubmissionOverrides: {
// standard PayloadCMS collection overrides
},
},
}
export default buildConfig(
withSysthema(
{
secret: process.env.PAYLOAD_SECRET || '',
db: yourDatabaseAdapter({ /* ... */ }),
email: resendAdapter({
defaultFromAddress: process.env.SYSTHEMA_EMAIL_FROM ?? 'Company <noreply@mail.example.com>',
defaultFromName: 'Company',
apiKey: process.env.RESEND_API_KEY || '',
}),
},
userSysthemaConfig,
),
)All fields support a width percentage for multi-column layouts via <FormRow>. The values shown above are the defaults — you only need to specify fields you want to change.
Environment variablesLink to this section
| Variable | Purpose | Example |
|---|---|---|
SYSTHEMA_EMAIL_FROM | Default "from" address for all emails | "Company <noreply@mail.example.com>" |
SYSTHEMA_EMAIL_REPLY_TO | Default reply-to address | support@example.com |
SYSTHEMA_EMAIL_ADMIN_ADDRESS | Fallback recipient when form has no addressee | admin@example.com |
RESEND_API_KEY | Resend API key (or your provider's equivalent) | re_xxxxxxxxxxxx |
NEXT_PUBLIC_SERVER_URL | Server URL for logo/attachment resolution | https://example.com |
Email settings can also be managed from the admin panel via the Emails global (from name, from address, reply-to, logo). The precedence is: form-level settings > Emails global > environment variables.
Custom email template with react-emailLink to this section
The built-in template works out of the box, but you can replace it entirely with a react-email template. The emailTemplate option receives the pre-processed HTML body (with inline styles already applied) and an options object with logoUrl and serverUrl.
Create your template components:
// src/emails/EmailLayout.tsx
import { Html, Head, Body, Container, Img } from '@react-email/components'
import { Tailwind } from '@react-email/tailwind'
import { getEmailTailwindConfig } from '@systhemaui/core'
export function EmailLayout({
children,
logoUrl,
}: {
children: React.ReactNode
logoUrl?: string
}) {
return (
<Html>
<Head />
<Tailwind config={getEmailTailwindConfig()}>
<Body>
<Container style={{ maxWidth: '512px' }}>
{logoUrl && <Img src={logoUrl} alt="Logo" height={48} />}
{children}
</Container>
</Body>
</Tailwind>
</Html>
)
}// src/emails/FormNotificationEmail.tsx
import { EmailLayout } from './EmailLayout'
export function FormNotificationEmail({
content,
logoUrl,
}: {
content: string
logoUrl?: string
serverUrl?: string
}) {
return (
<EmailLayout logoUrl={logoUrl}>
<div dangerouslySetInnerHTML={{ __html: content }} />
</EmailLayout>
)
}// src/emails/renderer.ts
import { render } from '@react-email/components'
import { FormNotificationEmail } from './FormNotificationEmail'
export async function renderEmailTemplate(
html: string,
options: { logoUrl?: string; serverUrl?: string },
): Promise<string> {
return render(
FormNotificationEmail({
content: html,
logoUrl: options.logoUrl,
serverUrl: options.serverUrl,
}),
)
}Then wire it up in your payload config:
// payload.config.ts
import { renderEmailTemplate } from './emails/renderer'
const userSysthemaConfig: SysthemaPayloadPluginOptions = {
emailTemplate: renderEmailTemplate,
}Upload OptimizationLink to this section
Uploaded images are now automatically optimized before storage. By default, images wider than 2560px are resized, JPEGs are compressed to 60% quality, and opaque PNGs (without actual transparency) are converted to JPEG. A 100MB file size limit is enforced both client-side (with UI feedback) and server-side.
All defaults can be customized via the uploads option:
const userSysthemaConfig: SysthemaPayloadPluginOptions = {
uploads: {
maxFileSize: 52428800, // 50MB
imageOptimization: {
maxWidth: 1920, // resize to max 1920px wide (default: 2560)
jpegQuality: 80, // JPEG quality 1-100 (default: 60)
convertPngToJpeg: false, // keep PNGs as-is (default: true)
pngQuality: 80, // PNG quality 1-100 (default: 60)
},
},
}Set imageOptimization: false to disable all processing entirely.
List of all changesLink to this section
🚀 FeaturesLink to this section
coreLink to this section
- feat(core): add email Tailwind config with typography and color primitives (44bbc16 (opens in new tab))
core,reactLink to this section
- feat(core,react): added width="" attribute to all form field components, so we can correctly render them inside a next to each other, and also added placeholder styles to the select input (3f7ff94 (opens in new tab))
core,react,payloadLink to this section
- feat(core,react,payload): add FileField component with split-button design (1206612 (opens in new tab))
react,nextLink to this section
- feat(react,next): add DateField component and set NumberField inputMode to decimal (baf2457 (opens in new tab))
payloadLink to this section
- !feat(payload): add hidden fields, keyField, and IP/geo template variables to forms (breaking) (ab0f60d (opens in new tab))
- feat(payload): add form builder integration with @payloadcms/plugin-form-builder (adc3f35 (opens in new tab))
- feat(payload): add RenderForm component for standalone form rendering (2ab9017 (opens in new tab))
- feat(payload): add file upload field to form builder (ff19d81 (opens in new tab))
- feat(payload): add configurable max file size to form file upload field (998d420 (opens in new tab))
- feat(payload): add configurable upload optimization and file size validation (7324fad (opens in new tab))
- feat(payload): improve form builder admin UX with drawers, icons, and global email settings (689b3f6 (opens in new tab))
- feat(payload): add date/datetime form field with validation and step presets (07f51e2 (opens in new tab))
- feat(payload): add branded email template, file attachments, and case-insensitive field resolution (3932159 (opens in new tab))
- feat(payload): improve form builder UX with clearer labels, unique IDs, and simplified options (367fa68 (opens in new tab))
- feat(payload): render message field with Lexical richtext, simplify options display, remove date min/max/step (d81f040 (opens in new tab))
- feat(payload): add programmatic payload type generation script (1c7a459 (opens in new tab))
- feat(payload): integrate type generation into build and dev scripts (4fd4266 (opens in new tab))
- feat(payload): add /sys/revalidate route with Bearer auth and wildcard support (d69c0ce (opens in new tab))
- feat(payload): hide collections from admin nav based on user roles (7c8a4e1 (opens in new tab))
- feat(payload): hide globals from admin nav for non-editor users and export hidden helpers (cdb2c13 (opens in new tab))
- feat(payload): add email lexical editor and inline email styles (998cbc1 (opens in new tab))
- feat(payload): resolve all typography properties in email inline styles (023e492 (opens in new tab))
- feat(payload): humanize field names in email wildcard output (b027a93 (opens in new tab))
templates/payloadLink to this section
- feat(templates/payload): add react-email template example with emailTemplate plugin option (9aa4e83 (opens in new tab))
- feat(payload-template): wire up Systhema email Tailwind config and auto-width logo (885f199 (opens in new tab))
♻️ RefactorsLink to this section
payloadLink to this section
- refactor(payload): remove date/datetime min, max, step types until custom implementation (c95089e (opens in new tab))
generalLink to this section
- refactor: gate email features behind adapter check and refine Emails global (2a4ef81 (opens in new tab))
🐛 Bug fixesLink to this section
coreLink to this section
- fix(core): flatten media-overlay CSS selectors and use pseudo-element for icon mask (a1cf89d (opens in new tab))
core,payloadLink to this section
- fix(core,payload): append px unit to bare numeric token values in email styles (39af1ea (opens in new tab))
reactLink to this section
- fix(react): disable Swiper loop when not enough slides for loop mode (c03c716 (opens in new tab))
- fix(react): stop passing required to individual checkboxes in CheckboxField (a4b7cf4 (opens in new tab))
react,nextLink to this section
- fix(react,next): suppress hydration warnings for config-dependent class names (15fcab1 (opens in new tab))
- fix(react,next,payload): added missing preload="auto" to <video> tags (2772883 (opens in new tab))
payloadLink to this section
- fix(payload): removed unnecessary margins and paddings from inline blocks that have a systhema style renderer in them (2c73d64 (opens in new tab))
- fix(payload): correct FormBlock width alignment detection and hide empty drawer (24f372a (opens in new tab))
- fix(payload): remove file size limit constraint on form file upload field (91d5e0b (opens in new tab))
- fix(payload): improve video block controls and parallax logic (316641a (opens in new tab))
- fix(payload): add playsInline to video elements with poster images (3a79728 (opens in new tab))
- fix(payload): make sure UploadFeature's jsx converter is also using the Image component from
@systhemaui/next(c40a5a6 (opens in new tab)) - fix(payload): coerce non-string defaultValues in buildInitialFormState (0ae021c (opens in new tab))
- fix(payload): correct private IP range check for 172.16-31.x.x in geo route (9eb90dd (opens in new tab))
- fix(payload): prevent redundant API calls in async hidden field resolution (05cd6a2 (opens in new tab))
- fix(payload): harden form-upload endpoint with server-side validation (f3ae591 (opens in new tab))
- fix(payload): replace lite-youtube embed with standard YouTube iframe (d028cd3 (opens in new tab))
- fix(payload): enforce 16/9 aspect ratio on YouTube iframe embeds (d4d0344 (opens in new tab))
- fix(payload): disallow api route in robots.txt (89424a9 (opens in new tab))
- fix(payload): revert admin.hidden access control changes (c89ee81 (opens in new tab))
- fix(payload): restrict global update access to editor role (b39532b (opens in new tab))
- fix(payload): enable SSG for CMS pages in catch-all route (94982eb (opens in new tab))
- fix(payload): replace force-dynamic with hourly ISR on sitemap route (08b6a4e (opens in new tab))
- fix(payload): fix queryPageByPath cache key and add home page metadata (059a138 (opens in new tab))
- fix(payload): use empty string check for homepage path in revalidateDelete (3265bdb (opens in new tab))
- fix(payload): Next.js version constraints in correct place of the package.json file (f68bccb (opens in new tab))
- fix(payload): default video and YouTube block aspect ratio to 16/9 (81f96a4 (opens in new tab))
- fix(payload): use object form for typescript.declare config option (8cc6ae2 (opens in new tab))
- fix(payload,next): add viewport meta, robots noindex for admin, and Safari iOS zoom prevention (7331470 (opens in new tab))
- fix(payload): fix type mismatches exposed by auto-generated payload types (33650c1 (opens in new tab))
- fix(payload): centralize email processing for all outgoing emails (8a0188b (opens in new tab))
- fix(payload): parse defaultFromAddress format and change email logo to upload field (e533bba (opens in new tab))
- fix(payload): fix form field ID generation, button borders, and form builder UX (e1d9151 (opens in new tab))
- fix(payload): update addClassNamesToElement import for richtext-lexical 3.79.0 (2f522f0 (opens in new tab))
- fix(payload): preserve custom ID field values on page reload (b4a2567 (opens in new tab))
- fix(payload): resolve key field lock checkbox path for nested fields (3fa20f7 (opens in new tab))
- fix(payload): use custom select placeholder and default to first option (166f8d8 (opens in new tab))
- fix(payload): simplify default email template to plain layout (1265fcf (opens in new tab))
- fix(payload): remove explicit colors from email templates for dark mode (b58c9ab (opens in new tab))
- fix(payload): fetch file content as buffer for email attachments (d5e6a1f (opens in new tab))
- fix(payload): use auto width for email logo and restrict upload to jpg/png (aa7daaa (opens in new tab))
templates/payloadLink to this section
- fix(templates/payload): use valid EditorName value in custom block config (5947098 (opens in new tab))
- fix(templates/payload): include optimized version of the @tailwindcss/postcss package instead of the default messed up one (1bdb2d2 (opens in new tab))
generalLink to this section
- fix: apply aspect ratio to video blocks with poster images (1da30a3 (opens in new tab))
- fix: remove useField from drawer component to fix form field creation crash (26717be (opens in new tab))
⚡ PerformanceLink to this section
payloadLink to this section
- perf(payload): optimize revalidation hooks and richText field processing (bfc861e (opens in new tab))
💄 StyleLink to this section
payloadLink to this section
- style(payload): format auto-generated payload types (3b6a756 (opens in new tab))
- style(payload): center email template container and remove side padding (6204f5f (opens in new tab))
📚 DocsLink to this section
payloadLink to this section
- docs(payload): update form types comment to reference auto-generation (7aab781 (opens in new tab))
🧹 ChoresLink to this section
depsLink to this section
- chore(deps): update dependencies and bump peer dependency ranges (51a5695 (opens in new tab))
- chore(deps): update dependencies across all packages and templates (f7dbe27 (opens in new tab))
- chore(deps): updated dependencies on all packages and templates to latest versions (cc6b55e (opens in new tab))
- chore(deps): updated next packages to safest 15.4.11 version, still waiting for 16.2.0 (034616d (opens in new tab))
payloadLink to this section
- chore(payload): sync auto-generated payload types (f5314c3 (opens in new tab))
- chore(payload): update Next.js version constraint in package.json for compatibility and security (1cb0520 (opens in new tab))
generalLink to this section
- chore: publish internal packages under internal dist-tag (86ce792 (opens in new tab))
- chore: renamed GITHUBTOKEN to have a SYSTHEMA prefix, so i won't make conflicts with other applications, like the
ghcli (5db8f5a (opens in new tab))