- Notifications
You must be signed in to change notification settings - Fork 1.2k
Static preview #2295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Static preview #2295
Changes from 7 commits
Commits
Show all changes
19 commits Select commit Hold shift + click to select a range
3f46844 Initiate pages in User Guide and Dev Docs
pwizla 8d27413 Add the Dev Docs part
pwizla d5bcbb3 Fix callout cross-linking dev docs and user guide
pwizla a9a4a7b Add user guide draft
pwizla 8f48dca Improve Dev Docs intro.
pwizla 58ea5ac Improve Dev Docs
pwizla 5f46ec4 Slightly improve User Guide
pwizla 8b9f365 Add beta badge to Dev Docs
pwizla 36c178c Add beta badge to User Guide
pwizla defb489 Fix: Use arraw for allowed origins
pwizla d6d945a Update Open preview disabled/save changes behavior
pwizla 6e40974 Fix Dev Docs code (status)
pwizla 994fb3c Merge remote-tracking branch 'origin/repo/static-preview' into repo/s…
pwizla 173bd93 Add front-end fetching to implementation guide
pwizla 73fceba Improve URL handler
pwizla 44e0269 Update docusaurus/docs/dev-docs/preview.md
pwizla 3b1c0d9 Update docusaurus/docs/dev-docs/preview.md
pwizla c56851a Add CSP directive in front end
pwizla 63f8505 Fix typo
pwizla File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| --- | ||
| title: Setting up the Preview feature | ||
| description: Learn to set up the Preview feature to link your front end application to Strapi's Content Manager Preview feature. | ||
| displayedSidebar: devDocsSidebar | ||
| tags: | ||
| - content manager | ||
| - preview | ||
| - configuration | ||
| --- | ||
| | ||
| # Setting up the Preview feature | ||
pwizla marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| | ||
| Strapi's Preview feature enables previewing content in a frontend application directly from the Strapi admin panel. | ||
| | ||
| The present page describes how to set up the Preview feature in Strapi. Once set up, the feature can be used as described in the [User Guide](/user-docs/content-manager/previewing-content). | ||
| | ||
| :::prerequisites | ||
| * The following environment variables must be defined in your `.env` file, replacing example values with appropriate values: | ||
| | ||
| ```bash | ||
| CLIENT_URL=https://your-frontend-app.com | ||
| PREVIEW_SECRET=your-secret-key # optional, required with Next.js draft mode | ||
| ``` | ||
| | ||
| * A front-end application for your Strapi project should be already created and set up. | ||
| ::: | ||
| | ||
| ## Configuration components | ||
| | ||
| The Preview feature configuration is stored in the `preview` object of [the `config/admin` file](/dev-docs/configurations/admin-panel) and consists of 3 key components: | ||
| | ||
| ### Activation flag | ||
| | ||
| Enables or disables the preview feature: | ||
| ```javascript title="config/admin.ts|js" {3} | ||
| // … | ||
| preview: { | ||
| enabled: true, | ||
| // … | ||
| } | ||
| // … | ||
| ``` | ||
| | ||
| ### Allowed origins | ||
| | ||
| Controls which domains can access previews: | ||
pwizla marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| | ||
| ```javascript title="config/admin.ts|js" {5} | ||
| // … | ||
| preview: { | ||
| enabled: true, | ||
| config: { | ||
| allowedOrigins: env("CLIENT_URL"), // Usually your frontend application URL | ||
pwizla marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| // … | ||
| } | ||
| } | ||
| // … | ||
| ``` | ||
| | ||
| ### Preview handler | ||
| | ||
| Manages the preview logic and URL generation, as in the following basic example where `uid` is the content-type identifier (e.g., `api::article.article` or `plugin::my-api.my-content-type`): | ||
| | ||
| ```jsx title="config/admin.ts|js" {6-11} | ||
| // … | ||
| preview: { | ||
| enabled: true, | ||
| config: { | ||
| // … | ||
| async handler(uid, { documentId, locale, status }) { | ||
| const document = await strapi.documents(uid).findOne({ documentId }); | ||
| const pathname = getPreviewPathname(uid, { locale, document }); | ||
| | ||
| return `${env('PREVIEW_URL')}${pathname}` | ||
| }, | ||
| } | ||
| } | ||
| // … | ||
| ``` | ||
| | ||
| An example of [URL generation logic](#2-add-url-generation-logic) in given in the following basic implementation guide. | ||
| | ||
| #### Previewing draft entries | ||
| | ||
| The strategy for the front end application to query draft or published content is framework-specific. At least 3 strategies exist: | ||
| | ||
| - using a query parameter, having something like `/your-path?preview=true` (this is, for instance, how [Nuxt](https://nuxt.com/docs/api/composables/use-preview-modehow) works) | ||
| - redirecting to a dedicated preview route like `/preview?path=your-path`(this is, for instance, how [Next's draft mode](https://nextjs.org/docs/app/building-your-application/configuring/draft-mode) works) | ||
| - or using a different domain for previews like `preview.mysite.com/your-path`. | ||
| | ||
| When [Draft & Publish](/user-docs/content-manager/saving-and-publishing-content.md) is enabled for your content-type, you can also directly leverage Strapi's `status` parameter to handle the logic within the Preview handler, using the following generic approach: | ||
| | ||
| ```javascript | ||
| async handler(uid, { documentId, locale, status }) { | ||
| const document = await strapi.documents(uid).findOne({ documentId }); | ||
| const pathname = getPreviewPathname(uid, { locale, document }); | ||
| if (status === 'published') { | ||
| // return the published version | ||
| } | ||
| // return the draft version | ||
| }, | ||
| ``` | ||
| | ||
| A more detailed example using the draft mode of Next.js is given in the [basic implementation guide](#3-add-handler-logic). | ||
| | ||
| ## Basic implementation guide | ||
| | ||
| Follow these steps to add Preview capabilities to your content types. | ||
| | ||
| ### 1. Create the Preview configuration | ||
| | ||
| Create a new file `/config/admin.ts` (or update it if it exists) with the following basic structure: | ||
| | ||
| ```javascript title="config/admin.ts" | ||
| export default ({ env }) => ({ | ||
| // Other admin-related configurations go here | ||
| // (see docs.strapi.io/dev-docs/configurations/admin-panel) | ||
| preview: { | ||
| enabled: true, | ||
| config: { | ||
| allowedOrigins: env('CLIENT_URL'), | ||
| async handler (uid, { documentId, locale, status }) => { | ||
| // Handler implementation coming in step 3 | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| ``` | ||
| | ||
| ### 2. Add URL generation logic | ||
| | ||
| Add the URL generation logic with a `getPreviewPathname` function. The following example is taken from the [Launchpad](https://github.com/strapi/LaunchPad/tree/feat/preview) Strapi demo application: | ||
| | ||
| ```typescript title="config/admin.ts" | ||
| // Function to generate preview pathname based on content type and document | ||
| const getPreviewPathname = (uid, { locale, document }): string => { | ||
| const { slug } = document; | ||
| | ||
| // Handle different content types with their specific URL patterns | ||
| switch (uid) { | ||
| // Handle pages with predefined routes | ||
| case "api::page.page": | ||
| switch (slug) { | ||
| case "homepage": | ||
| return `/${locale}`; // Localized homepage | ||
| case "pricing": | ||
| return "/pricing"; // Pricing page | ||
| case "contact": | ||
| return "/contact"; // Contact page | ||
| case "faq": | ||
| return "/faq"; // FAQ page | ||
| } | ||
| // Handle product pages | ||
| case "api::product.product": { | ||
| if (!slug) { | ||
| return "/products"; // Products listing page | ||
| } | ||
| return `/products/${slug}`; // Individual product page | ||
| } | ||
| // Handle blog articles | ||
| case "api::article.article": { | ||
| if (!slug) { | ||
| return "/blog"; // Blog listing page | ||
| } | ||
| return `/blog/${slug}`; // Individual article page | ||
| } | ||
| } | ||
| return "/"; // Default fallback route | ||
remidej marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| }; | ||
| | ||
| // … main export (see step 3) | ||
| ``` | ||
| | ||
| ### 3. Add handler logic | ||
| | ||
| Create the complete configuration, expanding the basic configuration created in step 1. with the URL generation logic created in step 2., adding an appropriate handler logic: | ||
| | ||
| ```typescript title="config/admin.ts" {8-9,18-35} | ||
| const getPreviewPathname = (uid, { locale, document }): string => { | ||
| // … as defined in step 2 | ||
| }; | ||
| | ||
| // Main configuration export | ||
| export default ({ env }) => { | ||
| // Get environment variables | ||
| const clientUrl = env("CLIENT_URL"); // Frontend application URL | ||
| const previewSecret = env("PREVIEW_SECRET"); // Secret key for preview authentication | ||
| | ||
| return { | ||
| // Other admin-related configurations go here | ||
| // (see docs.strapi.io/dev-docs/configurations/admin-panel) | ||
| preview: { | ||
| enabled: true, // Enable preview functionality | ||
| config: { | ||
| allowedOrigins: clientUrl, // Restrict preview access to specific domain | ||
| async handler(uid, { documentId, locale, status }) { | ||
| // Fetch the complete document from Strapi | ||
| const document = await strapi.documents(uid).findOne({ documentId }); | ||
| | ||
| // Generate the preview pathname based on content type and document | ||
| const previewPathname = getPreviewPathname(uid, { locale, document }); | ||
| | ||
| // For published content, return direct URL | ||
| if (status === "published") { | ||
| return `${clientUrl}${previewPathname}`; | ||
| } | ||
| | ||
| // For draft content, use Next.js draft mode, passing it a secret key | ||
| const urlSearchParams = new URLSearchParams({ | ||
| url: previewPathname, | ||
| secret: previewSecret, // Add security token | ||
| }); | ||
| return `${clientUrl}/api/preview?${urlSearchParams}`; | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| }; | ||
| ``` | ||
| | ||
| ### 4. Set up the front-end preview route | ||
| | ||
| Setting up the front-end preview route is highly dependent on the framework used for your front-end application. | ||
| | ||
| For instance, [Next.js draft mode](https://nextjs.org/docs/app/building-your-application/configuring/draft-mode) and | ||
| [Nuxt preview mode](https://nuxt.com/docs/api/composables/use-preview-mode) provide additional documentation on how to implement the front-end part in their respective documentations. | ||
| | ||
| If using Next.js, a basic implementation could be like in the following example taken from the [Launchpad](https://github.com/strapi/LaunchPad/tree/feat/preview) Strapi demo application: | ||
| | ||
| ```typescript title="/next/api/preview/route.ts" | ||
| import { draftMode } from "next/headers"; | ||
| import { redirect } from "next/navigation"; | ||
| | ||
| export async function GET(request: Request) { | ||
| // Parse query string parameters | ||
| const { searchParams } = new URL(request.url); | ||
| const secret = searchParams.get("secret"); | ||
| const url = searchParams.get("url"); | ||
| | ||
| // Check the secret and next parameters | ||
| // This secret should only be known to this route handler and the CMS | ||
| if (secret !== process.env.PREVIEW_SECRET) { | ||
| return new Response("Invalid token", { status: 401 }); | ||
| } | ||
| | ||
| // Enable Draft Mode by setting the cookie | ||
| draftMode().enable(); | ||
| | ||
| // Redirect to the path from the fetched post | ||
| redirect(url || "/"); | ||
| } | ||
| | ||
52 changes: 52 additions & 0 deletions 52 docusaurus/docs/user-docs/content-manager/previewing-content.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| --- | ||
| title: Previewing content | ||
| description: With the Preview feature, you can preview your front-end directly from the Content Manager | ||
| displayedSidebar: userSidebar | ||
| tags: | ||
| - content manager | ||
| - preview | ||
| --- | ||
| | ||
| # Previewing content | ||
pwizla marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| | ||
| With the Preview feature, you can preview your front end application directly from Strapi's admin panel. This is helpful to see how updates to your content in the Edit View of the Content Manager will affect the final result. | ||
| | ||
| <!-- TODO: add a dark mode GIF --> | ||
| <ThemedImage | ||
| alt="Previewing content" | ||
| sources={{ | ||
| light: '/img/assets/content-manager/previewing-content.gif', | ||
| dark: '/img/assets/content-manager/previewing-content.gif', | ||
| }} | ||
| /> | ||
| | ||
| <!-- <div style={{position: 'relative', paddingBottom: 'calc(54.43121693121693% + 50px)', height: '0'}}> | ||
| <iframe id="zpen5g4t8p" src="https://app.guideflow.com/embed/zpen5g4t8p" width="100%" height="100%" style={{overflow:'hidden', position:'absolute', border:'none'}} scrolling="no" allow="clipboard-read; clipboard-write" webkitallowfullscreen mozallowfullscreen allowfullscreen allowtransparency="true"></iframe> | ||
| </div> --> | ||
| | ||
| :::prerequisites | ||
| - The Strapi admin panel user should have read permissions for the content-type. | ||
| - The Preview feature should be configured in the code of the `config/admin` file (see [Developer Docs](/dev-docs/preview) for details). | ||
| - A front-end application should already be created and running so you can preview it. | ||
| ::: | ||
| | ||
| When the Preview feature is properly set up, an **Open preview** button is visible on the right in the Edit View of the Content Manager. 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: | ||
| | ||
| <!-- TODO: add a dark mode screenshot --> | ||
| <ThemedImage | ||
| alt="Previewing content" | ||
| sources={{ | ||
| light: '/img/assets/content-manager/previewing-content.png', | ||
| dark: '/img/assets/content-manager/previewing-content.png', | ||
| }} | ||
| /> | ||
| | ||
| From the Preview screen, you can: | ||
| | ||
| - click the close button  in the upper left corner to go back to the Edit View of the Content Manager, | ||
| - switch between previewing the draft and the published version (if [Draft & Publish](/user-docs/content-manager/saving-and-publishing-content) 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. | ||
| | ||
| :::caution | ||
| When making updates to the content, first save them before clicking on Open Preview again, otherwise your latest updates will be lost. A pop up window will warn you about this behavior. | ||
| ::: | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm realizing now there's one last step needed to get the whole thing to work. Apologies for not seeing this earlier.
The permissions to load an iframe go both ways:
That second step requires the preview frontend to have its own header directive: the CSP
frame-ancestorsdirective. The way to set it up will depend on how they build their site. For nextjs it requires a middleware config: https://nextjs.org/docs/app/building-your-application/configuring/content-security-policyThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many thanks for all these details, @remidej ! I've just updated the docs. Are we good to merge the PR this afternoon?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is perfect 👍