diff --git a/docusaurus/docs/cms/features/preview.md b/docusaurus/docs/cms/features/preview.md
index 99dff7725e..2da484d34c 100644
--- a/docusaurus/docs/cms/features/preview.md
+++ b/docusaurus/docs/cms/features/preview.md
@@ -293,80 +293,164 @@ On the Strapi side, [the `allowedOrigins` configuration parameter](#allowed-orig
This requires the front-end application to have its own header directive, the CSP `frame-ancestors` directive. Setting this directive up depends on how your website is built. For instance, setting this up in Next.js requires a middleware configuration (see [Next.js docs](https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy)).
-#### 6. [Front end] Detect changes in Strapi and refresh the front-end {#6-refresh-frontend}
+#### 6. [Front end] Adapt data fetching for draft content {#6-fetch-draft-content}
-Strapi emits a `strapiUpdate` message to inform the front end that data has changed.
+Once the preview system is set up, you need to adapt your data fetching logic to handle draft content appropriately. This involves the following steps:
+
+1. Create or adapt your data fetching utility to check if draft mode is enabled
+2. Update your API calls to include the draft status parameter when appropriate
+
+The following, taken from the Strapi demo application, is an example of how to implement draft-aware data fetching in your Next.js front-end application:
+
+```typescript {8-18}
+import { draftMode } from "next/headers";
+import qs from "qs";
+
+export default async function fetchContentType(
+ contentType: string,
+ params: Record = {}
+): Promise {
+ // Check if Next.js draft mode is enabled
+ const { isEnabled: isDraftMode } = await draftMode();
+
+ try {
+ const queryParams = { ...params };
+ // Add status=draft parameter when draft mode is enabled
+ if (isDraftMode) {
+ queryParams.status = "draft";
+ }
+
+ const url = `${baseURL}/${contentType}?${qs.stringify(queryParams)}`;
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch data from Strapi (url=${url}, status=${response.status})`
+ );
+ }
+ return await response.json();
+ } catch (error) {
+ console.error("Error fetching content:", error);
+ throw error;
+ }
+}
+```
+
+This utility method can then be used in your page components to fetch either draft or published content based on the preview state:
+
+```typescript
+// In your page component:
+const pageData = await fetchContentType('api::page.page', {
+ // Your other query parameters
+});
+```
+
+### Live Preview implementation
+
+After setting up the basic Preview feature, you can enhance the experience by implementing Live Preview.
+
+#### Window messages
-To track this, within your front-end application, add an event listener to listen to events posted through [the `postMessage()` API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) on the `window` object. The listener needs to filter through messages and react only to Strapi-initiated messages, then refresh the iframe content.
+Live Preview creates a more interactive experience by communicating between the admin and your frontend. It relies on events posted through [the `postMessage()` API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) on the `window` object.
+
+You need to add an event listener in your application. It should be present on all pages, ideally in a layout component that wraps your entire application. The listener needs to filter through messages and react only to Strapi-initiated messages.
+
+There are 2 messages to listen to:
+
+- `strapiUpdate`: sent by Strapi when a content update has been saved to the database. It's an opportunity to fetch the updated version of the content and refresh the preview. With Next.js, the recommended way to refresh the iframe content is with .
+- `previewScript`: sent by Strapi to give you a script that powers the Live Preview functionality. This script should be injected into the page's `
` tag. It handles highlighting editable areas in the preview and sending messages back to Strapi when an area is double-clicked for editing.
+
+In order to receive the `previewScript` message, you need to let Strapi know that your frontend is ready to receive it. This is done by posting a `previewReady` message to the parent window.
+
+When putting it all together, a component ready to be added to your global layout could look like:
-With Next.js, the recommended way to refresh the iframe content is with .
-
+
+
+```jsx title="next/app/path/to/your/front/end/logic.jsx"
+'use client';
-```tsx title="next/app/path/to/your/front/end/logic.jsx" {6-17}
-export default function MyClientComponent({...props}) {
+export default function LivePreview() {
// …
const router = useRouter();
useEffect(() => {
const handleMessage = async (message) => {
- if (
- // Filters events emitted through the postMessage() API
- message.origin === process.env.NEXT_PUBLIC_API_URL &&
- message.data.type === "strapiUpdate"
- ) { // Recommended way to refresh with Next.js
+ const { origin, data } = message;
+
+ if (origin !== process.env.NEXT_PUBLIC_API_URL) {
+ return;
+ }
+
+ if (data.type === 'strapiUpdate') {
router.refresh();
+ } else if (data.type === 'strapiScript') {
+ const script = window.document.createElement('script');
+ script.textContent = data.payload.script;
+ window.document.head.appendChild(script);
}
};
// Add the event listener
- window.addEventListener("message", handleMessage);
+ window.addEventListener('message', handleMessage);
+
+ // Let Strapi know we're ready to receive the script
+ window.parent?.postMessage({ type: 'previewReady' }, '*');
- // Cleanup the event listener on unmount
+ // Remove the event listener on unmount
return () => {
- window.removeEventListener("message", handleMessage);
+ window.removeEventListener('message', handleMessage);
};
}, [router]);
- // ...
+ return null;
}
```
-
-
-```tsx title="next/app/path/to/your/front/end/logic.tsx" {6-17}
-export default function MyClientComponent({
- //…
+
+
+```tsx title="next/app/path/to/your/front/end/logic.tsx"
+'use client';
+
+export default function LivePreview() {
+ // …
const router = useRouter();
useEffect(() => {
const handleMessage = async (message: MessageEvent) => {
- if (
- // Filters events emitted through the postMessage() API
- message.origin === process.env.NEXT_PUBLIC_API_URL &&
- message.data.type === "strapiUpdate"
- ) { // Recommended way to refresh with Next.js
+ const { origin, data } = message;
+
+ if (origin !== process.env.NEXT_PUBLIC_API_URL) {
+ return;
+ }
+
+ if (data.type === 'strapiUpdate') {
router.refresh();
+ } else if (data.type === 'strapiScript') {
+ const script = window.document.createElement('script');
+ script.textContent = data.payload.script;
+ window.document.head.appendChild(script);
}
};
// Add the event listener
- window.addEventListener("message", handleMessage);
+ window.addEventListener('message', handleMessage);
+
+ // Let Strapi know we're ready to receive the script
+ window.parent?.postMessage({ type: 'previewReady' }, '*');
- // Cleanup the event listener on unmount
+ // Remove the event listener on unmount
return () => {
- window.removeEventListener("message", handleMessage);
+ window.removeEventListener('message', handleMessage);
};
}, [router]);
- // …
-})
+ return null;
+}
```
-
@@ -377,16 +461,15 @@ In Next.js, [cache persistence](https://nextjs.org/docs/app/building-your-applic
-#### [Front end] Next steps
+#### Content source maps
-Once the preview system is set up, you need to adapt your data fetching logic to handle draft content appropriately. This involves the following steps:
+Live Preview is able to identify the parts of your frontend that correspond to fields in Strapi. This is done through content source maps, which are metadata encoded as hidden characters in your string-based content (e.g., text fields). It uses the library to encode and decode this metadata.
-1. Create or adapt your data fetching utility to check if draft mode is enabled
-2. Update your API calls to include the draft status parameter when appropriate
+Metadatas will only be added in your Content API responses when the `strapi-encode-source-maps` header is set to `true`. You can set this header in your data fetching utility. Make sure to only pass the header when you detect that your site is rendered in a preview context.
-The following, taken from the Strapi demo application, is an example of how to implement draft-aware data fetching in your Next.js front-end application:
+For a Next.js application, you may use the `draftMode()` method from `next/headers` to detect if draft mode is enabled, and set the header accordingly in all your API calls:
-```typescript {8-18}
+```typescript {20-23}
import { draftMode } from "next/headers";
import qs from "qs";
@@ -395,7 +478,7 @@ export default async function fetchContentType(
params: Record = {}
): Promise {
// Check if Next.js draft mode is enabled
- const { isEnabled: isDraftMode } = draftMode();
+ const { isEnabled: isDraftMode } = await draftMode();
try {
const queryParams = { ...params };
@@ -405,7 +488,12 @@ export default async function fetchContentType(
}
const url = `${baseURL}/${contentType}?${qs.stringify(queryParams)}`;
- const response = await fetch(url);
+ const response = await fetch(url, {
+ headers: {
+ // Enable content source maps in preview mode
+ "strapi-encode-source-maps": isDraftMode ? "true" : "false",
+ },
+ });
if (!response.ok) {
throw new Error(
`Failed to fetch data from Strapi (url=${url}, status=${response.status})`
@@ -419,15 +507,6 @@ export default async function fetchContentType(
}
```
-This utility method can then be used in your page components to fetch either draft or published content based on the preview state:
-
-```typescript
-// In your page component:
-const pageData = await fetchContentType('api::page.page', {
- // Your other query parameters
-});
-```
-
## Usage
**Path to use the feature:** Content Manager, edit view of your content type
@@ -435,7 +514,7 @@ const pageData = await fetchContentType('api::page.page', {
:::strapi Preview vs. Live Preview
Based on your CMS plan, your experience with Preview will be different:
- With the Free plan, Preview will be full screen only.
-- With the and plans, you get access to Live Preview. With Live Preview, you can see the Preview alongside the Edit view of the Content Manager, allowing you to edit your content and previewing it simultaneously.
+- With the and plans, you get access to an enhanced experience called Live Preview. With Live Preview, you can see the Preview alongside the Edit view of the Content Manager, and you can also edit the content directly within the preview itself by double-clicking on any content.
:::
Once the Preview feature is properly set up, an **Open preview** button is visible on the right side of the [Content Manager's edit view](/cms/features/content-manager#overview). Clicking it will display the preview of your content as it will appear in your front-end application, but directly within Strapi's the admin panel.
@@ -444,25 +523,45 @@ Once the Preview feature is properly set up, an **Open preview** button is visib
Once the Preview is open, you can:
- click the close button in the upper left corner to go back to the Edit View of the Content Manager,
+- switch between the Desktop and Mobile preview using the dropdown above the previewed content,
- switch between previewing the draft and the published version (if [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type),
- and click the link icon in the upper right corner to copy the preview link. Depending on the preview tab you are currently viewing, this will either copy the link to the preview of the draft or the published version.
-Additionally, with Live Preview, you can:
-- with and plans, expand the side panel by clicking on the button to make the Preview full screen,
-- and, with the plan, use buttons at the top right of the editor to define the assignee and stage for the [Review Workflows feature](/cms/features/review-workflows) if enabled.
-
:::note
In the Edit view of the Content Manager, the Open preview button will be disabled if you have unsaved changes. Save your latest changes and you should be able to preview content again.
:::
-:::tip
-Switch between Desktop and Mobile preview mode using the dropdown at the top to check how the page is displayed on different viewports.
-:::
\ No newline at end of file
+### Live Preview
+
+
+Live Preview is the enhanced Preview experience available with Strapi’s paid CMS plans.
+
+With Live Preview, in addition to what’s included in the Free plan, you can:
+
+* Use the Side Editor to view both the entry’s Edit view in the Content Manager and the front-end preview side by side. You can also switch between full-screen and side-by-side preview using the and buttons.
+* Double-click any content in the preview pane to edit it in place. This opens a popover that syncs the front-end content with the corresponding field in Strapi.
+
+
+
+
+:::caution Experimental feature
+This feature is currently experimental. Feel free to share or with the Strapi team.
+
+The current version of Live Preview comes with the following limitations:
+* Blocks fields are not detected, and changing them in the Side Editor won’t be reflected in the preview. Clicking on Save after updates should however still work.
+* Media assets and fields in dynamic zones are not handled.
+:::
diff --git a/docusaurus/package.json b/docusaurus/package.json
index 4f90201456..4391dd2c80 100644
--- a/docusaurus/package.json
+++ b/docusaurus/package.json
@@ -68,5 +68,6 @@
},
"engines": {
"node": ">=18.0"
- }
+ },
+ "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
}
diff --git a/docusaurus/src/components/Badge.js b/docusaurus/src/components/Badge.js
index 36dbe2b580..f2f5fe5b2e 100644
--- a/docusaurus/src/components/Badge.js
+++ b/docusaurus/src/components/Badge.js
@@ -14,6 +14,7 @@ export default function Badge({
version,
tooltip,
inline = false,
+ noTooltip = false,
...rest
}) {
const variantNormalized = variant.toLowerCase().replace(/\W/g, '');
@@ -28,7 +29,8 @@ export default function Badge({
(feature && `badge--featureflag`),
((variant === "Updated" || variant === "New") && `badge--content`),
(inline && 'badge--inline'),
- className
+ className,
+ (noTooltip && 'badge--no-tooltip')
)}
{...rest}
>
@@ -43,15 +45,14 @@ export default function Badge({
/>
{variant}
- {tooltip}
+ {!noTooltip && tooltip && {tooltip}}
>
) : (
<>
- {variant}
- {tooltip}
+ {variant}
+ {!noTooltip && tooltip && {tooltip}}
>
- )
- }
+ )}
>
) : (
@@ -62,7 +63,7 @@ export default function Badge({
/>
)}
{variant}
- {tooltip}
+ {!noTooltip && tooltip && {tooltip}}
)}
{children}
diff --git a/docusaurus/src/scss/badge.scss b/docusaurus/src/scss/badge.scss
index 360318dedb..13e193e432 100644
--- a/docusaurus/src/scss/badge.scss
+++ b/docusaurus/src/scss/badge.scss
@@ -544,8 +544,9 @@ h2 .badge {
top: -1px;
margin-left: 0.25rem;
margin-bottom: 0;
- vertical-align: middle;
+ vertical-align: middle !important;
display: inline-block;
+ height: 26px;
// Smaller size for inline usage
--ifm-badge-padding-horizontal: 4px;
@@ -565,6 +566,11 @@ h2 .badge {
left: 50%;
margin-left: calc(-1 * var(--custom-badge-tooltip-width) / 2);
}
+
+ .badge__link {
+ position: relative;
+ top: 2px;
+ }
&:hover .badge__tooltip {
visibility: visible;
diff --git a/docusaurus/src/scss/tabs.scss b/docusaurus/src/scss/tabs.scss
index dd50266a46..7737a99e34 100644
--- a/docusaurus/src/scss/tabs.scss
+++ b/docusaurus/src/scss/tabs.scss
@@ -25,7 +25,7 @@
padding-bottom: 18px !important;
font-weight: 600;
- &:hover {
+ &:not(.tabs__item--active):hover {
background-color: transparent;
color: var(--strapi-primary-600);
}
diff --git a/docusaurus/static/img/assets/content-manager/previewing-content-live.gif b/docusaurus/static/img/assets/content-manager/previewing-content-live.gif
new file mode 100644
index 0000000000..eb8cb7f39d
Binary files /dev/null and b/docusaurus/static/img/assets/content-manager/previewing-content-live.gif differ
diff --git a/docusaurus/static/img/assets/content-manager/previewing-content3.gif b/docusaurus/static/img/assets/content-manager/previewing-content3.gif
new file mode 100644
index 0000000000..d04bca53a9
Binary files /dev/null and b/docusaurus/static/img/assets/content-manager/previewing-content3.gif differ
diff --git a/docusaurus/static/llms-full.txt b/docusaurus/static/llms-full.txt
index d611ee61d6..0a224469a7 100644
--- a/docusaurus/static/llms-full.txt
+++ b/docusaurus/static/llms-full.txt
@@ -8346,6 +8346,10 @@ With the Preview feature, you can preview your front end application directly fr
+##### Live Preview specificities
+
+
+
Caching in Next.js:
@@ -8402,6 +8406,53 @@ const pageData = await fetchContentType('api::page.page', {
});
```
+### Live Preview implementation
+
+After setting up the basic Preview feature, you can enhance the experience by implementing Live Preview.
+
+#### Window messages
+
+Live Preview creates a more interactive experience by communicating between the admin and your frontend. It relies on events posted through [the `postMessage()` API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) on the `window` object.
+
+You need to add an event listener in your application. It should be present on all pages, ideally in a layout component that wraps your entire application. The listener needs to filter through messages and react only to Strapi-initiated messages.
+
+There are two messages to listen to:
+
+- `strapiUpdate`: sent by Strapi when a content update has been saved to the database. It's an opportunity to fetch the updated version of the content and refresh the preview. With Next.js, the recommended way to refresh the iframe content is with .
+- `previewScript`: sent by Strapi to give you a script that powers the live preview functionality. This script should be injected into the page's `` tag. It handles highlighting editable areas in the preview and sending messages back to Strapi when an area is double-clicked for editing.
+
+In order to receive the `previewScript` message, you need to let Strapi know that your frontend is ready to receive it. This is done by posting a `previewReady` message to the parent window.
+
+// TODO snippet
+
+#### Content source maps
+
+Live Preview is able to identify the parts of your frontend that correspond to fields in Strapi. This is done through content source maps, which are metadata encoded as hidden characters in your string-based content (e.g., text fields). It uses the library to encode and decode this metadata.
+
+Metadatas will only be added in your Content API responses when the `strapi-encode-source-maps` header is set to `true`. You can set this header in your data fetching utility. Make sure to only pass the header when you detect that your site is rendered in a preview context.
+
+For a Next.js application, you may use the `draftMode()` method from `next/headers` to detect if draft mode is enabled, and set the header accordingly in all your API calls:
+
+```typescript {5-12}
+
+ const headers = new Headers(options.headers);
+ if (draftMode().isEnabled) {
+ headers.set('strapi-encode-source-maps', 'true');
+ }
+
+ const response = await fetch(`/api/content/${contentType}`, {
+ ...options,
+ headers,
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch content');
+ }
+
+ return response.json();
+}
+```
+
## Usage
**Path to use the feature:** Content Manager, edit view of your content type
@@ -8409,7 +8460,7 @@ const pageData = await fetchContentType('api::page.page', {
:::strapi Preview vs. Live Preview
Based on your CMS plan, your experience with Preview will be different:
- With the Free plan, Preview will be full screen only.
-- With the and plans, you get access to Live Preview. With Live Preview, you can see the Preview alongside the Edit view of the Content Manager, allowing you to edit your content and previewing it simultaneously.
+- With the and plans, you get access to an enhanced experience called Live Preview. With Live Preview, you can see the Preview alongside the Edit view of the Content Manager, and you can also edit the content directly within the preview itself by double-clicking on any content.
:::
Once the Preview feature is properly set up, an **Open preview** button is visible on the right side of the [Content Manager's edit view](/cms/features/content-manager#overview). Clicking it will display the preview of your content as it will appear in your front-end application, but directly within Strapi's the admin panel.
@@ -8419,19 +8470,29 @@ Once the Preview feature is properly set up, an **Open preview** button is visib
Once the Preview is open, you can:
- click the close button in the upper left corner to go back to the Edit View of the Content Manager,
+- switch between the Desktop and Mobile preview using the dropdown above the previewed content,
- switch between previewing the draft and the published version (if [Draft & Publish](/cms/features/draft-and-publish) is enabled for the content-type),
- and click the link icon in the upper right corner to copy the preview link. Depending on the preview tab you are currently viewing, this will either copy the link to the preview of the draft or the published version.
-Additionally, with Live Preview, you can:
-- with and plans, expand the side panel by clicking on the button to make the Preview full screen,
-- and, with the plan, use buttons at the top right of the editor to define the assignee and stage for the [Review Workflows feature](/cms/features/review-workflows) if enabled.
-
:::note
In the Edit view of the Content Manager, the Open preview button will be disabled if you have unsaved changes. Save your latest changes and you should be able to preview content again.
:::
-:::tip
-Switch between Desktop and Mobile preview mode using the dropdown at the top to check how the page is displayed on different viewports.
+### Live Preview
+
+Live Preview is the enhanced Preview experience available with Strapi’s paid CMS plans.
+
+With Live Preview, in addition to what’s included in the Free plan, you can:
+
+* Use the Side Editor to view both the entry’s Edit view in the Content Manager and the front-end preview side by side. You can also switch between full-screen and side-by-side preview using the and buttons.
+* Double-click any content in the preview pane to edit it in place. This opens a popover that syncs the front-end content with the corresponding field in Strapi.
+
+:::caution Experimental feature
+This feature is currently experimental. Feel free to share or with the Strapi team.
+
+The current version of Live Preview comes with the following limitations:
+* Blocks fields are not detected, and changing them in the Side Editor won’t be reflected in the preview. Clicking on Save after updates should however still work.
+* Media assets and fields in dynamic zones are not handled.
:::