From 41019c23142021e1cc605bf69b930e726cb0eb6d Mon Sep 17 00:00:00 2001 From: Cody Olsen <81981+stipsan@users.noreply.github.com> Date: Mon, 11 Mar 2024 03:49:18 +0100 Subject: [PATCH] Update Sanity example for App Router (#63045) This PR updates the `cms-sanity` example to use: - App Router - TypeScript - Sanity Studio v3 instead of v2 - Embeds the Studio inside the next app on the `/studio` route. - ISR / Data Cache (revalidations through `revalidatePath` while in Live Visual Editing, time-based to match the Sanity API CDN in production). - Support Vercel Visual Editing out of the box. - The new `next-sanity/image` component. - Vercel Speed Insights. - The Sanity Presentation Tool for live content previews. - Sanity Portable Text setup to fully support `@tailwindcss/typography`. - [AI Assist](https://www.sanity.io/docs/ai-assist) - Auto fill in `alt` text on images. - Preset prompts for content creation --- .../cms-contentful/app/posts/[slug]/page.tsx | 14 +- examples/cms-dotcms/components/post-body.tsx | 4 +- examples/cms-sanity/.env.local.example | 2 +- examples/cms-sanity/.eslintrc | 4 + examples/cms-sanity/.gitignore | 2 +- examples/cms-sanity/README.md | 560 ++++++++---------- examples/cms-sanity/app/(blog)/actions.ts | 12 + .../cms-sanity/app/(blog)/alert-banner.tsx | 52 ++ examples/cms-sanity/app/(blog)/avatar.tsx | 31 + .../cms-sanity/app/(blog)/cover-image.tsx | 31 + examples/cms-sanity/app/(blog)/date.tsx | 9 + examples/cms-sanity/app/(blog)/layout.tsx | 115 ++++ .../cms-sanity/app/(blog)/more-stories.tsx | 52 ++ examples/cms-sanity/app/(blog)/onboarding.tsx | 73 +++ examples/cms-sanity/app/(blog)/page.tsx | 116 ++++ .../cms-sanity/app/(blog)/portable-text.tsx | 49 ++ .../app/(blog)/posts/[slug]/page.tsx | 118 ++++ .../cms-sanity/app/(sanity)/apple-icon.png | Bin 0 -> 3682 bytes examples/cms-sanity/app/(sanity)/icon.ico | Bin 0 -> 4286 bytes examples/cms-sanity/app/(sanity)/icon.png | Bin 0 -> 10466 bytes examples/cms-sanity/app/(sanity)/icon.svg | 7 + examples/cms-sanity/app/(sanity)/layout.tsx | 23 + .../app/(sanity)/studio/[[...tool]]/page.tsx | 9 + examples/cms-sanity/app/api/draft/route.tsx | 27 + examples/cms-sanity/app/favicon.ico | Bin 0 -> 15086 bytes .../{styles/index.css => app/globals.css} | 2 - examples/cms-sanity/components/alert.js | 42 -- examples/cms-sanity/components/avatar.js | 23 - examples/cms-sanity/components/container.js | 3 - examples/cms-sanity/components/cover-image.js | 38 -- examples/cms-sanity/components/date.js | 8 - examples/cms-sanity/components/footer.js | 30 - examples/cms-sanity/components/header.js | 12 - examples/cms-sanity/components/hero-post.js | 37 -- examples/cms-sanity/components/intro.js | 28 - .../cms-sanity/components/landing-preview.js | 8 - examples/cms-sanity/components/landing.js | 34 -- examples/cms-sanity/components/layout.js | 16 - .../components/markdown-styles.module.css | 18 - examples/cms-sanity/components/meta.js | 42 -- .../cms-sanity/components/more-stories.js | 24 - examples/cms-sanity/components/post-body.js | 10 - examples/cms-sanity/components/post-header.js | 26 - examples/cms-sanity/components/post-plug.js | 31 - .../cms-sanity/components/post-preview.js | 9 - examples/cms-sanity/components/post-title.js | 7 - examples/cms-sanity/components/post.js | 65 -- .../components/section-separator.js | 3 - examples/cms-sanity/lib/config.js | 15 - examples/cms-sanity/lib/constants.js | 5 - examples/cms-sanity/lib/queries.js | 37 -- examples/cms-sanity/lib/sanity.js | 13 - examples/cms-sanity/lib/sanity.server.js | 35 -- examples/cms-sanity/next.config.js | 11 +- examples/cms-sanity/package.json | 39 +- examples/cms-sanity/pages/_app.js | 7 - examples/cms-sanity/pages/_document.js | 13 - examples/cms-sanity/pages/api/exit-preview.js | 8 - examples/cms-sanity/pages/api/preview.js | 40 -- examples/cms-sanity/pages/api/revalidate.js | 79 --- examples/cms-sanity/pages/index.js | 28 - examples/cms-sanity/pages/posts/[slug].js | 49 -- examples/cms-sanity/postcss.config.js | 2 - .../public/favicon/android-chrome-192x192.png | Bin 4795 -> 0 bytes .../public/favicon/android-chrome-512x512.png | Bin 14640 -> 0 bytes .../public/favicon/apple-touch-icon.png | Bin 1327 -> 0 bytes .../public/favicon/browserconfig.xml | 9 - .../public/favicon/favicon-16x16.png | Bin 595 -> 0 bytes .../public/favicon/favicon-32x32.png | Bin 880 -> 0 bytes .../cms-sanity/public/favicon/favicon.ico | Bin 15086 -> 0 bytes .../public/favicon/mstile-150x150.png | Bin 3567 -> 0 bytes .../public/favicon/safari-pinned-tab.svg | 33 -- .../public/favicon/site.webmanifest | 19 - .../{studio/sanity.cli.js => sanity.cli.ts} | 10 +- examples/cms-sanity/sanity.config.ts | 50 ++ examples/cms-sanity/sanity/lib/api.ts | 33 ++ examples/cms-sanity/sanity/lib/client.ts | 22 + examples/cms-sanity/sanity/lib/demo.ts | 59 ++ examples/cms-sanity/sanity/lib/fetch.ts | 51 ++ examples/cms-sanity/sanity/lib/queries.ts | 61 ++ examples/cms-sanity/sanity/lib/token.ts | 15 + examples/cms-sanity/sanity/lib/utils.ts | 37 ++ examples/cms-sanity/sanity/plugins/assist.ts | 265 +++++++++ examples/cms-sanity/sanity/plugins/locate.ts | 95 +++ .../cms-sanity/sanity/plugins/settings.tsx | 65 ++ .../sanity/schemas/documents/author.ts | 38 ++ .../sanity/schemas/documents/post.ts | 104 ++++ .../sanity/schemas/singletons/settings.tsx | 122 ++++ examples/cms-sanity/studio/.gitignore | 35 -- examples/cms-sanity/studio/copyEnv.js | 9 - examples/cms-sanity/studio/package.json | 27 - examples/cms-sanity/studio/plugins/.gitkeep | 1 - .../cms-sanity/studio/resolveProductionUrl.js | 21 - examples/cms-sanity/studio/sanity.config.js | 36 -- examples/cms-sanity/studio/schemas/author.js | 20 - examples/cms-sanity/studio/schemas/post.js | 64 -- examples/cms-sanity/studio/static/.gitkeep | 1 - examples/cms-sanity/studio/static/favicon.ico | Bin 1150 -> 0 bytes examples/cms-sanity/studio/tailwind.config.js | 6 - examples/cms-sanity/tailwind.config.js | 38 -- examples/cms-sanity/tailwind.config.ts | 17 + examples/cms-sanity/tsconfig.json | 28 + 102 files changed, 2096 insertions(+), 1502 deletions(-) create mode 100644 examples/cms-sanity/.eslintrc create mode 100644 examples/cms-sanity/app/(blog)/actions.ts create mode 100644 examples/cms-sanity/app/(blog)/alert-banner.tsx create mode 100644 examples/cms-sanity/app/(blog)/avatar.tsx create mode 100644 examples/cms-sanity/app/(blog)/cover-image.tsx create mode 100644 examples/cms-sanity/app/(blog)/date.tsx create mode 100644 examples/cms-sanity/app/(blog)/layout.tsx create mode 100644 examples/cms-sanity/app/(blog)/more-stories.tsx create mode 100644 examples/cms-sanity/app/(blog)/onboarding.tsx create mode 100644 examples/cms-sanity/app/(blog)/page.tsx create mode 100644 examples/cms-sanity/app/(blog)/portable-text.tsx create mode 100644 examples/cms-sanity/app/(blog)/posts/[slug]/page.tsx create mode 100644 examples/cms-sanity/app/(sanity)/apple-icon.png create mode 100644 examples/cms-sanity/app/(sanity)/icon.ico create mode 100644 examples/cms-sanity/app/(sanity)/icon.png create mode 100644 examples/cms-sanity/app/(sanity)/icon.svg create mode 100644 examples/cms-sanity/app/(sanity)/layout.tsx create mode 100644 examples/cms-sanity/app/(sanity)/studio/[[...tool]]/page.tsx create mode 100644 examples/cms-sanity/app/api/draft/route.tsx create mode 100644 examples/cms-sanity/app/favicon.ico rename examples/cms-sanity/{styles/index.css => app/globals.css} (52%) delete mode 100644 examples/cms-sanity/components/alert.js delete mode 100644 examples/cms-sanity/components/avatar.js delete mode 100644 examples/cms-sanity/components/container.js delete mode 100644 examples/cms-sanity/components/cover-image.js delete mode 100644 examples/cms-sanity/components/date.js delete mode 100644 examples/cms-sanity/components/footer.js delete mode 100644 examples/cms-sanity/components/header.js delete mode 100644 examples/cms-sanity/components/hero-post.js delete mode 100644 examples/cms-sanity/components/intro.js delete mode 100644 examples/cms-sanity/components/landing-preview.js delete mode 100644 examples/cms-sanity/components/landing.js delete mode 100644 examples/cms-sanity/components/layout.js delete mode 100644 examples/cms-sanity/components/markdown-styles.module.css delete mode 100644 examples/cms-sanity/components/meta.js delete mode 100644 examples/cms-sanity/components/more-stories.js delete mode 100644 examples/cms-sanity/components/post-body.js delete mode 100644 examples/cms-sanity/components/post-header.js delete mode 100644 examples/cms-sanity/components/post-plug.js delete mode 100644 examples/cms-sanity/components/post-preview.js delete mode 100644 examples/cms-sanity/components/post-title.js delete mode 100644 examples/cms-sanity/components/post.js delete mode 100644 examples/cms-sanity/components/section-separator.js delete mode 100644 examples/cms-sanity/lib/config.js delete mode 100644 examples/cms-sanity/lib/constants.js delete mode 100644 examples/cms-sanity/lib/queries.js delete mode 100644 examples/cms-sanity/lib/sanity.js delete mode 100644 examples/cms-sanity/lib/sanity.server.js delete mode 100644 examples/cms-sanity/pages/_app.js delete mode 100644 examples/cms-sanity/pages/_document.js delete mode 100644 examples/cms-sanity/pages/api/exit-preview.js delete mode 100644 examples/cms-sanity/pages/api/preview.js delete mode 100644 examples/cms-sanity/pages/api/revalidate.js delete mode 100644 examples/cms-sanity/pages/index.js delete mode 100644 examples/cms-sanity/pages/posts/[slug].js delete mode 100644 examples/cms-sanity/public/favicon/android-chrome-192x192.png delete mode 100644 examples/cms-sanity/public/favicon/android-chrome-512x512.png delete mode 100644 examples/cms-sanity/public/favicon/apple-touch-icon.png delete mode 100644 examples/cms-sanity/public/favicon/browserconfig.xml delete mode 100644 examples/cms-sanity/public/favicon/favicon-16x16.png delete mode 100644 examples/cms-sanity/public/favicon/favicon-32x32.png delete mode 100644 examples/cms-sanity/public/favicon/favicon.ico delete mode 100644 examples/cms-sanity/public/favicon/mstile-150x150.png delete mode 100644 examples/cms-sanity/public/favicon/safari-pinned-tab.svg delete mode 100644 examples/cms-sanity/public/favicon/site.webmanifest rename examples/cms-sanity/{studio/sanity.cli.js => sanity.cli.ts} (64%) create mode 100644 examples/cms-sanity/sanity.config.ts create mode 100644 examples/cms-sanity/sanity/lib/api.ts create mode 100644 examples/cms-sanity/sanity/lib/client.ts create mode 100644 examples/cms-sanity/sanity/lib/demo.ts create mode 100644 examples/cms-sanity/sanity/lib/fetch.ts create mode 100644 examples/cms-sanity/sanity/lib/queries.ts create mode 100644 examples/cms-sanity/sanity/lib/token.ts create mode 100644 examples/cms-sanity/sanity/lib/utils.ts create mode 100644 examples/cms-sanity/sanity/plugins/assist.ts create mode 100644 examples/cms-sanity/sanity/plugins/locate.ts create mode 100644 examples/cms-sanity/sanity/plugins/settings.tsx create mode 100644 examples/cms-sanity/sanity/schemas/documents/author.ts create mode 100644 examples/cms-sanity/sanity/schemas/documents/post.ts create mode 100644 examples/cms-sanity/sanity/schemas/singletons/settings.tsx delete mode 100644 examples/cms-sanity/studio/.gitignore delete mode 100644 examples/cms-sanity/studio/copyEnv.js delete mode 100644 examples/cms-sanity/studio/package.json delete mode 100644 examples/cms-sanity/studio/plugins/.gitkeep delete mode 100644 examples/cms-sanity/studio/resolveProductionUrl.js delete mode 100644 examples/cms-sanity/studio/sanity.config.js delete mode 100644 examples/cms-sanity/studio/schemas/author.js delete mode 100644 examples/cms-sanity/studio/schemas/post.js delete mode 100644 examples/cms-sanity/studio/static/.gitkeep delete mode 100644 examples/cms-sanity/studio/static/favicon.ico delete mode 100644 examples/cms-sanity/studio/tailwind.config.js delete mode 100644 examples/cms-sanity/tailwind.config.js create mode 100644 examples/cms-sanity/tailwind.config.ts create mode 100644 examples/cms-sanity/tsconfig.json diff --git a/examples/cms-contentful/app/posts/[slug]/page.tsx b/examples/cms-contentful/app/posts/[slug]/page.tsx index 02fc7f5818..2fb00f709c 100644 --- a/examples/cms-contentful/app/posts/[slug]/page.tsx +++ b/examples/cms-contentful/app/posts/[slug]/page.tsx @@ -27,26 +27,26 @@ export default async function PostPage({ return (
-

+

Blog .

-

+

{post.title}

-
+
{post.author && ( )}
-
+
-
-
+
+
{post.author && ( )} @@ -56,7 +56,7 @@ export default async function PostPage({
-
+
diff --git a/examples/cms-dotcms/components/post-body.tsx b/examples/cms-dotcms/components/post-body.tsx index b0c76143f8..099b1bfe4b 100644 --- a/examples/cms-dotcms/components/post-body.tsx +++ b/examples/cms-dotcms/components/post-body.tsx @@ -4,8 +4,8 @@ import Avatar from "./avatar"; export default function PostBody({ content }) { return ( -
-
+
+
{content.author.length ? ( [!CAUTION] +> Make sure to add `.env.local` to your `.gitignore` file so you don't accidentally commit it to your repository. + +## Step 2. Run Next.js locally in development mode + +```bash +npm install && npm run dev +``` + +```bash +yarn install && yarn dev +``` + +```bash +pnpm install && pnpm dev +``` + +Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). + +## Step 3. Populate content + +Open your Sanity Studio that should be running on [http://localhost:3000/studio](http://localhost:3000/studio). + +By default you're taken to the [Presentation tool][presentation], which has a preview of the blog on the left hand side, and a list of documents on the right hand side. + +
+View screenshot ✨ + +![screenshot](https://github.com/vercel/next.js/assets/81981/07cbc580-4a03-4837-9aa4-90b632c95630) + +
+ +We're all set to do some content creation! + +- Click on the **"+ Create"** button top left and select **Post** +- Type some dummy data for the **Title** +- **Generate** a **Slug** +
+ Now that you have a slug you should see the post show up in the preview on the left hand side ✨ + + ![screenshot](https://github.com/vercel/next.js/assets/81981/05b74848-6ae4-442b-8995-0b7e2180aa74) + +
+ +- Fill in **Content** with some dummy text +
+ Or generate it with AI Assist ✨ + + If you've enabled [AI Assist][enable-ai-assist] you click on the sparkles ✨ button and generate a draft based on your title and then on **Generate sample content**. + + ![screenshot](https://github.com/vercel/next.js/assets/81981/2276d8ad-5b55-447c-befe-d53249f091e1) + +
+ +- Summarize the **Content** in the **Excerpt** field +
+ Or have AI Assist summarize it for you ✨ + + If you've enabled [AI Assist][enable-ai-assist] you click on the sparkles ✨ button and then on **Generate sample content**. + + ![screenshot](https://github.com/vercel/next.js/assets/81981/d24b9b37-cd88-4519-8094-f4c956102450) + +
+ +- Select a **Cover Image** from [Unsplash]. +
+ Unsplash is available in the **Select** dropdown ✨ + + ![screenshot](https://github.com/vercel/next.js/assets/81981/204d004d-9396-434e-8795-a8b68a2ed89b) + +
+
+ Click the "Crop image" button to adjust hotspots and cropping ✨ + + ![screenshot](https://github.com/vercel/next.js/assets/81981/e905fc6e-5bab-46a7-baec-7cb08747772c) + +
+
+ You can preview the results live on the left side, and additional formats on the right side ✨ + + ![screenshot](https://github.com/vercel/next.js/assets/81981/6c59eef0-d2d9-4d77-928a-98e99df4b1df) + +
+ +- Customize the blog name, description and more. +
+ Click "Structure" at the top center, then on "Settings" on the left hand side ✨ + + ![screenshot](https://github.com/vercel/next.js/assets/81981/14f48d83-af81-4589-900e-a7a598cc608a) + +
+
+ Once you have a "Settings" document, you can customize it inside "Presentation" ✨ + + ![screenshot](https://github.com/vercel/next.js/assets/81981/e3473f7b-5e7e-46ab-8d43-cae54a4b929b) + +
+ +> [!IMPORTANT] +> For each post record, you need to click **Publish** after saving for it to be visible outside Draft Mode. In production new content is using [Time-based Revalidation](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#time-based-revalidation), which means it may take up to 1 minute before changes show up. Since a stale-while-revalidate pattern is used you may need to refresh a couple of times to see the changes. + +## Step 4. Deploy to production + +> [!NOTE] +> If you already [deployed with Vercel earlier](#deploy-your-own) you can skip this step. + +To deploy your local project to Vercel, push it to [GitHub](https://docs.github.com/en/get-started/importing-your-projects-to-github/importing-source-code-to-github/adding-locally-hosted-code-to-github)/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example). + +> [!IMPORTANT] +> When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file. + +After it's deployed link your local code to the Vercel project: + +```bash +npx vercel link +``` + +> [!TIP] +> In production you can exit Draft Mode by clickin on _"Back to published"_ at the top. On [Preview deployments](https://vercel.com/docs/deployments/preview-deployments) you can [toggle Draft Mode in the Vercel Toolbar](https://vercel.com/docs/workflow-collaboration/draft-mode#enabling-draft-mode-in-the-vercel-toolbar). + +## Next steps + +- [Join the Sanity community](https://slack.sanity.io/) ## Related examples @@ -38,298 +288,10 @@ You'll get: - [Blog Starter](/examples/blog-starter) - [WordPress](/examples/cms-wordpress) -# Configuration - -- [Step 1. Set up the environment](#step-1-set-up-the-environment) -- [Step 2. Run Next.js locally in development mode](#step-2-run-nextjs-locally-in-development-mode) -- [Step 3. Populate content](#step-3-populate-content) -- [Step 4. Deploy to production & use Preview Mode from anywhere](#step-4-deploy-to-production--use-preview-mode-from-anywhere) - - [If you didn't Deploy with Vercel earlier do so now](#if-you-didnt-deploy-with-vercel-earlier-do-so-now) - - [Configure CORS for production](#configure-cors-for-production) - - [Add the preview secret environment variable](#add-the-preview-secret-environment-variable) - - [How to test locally that the secret is setup correctly](#how-to-test-locally-that-the-secret-is-setup-correctly) - - [How to start Preview Mode for Next.js in production from a local Studio](#how-to-start-preview-mode-for-nextjs-in-production-from-a-local-studio) - - [If you regret sending a preview link to someone](#if-you-regret-sending-a-preview-link-to-someone) -- [Step 5. Deploy your Studio and publish from anywhere](#step-5-deploy-your-studio-and-publish-from-anywhere) -- [Step 6. Setup Revalidation Webhook](#step-6-setup-revalidation-webhook) - - [Testing the Webhook](#testing-the-webhook) -- [Next steps](#next-steps) - -## Step 1. Set up the environment - -Use the Deploy Button below, you'll deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) as well as connect it to your Sanity dataset using [the Sanity Vercel Integration][integration]. - -[![Deploy with Vercel](https://vercel.com/button)][vercel-deploy] - -[Clone the repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) that Vercel created for you and from the root directory of your local checkout. -Then link your clone to Vercel: - -```bash -npx vercel link -``` - -Download the environment variables needed to connect Next.js and Studio to your Sanity project: - -```bash -npx vercel env pull -``` - -## Step 2. Run Next.js locally in development mode - -```bash -npm install && npm run dev -``` - -```bash -yarn install && yarn dev -``` - -Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). - -Note: This also installs dependencies for Sanity Studio as a post-install step. - -## Step 3. Populate content - -In another terminal start up the studio: - -```bash -npm run studio:dev -``` - -Your studio should be up and running on [http://localhost:3333](http://localhost:3333)! - -### Create content - -Create content in Sanity Studio and live preview it in Next.js, side-by-side, by opening these URLs: - -- [`http://localhost:3333`](http://localhost:3333) -- [`http://localhost:3000/api/preview`](http://localhost:3000/api/preview) - -
-View screenshot ✨ - -![screenshot](https://user-images.githubusercontent.com/81981/182991870-7a0f6e54-b35e-4728-922b-409fcf1d6cc3.png) - -
- -We're all set to do some content creation! - -- Click on the **"Create new document"** button top left and select **Post** -- Type some dummy data for the **Title** -- **Generate** a **Slug** -
- View screenshot ✨ - - ![screenshot](https://user-images.githubusercontent.com/81981/182993687-b6313086-f60a-4b36-b038-4c1c63b53c54.png) - -
- -- Set the **Date** -- Select a **Cover Image** from [Unsplash]. -
- View screenshot ✨ - - ![screenshot](https://user-images.githubusercontent.com/81981/182994571-f204c41c-e1e3-44f4-82b3-99fefbd25bec.png) - -
- -- Let's create an **Author** inline, click **Create new**. -- Give the **Author** a **Name**. -- After selecting a **Picture** of a **face** from [Unsplash], set a hotspot to ensure pixel-perfect cropping. -
- View screenshot ✨ - - ![screenshot](https://user-images.githubusercontent.com/81981/182995772-33d63e45-4920-48c5-aa47-ccb7ce10170c.png) - -
- -- Create a couple more **Posts** and watch how the layout adapt to more content. - -**Important:** For each post record, you need to click **Publish** after saving for it to be visible outside Preview Mode. - -To exit Preview Mode, you can click on _"Click here to exit preview mode"_ at the top. - -## Step 4. Deploy to production & use Preview Mode from anywhere - -### If you didn't [Deploy with Vercel earlier](#step-1-set-up-the-environment) do so now - -To deploy your local project to Vercel, push it to [GitHub](https://docs.github.com/en/get-started/importing-your-projects-to-github/importing-source-code-to-github/adding-locally-hosted-code-to-github)/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example). - -**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file. - -After it's deployed link your local code to the Vercel project: - -```bash -npx vercel link -``` - -### Configure CORS for production - -Add your `production url` to the list over CORS origins. - -
-Don't remember the production url? 🤔 - -No worries, it's easy to find out. Go to your [Vercel Dashboard](https://vercel.com/) and click on your project: - -![screenshot](https://user-images.githubusercontent.com/81981/183002637-6aa6b1d8-e0ee-4a9b-bcc0-d49799fcc984.png) - -In the screenshot above the `production url` is `https://cms-sanity.vercel.app`. - -
- -```bash -npm --prefix studio run cors:add -- [your production url] --credentials -``` - -### Add the preview secret environment variable - -It's required to set a secret that makes Preview Mode activation links unique. Otherwise anyone could see your unpublished content by just opening `[your production url]/api/preview`. -Run this and it'll prompt you for a value: - -```bash -npx vercel env add SANITY_STUDIO_PREVIEW_SECRET -``` - -The secret can be any combination of random words and letters as long as it's URL safe. -You can generate one in your DevTools console using `copy(Math.random().toString(36).substr(2, 10))` if you don't feel like inventing one. - -You should see something like this in your terminal afterwards: - -```bash -$ npx vercel env add SANITY_STUDIO_PREVIEW_SECRET -Vercel CLI 27.3.7 -? What’s the value of SANITY_STUDIO_PREVIEW_SECRET? 2whpu1jefs -? Add SANITY_STUDIO_PREVIEW_SECRET to which Environments (select multiple)? Production, Preview, Development -✅ Added Environment Variable SANITY_STUDIO_PREVIEW_SECRET to Project cms-sanity [1s] -``` - -Redeploy production to apply the secret to the preview api: - -```bash -npx vercel --prod -``` - -After it deploys it should now start preview mode if you launch `[your production url]/api/preview?secret=[your preview secret]`. You can send that preview url to people you want to show the content you're working on before you publish it. - -### How to test locally that the secret is setup correctly - -In order to test that the secret will prevent unauthorized people from activating preview mode, start by updating the local `.env` with the secret you just made: - -```bash -npx vercel env pull -``` - -Restart your Next.js and Studio processes so the secret is applied: - -```bash -npm run dev -``` - -```bash -npm run studio:dev -``` - -And now you'll get an error if `[secret]` is incorrect when you try to open `https://localhost:3000/api/preview?secret=[secret]`. - -### How to start Preview Mode for Next.js in production from a local Studio - -Run this to make the Studio open previews at `[your production url]/api/preview` instead of `http://localhost:3000/api/preview` - -```bash -SANITY_STUDIO_PREVIEW_URL=[your production url] npm run studio:dev -``` - -### If you regret sending a preview link to someone - -Revoke their access by creating a new secret: - -```bash -npx vercel env rm SANITY_STUDIO_PREVIEW_SECRET -npx vercel env add SANITY_STUDIO_PREVIEW_SECRET -npx vercel --prod -``` - -## Step 5. Deploy your Studio and publish from anywhere - -Live previewing content is fun, but collaborating on content in real-time is next-level: - -```bash -SANITY_STUDIO_PREVIEW_URL=[your production url] npm run studio:deploy -``` - -If it's successful you should see something like this in your terminal: - -```bash -SANITY_STUDIO_PREVIEW_URL="https://cms-sanity.vercel.app" npm run studio:deploy -? Studio hostname (.sanity.studio): cms-sanity - -Including the following environment variables as part of the JavaScript bundle: -- SANITY_STUDIO_PREVIEW_URL -- SANITY_STUDIO_PREVIEW_SECRET -- SANITY_STUDIO_API_PROJECT_ID -- SANITY_STUDIO_API_DATASET - -✔ Deploying to Sanity.Studio - -Success! Studio deployed to https://cms-sanity.sanity.studio/ -``` - -This snippet is stripped from verbose information, you'll see a lot of extra stuff in your terminal. - -## Step 6. Setup Revalidation Webhook - -Using GROQ Webhooks Next.js can rebuild pages that have changed content. It rebuilds so fast it can almost compete with Preview Mode. - -Create a secret and give it a value the same way you did for `SANITY_STUDIO_PREVIEW_SECRET` in [Step 4](#add-the-preview-secret-environment-variable). It's used to verify that webhook payloads came from Sanity infra, and set it as the value for `SANITY_REVALIDATE_SECRET`: - -```bash -npx vercel env add SANITY_REVALIDATE_SECRET -``` - -You should see something like this in your terminal afterwards: - -```bash -$ npx vercel env add SANITY_REVALIDATE_SECRET -Vercel CLI 27.3.7 -? What’s the value of SANITY_REVALIDATE_SECRET? jwh3nr85ft -? Add SANITY_REVALIDATE_SECRET to which Environments (select multiple)? Production, Preview, Development -✅ Added Environment Variable SANITY_REVALIDATE_SECRET to Project cms-sanity [1s] -``` - -Apply the secret to production: - -```bash -npx vercel --prod -``` - -Wormhole into the [manager](https://manage.sanity.io/) by running: - -```bash -(cd studio && npx sanity hook create) -``` - -- **Name** it "On-demand Revalidation". -- Set the **URL** to`[your production url]/api/revalidate`, for example: `https://cms-sanity.vercel.app/api/revalidate` -- Set the **Trigger on** field to -- Set the **Filter** to `_type == "post" || _type == "author"` -- Set the **Secret** to the same value you gave `SANITY_REVALIDATE_SECRET` earlier. -- Hit **Save**! - -### Testing the Webhook - -- Open the Deployment function log. (**Vercel Dashboard > Deployment > Functions** and filter by `api/revalidate`) -- Edit a Post in your Sanity Studio and publish. -- The log should start showing calls. -- And the published changes show up on the site after you reload. - -## Next steps - -- Mount your preview inside the Sanity Studio for comfortable side-by-side editing -- [Join the Sanity community](https://slack.sanity.io/) - -[vercel-deploy]: https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fcms-sanity&repository-name=cms-sanity&project-name=cms-sanity&demo-title=Blog%20using%20Next.js%20%26%20Sanity&demo-description=On-demand%20ISR%2C%20sub-second%20as-you-type%20previews&demo-url=https%3A%2F%2Fnext-blog-sanity.vercel.app%2F&demo-image=https%3A%2F%2Fuser-images.githubusercontent.com%2F110497645%2F182727236-75c02b1b-faed-4ae2-99ce-baa089f7f363.png&integration-ids=oac_hb2LITYajhRQ0i4QznmKH7gx +[vercel-deploy]: https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fcms-sanity&repository-name=cms-sanity&project-name=cms-sanity&demo-title=Blog%20using%20Next.js%20%26%20Sanity&demo-description=Real-time%20updates%2C%20seamless%20editing%2C%20no%20rebuild%20delays.&demo-url=https%3A%2F%2Fnext-blog.sanity.build%2F&demo-image=https%3A%2F%2Fgithub.com%2Fsanity-io%2Fnext-sanity%2Fassets%2F81981%2Fb81296a9-1f53-4eec-8948-3cb51aca1259&integration-ids=oac_hb2LITYajhRQ0i4QznmKH7gx [integration]: https://www.sanity.io/docs/vercel-integration -[`sanity.json`]: studio/sanity.json [`.env.local.example`]: .env.local.example [unsplash]: https://unsplash.com +[sanity-homepage]: https://www.sanity.io?utm_source=github.com&utm_medium=referral&utm_campaign=nextjs-v3vercelstarter +[presentation]: https://www.sanity.io/docs/presentation +[enable-ai-assist]: https://www.sanity.io/plugins/ai-assist#enabling-the-ai-assist-api diff --git a/examples/cms-sanity/app/(blog)/actions.ts b/examples/cms-sanity/app/(blog)/actions.ts new file mode 100644 index 0000000000..7b5cc9ee80 --- /dev/null +++ b/examples/cms-sanity/app/(blog)/actions.ts @@ -0,0 +1,12 @@ +"use server"; + +import { draftMode } from "next/headers"; + +export async function disableDraftMode() { + "use server"; + await Promise.allSettled([ + draftMode().disable(), + // Simulate a delay to show the loading state + new Promise((resolve) => setTimeout(resolve, 1000)), + ]); +} diff --git a/examples/cms-sanity/app/(blog)/alert-banner.tsx b/examples/cms-sanity/app/(blog)/alert-banner.tsx new file mode 100644 index 0000000000..567bf34e2c --- /dev/null +++ b/examples/cms-sanity/app/(blog)/alert-banner.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useSyncExternalStore, useTransition } from "react"; + +import { disableDraftMode } from "./actions"; + +const emptySubscribe = () => () => {}; + +export default function AlertBanner() { + const router = useRouter(); + const [pending, startTransition] = useTransition(); + + const shouldShow = useSyncExternalStore( + emptySubscribe, + () => window.top === window, + () => false, + ); + + if (!shouldShow) return null; + + return ( +
+
+ {pending ? ( + "Disabling draft mode..." + ) : ( + <> + {"Previewing drafts. "} + + + )} +
+
+ ); +} diff --git a/examples/cms-sanity/app/(blog)/avatar.tsx b/examples/cms-sanity/app/(blog)/avatar.tsx new file mode 100644 index 0000000000..5e928c04ba --- /dev/null +++ b/examples/cms-sanity/app/(blog)/avatar.tsx @@ -0,0 +1,31 @@ +import { Image } from "next-sanity/image"; + +import { Author } from "@/sanity/lib/queries"; +import { urlForImage } from "@/sanity/lib/utils"; + +export default function Avatar({ name, picture }: Author) { + return ( +
+ {picture?.asset?._ref ? ( +
+ {picture?.alt +
+ ) : ( +
By
+ )} +
{name}
+
+ ); +} diff --git a/examples/cms-sanity/app/(blog)/cover-image.tsx b/examples/cms-sanity/app/(blog)/cover-image.tsx new file mode 100644 index 0000000000..97057ec8c9 --- /dev/null +++ b/examples/cms-sanity/app/(blog)/cover-image.tsx @@ -0,0 +1,31 @@ +import { Image } from "next-sanity/image"; + +import { urlForImage } from "@/sanity/lib/utils"; + +interface CoverImageProps { + image: any; + priority?: boolean; +} + +export default function CoverImage(props: CoverImageProps) { + const { image: source, priority } = props; + const image = source?.asset?._ref ? ( + {source?.alt + ) : ( +
+ ); + + return ( +
+ {image} +
+ ); +} diff --git a/examples/cms-sanity/app/(blog)/date.tsx b/examples/cms-sanity/app/(blog)/date.tsx new file mode 100644 index 0000000000..2ca5b6190a --- /dev/null +++ b/examples/cms-sanity/app/(blog)/date.tsx @@ -0,0 +1,9 @@ +import { format } from "date-fns"; + +export default function DateComponent({ dateString }: { dateString: string }) { + return ( + + ); +} diff --git a/examples/cms-sanity/app/(blog)/layout.tsx b/examples/cms-sanity/app/(blog)/layout.tsx new file mode 100644 index 0000000000..9001e0f7e1 --- /dev/null +++ b/examples/cms-sanity/app/(blog)/layout.tsx @@ -0,0 +1,115 @@ +import "../globals.css"; + +import { SpeedInsights } from "@vercel/speed-insights/next"; +import { Metadata } from "next"; +import { PortableTextBlock, VisualEditing, toPlainText } from "next-sanity"; +import { Inter } from "next/font/google"; +import { draftMode } from "next/headers"; +import { Suspense } from "react"; + +import AlertBanner from "./alert-banner"; +import PortableText from "./portable-text"; + +import * as demo from "@/sanity/lib/demo"; +import { sanityFetch } from "@/sanity/lib/fetch"; +import { SettingsQueryResponse, settingsQuery } from "@/sanity/lib/queries"; +import { resolveOpenGraphImage } from "@/sanity/lib/utils"; + +export async function generateMetadata(): Promise { + const settings = await sanityFetch({ + query: settingsQuery, + // Metadata should never contain stega + stega: false, + }); + const title = settings?.title || demo.title; + const description = settings?.description || demo.description; + + const ogImage = resolveOpenGraphImage(settings?.ogImage); + let metadataBase: URL | undefined = undefined; + try { + metadataBase = settings?.ogImage?.metadataBase + ? new URL(settings.ogImage.metadataBase) + : undefined; + } catch { + // ignore + } + return { + metadataBase, + title: { + template: `%s | ${title}`, + default: title, + }, + description: toPlainText(description), + openGraph: { + images: ogImage ? [ogImage] : [], + }, + }; +} + +const inter = Inter({ + variable: "--font-inter", + subsets: ["latin"], + display: "swap", +}); + +async function Footer() { + const data = await sanityFetch({ + query: settingsQuery, + }); + const footer = data?.footer || ([] as PortableTextBlock[]); + + return ( + + ); +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +
+ {draftMode().isEnabled && } +
{children}
+ +
+ +
+ {draftMode().isEnabled && } + + + + ); +} diff --git a/examples/cms-sanity/app/(blog)/more-stories.tsx b/examples/cms-sanity/app/(blog)/more-stories.tsx new file mode 100644 index 0000000000..4ab1ff388e --- /dev/null +++ b/examples/cms-sanity/app/(blog)/more-stories.tsx @@ -0,0 +1,52 @@ +import Link from "next/link"; + +import Avatar from "./avatar"; +import CoverImage from "./cover-image"; +import DateComponent from "./date"; + +import { sanityFetch } from "@/sanity/lib/fetch"; +import { + MoreStoriesQueryResponse, + moreStoriesQuery, +} from "@/sanity/lib/queries"; + +export default async function MoreStories(params: { + skip: string; + limit: number; +}) { + const data = await sanityFetch({ + query: moreStoriesQuery, + params, + }); + + return ( + <> +
+ {data?.map((post) => { + const { _id, title, slug, coverImage, excerpt, author } = post; + return ( +
+ + + +

+ + {title} + +

+
+ +
+ {excerpt && ( +

+ {excerpt} +

+ )} + {author && } +
+ ); + })} +
+ + ); +} diff --git a/examples/cms-sanity/app/(blog)/onboarding.tsx b/examples/cms-sanity/app/(blog)/onboarding.tsx new file mode 100644 index 0000000000..2971b2bacb --- /dev/null +++ b/examples/cms-sanity/app/(blog)/onboarding.tsx @@ -0,0 +1,73 @@ +"use client"; + +/** + * This file is used for onboarding when you don't have any posts yet and are using the template for the first time. + * Once you have content, and know where to go to access the Sanity Studio and create content, you can delete this file. + */ + +import Link from "next/link"; +import { useSyncExternalStore } from "react"; + +const emptySubscribe = () => () => {}; + +export default function Onboarding() { + const target = useSyncExternalStore( + emptySubscribe, + () => (window.top === window ? undefined : "_blank"), + () => "_blank", + ); + + return ( +
+ +
+

No posts

+

+ Get started by creating a new post. +

+
+ +
+ + + Create Post + +
+
+ ); +} diff --git a/examples/cms-sanity/app/(blog)/page.tsx b/examples/cms-sanity/app/(blog)/page.tsx new file mode 100644 index 0000000000..41d4427801 --- /dev/null +++ b/examples/cms-sanity/app/(blog)/page.tsx @@ -0,0 +1,116 @@ +import Link from "next/link"; +import { Suspense } from "react"; + +import Avatar from "./avatar"; +import CoverImage from "./cover-image"; +import DateComponent from "./date"; +import MoreStories from "./more-stories"; +import Onboarding from "./onboarding"; +import PortableText from "./portable-text"; + +import * as demo from "@/sanity/lib/demo"; +import { sanityFetch } from "@/sanity/lib/fetch"; +import { + HeroQueryResponse, + Post, + SettingsQueryResponse, + heroQuery, + settingsQuery, +} from "@/sanity/lib/queries"; + +function Intro(props: { title: string | null | undefined; description: any }) { + const title = props.title || demo.title; + const description = props.description?.length + ? props.description + : demo.description; + return ( +
+

+ {title || demo.title} +

+

+ +

+
+ ); +} + +function HeroPost({ + title, + slug, + excerpt, + coverImage, + date, + author, +}: Pick< + Post, + "title" | "coverImage" | "date" | "excerpt" | "author" | "slug" +>) { + return ( +
+ + + +
+
+

+ + {title} + +

+
+ +
+
+
+ {excerpt && ( +

+ {excerpt} +

+ )} + {author && } +
+
+
+ ); +} + +export default async function Page() { + const [settings, heroPost] = await Promise.all([ + sanityFetch({ + query: settingsQuery, + }), + sanityFetch({ query: heroQuery }), + ]); + + return ( +
+ + {heroPost ? ( + + ) : ( + + )} + {heroPost?._id && ( + + )} +
+ ); +} diff --git a/examples/cms-sanity/app/(blog)/portable-text.tsx b/examples/cms-sanity/app/(blog)/portable-text.tsx new file mode 100644 index 0000000000..7ec07c5692 --- /dev/null +++ b/examples/cms-sanity/app/(blog)/portable-text.tsx @@ -0,0 +1,49 @@ +/** + * This component uses Portable Text to render a post body. + * + * You can learn more about Portable Text on: + * https://www.sanity.io/docs/block-content + * https://github.com/portabletext/react-portabletext + * https://portabletext.org/ + * + */ + +import { + PortableText, + type PortableTextComponents, + type PortableTextBlock, +} from "next-sanity"; + +export default function CustomPortableText({ + className, + value, +}: { + className?: string; + value: PortableTextBlock[]; +}) { + const components: PortableTextComponents = { + block: { + h5: ({ children }) => ( +
{children}
+ ), + h6: ({ children }) => ( +
{children}
+ ), + }, + marks: { + link: ({ children, value }) => { + return ( + + {children} + + ); + }, + }, + }; + + return ( +
+ +
+ ); +} diff --git a/examples/cms-sanity/app/(blog)/posts/[slug]/page.tsx b/examples/cms-sanity/app/(blog)/posts/[slug]/page.tsx new file mode 100644 index 0000000000..607ee3fbe0 --- /dev/null +++ b/examples/cms-sanity/app/(blog)/posts/[slug]/page.tsx @@ -0,0 +1,118 @@ +import type { Metadata, ResolvingMetadata } from "next"; +import { groq } from "next-sanity"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { Suspense } from "react"; + +import Avatar from "../../avatar"; +import CoverImage from "../../cover-image"; +import DateComponent from "../../date"; +import MoreStories from "../../more-stories"; +import PortableText from "../../portable-text"; + +import { sanityFetch } from "@/sanity/lib/fetch"; +import { + PostQueryResponse, + SettingsQueryResponse, + postQuery, + settingsQuery, +} from "@/sanity/lib/queries"; +import { resolveOpenGraphImage } from "@/sanity/lib/utils"; +import * as demo from "@/sanity/lib/demo"; + +type Props = { + params: { slug: string }; +}; + +export async function generateStaticParams() { + return sanityFetch<{ slug: string }[]>({ + query: groq`*[_type == "post" && defined(slug.current)]{"slug": slug.current}`, + perspective: "published", + stega: false, + }); +} + +export async function generateMetadata( + { params }: Props, + parent: ResolvingMetadata, +): Promise { + const post = await sanityFetch({ + query: postQuery, + params, + stega: false, + }); + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(post?.coverImage); + + return { + authors: post?.author?.name ? [{ name: post?.author?.name }] : [], + title: post?.title, + description: post?.excerpt, + openGraph: { + images: ogImage ? [ogImage, ...previousImages] : previousImages, + }, + } satisfies Metadata; +} + +export default async function PostPage({ params }: Props) { + const [post, settings] = await Promise.all([ + sanityFetch({ + query: postQuery, + params, + }), + sanityFetch({ + query: settingsQuery, + }), + ]); + + if (!post?._id) { + return notFound(); + } + + return ( +
+

+ + {settings?.title || demo.title} + +

+
+

+ {post.title} +

+
+ {post.author && ( + + )} +
+
+ +
+
+
+ {post.author && ( + + )} +
+
+
+ +
+
+
+ {post.content?.length && ( + + )} +
+ +
+ ); +} diff --git a/examples/cms-sanity/app/(sanity)/apple-icon.png b/examples/cms-sanity/app/(sanity)/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bc571d60bf69a93d5debb376809e95effe5b9421 GIT binary patch literal 3682 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 bE2>4$rH`-UA(7WFA&HKrKKw1rF8cofs=mk# literal 0 HcmV?d00001 diff --git a/examples/cms-sanity/app/(sanity)/icon.ico b/examples/cms-sanity/app/(sanity)/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3f67d3e8e86ed351662fe8528516d0e8662c38e2 GIT binary patch literal 4286 zcmeHLOKa3n6psJEmFsQ_E`*{*MSL*U5w(<3>-$xu+KRfp=bm%sjvy@J zpQuEN-2L0BRP!cs1LN=R^_d8_ef?p9qh)ljoR%y2=hvR=$oFRKuCk!$?Ci`enn z`ya%utrM|&vk2*t=!A@c8w?M}G>lK`1-U<>p)bwQctDComxg^3?maHWbmBY5 zscQ@?H-%@Drh^hjpB1?}vAo8^cNVZJ8C(bQ?*ndIHxBvW=xGIekI85|s~|nZO5XYK z$-vcN7T6cZyOff3pFG#Gz2(n7roCb7IseVX%&ZX@zb~!g?jx-rJ^iZhn(3PiuixZg z7=X(1zVAj6GncP3C~6+mG(8@C+Gicz3f{iYS-AFPZr+T9e_c}+=XzC)jBA*kGc2Aq zrn65C2H$^9AN39}j%PX{JjWkD8(6V1c>EDQ*_YP)n?^pTcnu@=ZSE5)$<#l z@mw-Bo%5}4$>uEDxW2aRZsFoSjD`tl1)B?Y68nrh4bZraF;m3}-L4bd=U0Cyv+n%=vJ*+cs%E zsem|n;W_6!FNTY4>yT`g(0_$tbfR1jnxooRzbD#wBwcEGUL~B}tupt$jIFz*P=9aP eeE;|5aUOw9DtQLXRP6lC&-`@$cK;s`_x&%0O+m;2 literal 0 HcmV?d00001 diff --git a/examples/cms-sanity/app/(sanity)/icon.png b/examples/cms-sanity/app/(sanity)/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6320b50010db12663587352ea09d398afbe2e04d GIT binary patch literal 10466 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�j63DEA*uxjMde{3sn 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 literal 0 HcmV?d00001 diff --git a/examples/cms-sanity/app/(sanity)/icon.svg b/examples/cms-sanity/app/(sanity)/icon.svg new file mode 100644 index 0000000000..1beaa9b757 --- /dev/null +++ b/examples/cms-sanity/app/(sanity)/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/cms-sanity/app/(sanity)/layout.tsx b/examples/cms-sanity/app/(sanity)/layout.tsx new file mode 100644 index 0000000000..0e8faaea8b --- /dev/null +++ b/examples/cms-sanity/app/(sanity)/layout.tsx @@ -0,0 +1,23 @@ +import "../globals.css"; + +import { Inter } from "next/font/google"; + +const inter = Inter({ + variable: "--font-inter", + subsets: ["latin"], + display: "swap", +}); + +export { metadata, viewport } from "next-sanity/studio"; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/examples/cms-sanity/app/(sanity)/studio/[[...tool]]/page.tsx b/examples/cms-sanity/app/(sanity)/studio/[[...tool]]/page.tsx new file mode 100644 index 0000000000..f3d0315aec --- /dev/null +++ b/examples/cms-sanity/app/(sanity)/studio/[[...tool]]/page.tsx @@ -0,0 +1,9 @@ +import { NextStudio } from "next-sanity/studio"; + +import config from "@/sanity.config"; + +export const dynamic = "force-static"; + +export default function StudioPage() { + return ; +} diff --git a/examples/cms-sanity/app/api/draft/route.tsx b/examples/cms-sanity/app/api/draft/route.tsx new file mode 100644 index 0000000000..38bf40eab0 --- /dev/null +++ b/examples/cms-sanity/app/api/draft/route.tsx @@ -0,0 +1,27 @@ +/** + * This file is used to allow Presentation to set the app in Draft Mode, which will load Visual Editing + * and query draft content and preview the content as it will appear once everything is published + */ + +import { validatePreviewUrl } from "@sanity/preview-url-secret"; +import { draftMode } from "next/headers"; +import { redirect } from "next/navigation"; + +import { client } from "@/sanity/lib/client"; +import { token } from "@/sanity/lib/token"; + +const clientWithToken = client.withConfig({ token }); + +export async function GET(request: Request) { + const { isValid, redirectTo = "/" } = await validatePreviewUrl( + clientWithToken, + request.url, + ); + if (!isValid) { + return new Response("Invalid secret", { status: 401 }); + } + + draftMode().enable(); + + redirect(redirectTo); +} diff --git a/examples/cms-sanity/app/favicon.ico b/examples/cms-sanity/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..af98450595e8b8efd9e505cddc5ed705b665a4d8 GIT binary patch literal 15086 zcmdU$XN*0PW5y#)gXL_h{3FkJ#X`oXpI?bB5#G)w~S7Qa+`W<1AB#X3C=?&UhvAO=|S+ zQpCR%R9`2h-z)7_x}fw(>6KDpfVd}*GI-!c2H9UDrKwS>=#NU@Ddj7@Q4*g7FEYrY z16}AW?v$zq+9+i!-B+T1{bG*9hmHqIUn`*-8^w`osM0?vVJi}rWb@IzKnYt#l~PpE zZq^t6O{HGFdQ!P^WzlxPHWGIHcd>D{}x={sTUg9&WW6602m zQnBH6>!yx}4jn2B7A%n2vuDesNt2{;dd zh7B@$^l0CFc@ZrPKrArsz@`@AQzbS#)CVarSbb!I8ty{PH zk}X}jv}9#v$)iV)Gb-#m>4qUO6)cT2PV3 zpI*Is$&Ww&XxZfC$&*s6RxM+zLWK&_s8J(BjQt6=fsGRsIez?@ix)2jvEROZTiUm8 zAGA}oYE|jjv7_)^<8Q|B8-jA4x_+h43HdwG`akQZ{rmR^u|IqEOoj~`7J{KpojS(< z>D#xjnZIoEL8L15HMT(z#WR80C=8eJ6m1l4LW@cu}s8OTj^Upsw@4?*W6SJES z?6iS9&)0pV`lp_$Z=3e*+h^9ZUK>rCG?5uIW|;4L`0(N0^@=YH0qjNg{kCh@PHx@0 zWueQ33l}2#^Q~LAHa;_LgYTK1p6(a3o6pAd+gv_56 zTeSG07MmF9|6)G^Q&#}`NMn_Um~~0isUI6)0h8ZU;`^Lf>f{vqND)`@P9?$9TK1fc;tCyQ6!+t!2dtUtke$_><;tw&q0@ECem?{wl!rS3|>b6OetkXT}@W?k_+rGrYh zl>Vi}m;6*0c#P>*l+@N$%>EBBFmQe{Fc#%OC9f_nmD)f~S=dohhj8vYl zv6`=x&=pVWsLX#+q8mig9W$TUxvt*}bzk7L-lNk`>Y+?Gm163T#zTyXvm=~)sb0N0 zaaweuJ6Zx`(zaEHG-%L3KK=Al6T>DZPhLQGq zy?Yqg@b-sP&7-d+WX~-}jDPy{>9TCuG9i8(A^wLgY}(XWZ-_f3WuJ2&?4j-5yH}Ph zSrTpU9$VNX@qMmWifEgC6yB+S|Ng&-_q*kY6P-PKR*oJ$D&4wui>NOkgH3F=P`Ve$ z3oFArk%&op&JP9oiCbR3eqHX}yJupw-hGKM`tJs8Gp0rz*J9ak*s!6TKY!j}B<@Qe z2xHHihfO=4d&@$equD1uh)vR!D_5jhvt}W(F>o+n#QfeP*=Ky4KYza26Uxue7xt`( zwGs1UFEoa2%CXJd6_q!V{lS9=%abQh(nAuU?8F#8U%^agA*f5vUU zO`A5Rzftd-H*XGp=j=}sgJvHomR>El^LJEU_xn@7oB=$3{5YoN^y$-PoVT`!UrwAj z(d>cRyeL=eJNUAXP0n?!TD8j9&dbY_&Ye4l$oA~n)9gP*+v9QJq3wP5KkQGLGDTj# zd};PI#*Q5uVjI0fh76HSn>LxVdoDY_;0u&tn{OaexyiTnfB*h{;fw|QLCcpf4;xpW zFkyme8_o^J(yQeMlzfjrrqcE2$dM!Fd_g$&SWBkEa^gaG!|Jt={ z=KFH#39++i(IT_Yx^(H%ko++GcKr1<|Ih~0rcI04uj!BQ`Z)V#=O2Y;=E9`s-!ShO zsSe!pFZPSf|L}>y>wkcu$o#Ju7{d8q*e4a4|Eapd)ji*PB>xlpq$2UZRgc#`wqyC< z*e8K8>Gg-|00V0nbj5maWeG|22l`3AlJ{NwJ|l|`ufMHXz7k^tYsg%g=)+?N8;M_9LtdS2KI3UcQZk+)cWFG|X4L^R?nKNgM zJp7#1#y{fx7Zcyn1ijH<*t+zGuIF{p8Z6OUCbF zJuqm{AfqFUdUSJ))2_z+s>OeFWelDY)Ji4$N2uccI^tDkzj4ezJD0s z6WNgVqu%*n4eJGq6%4dL{ - -
- {preview ? ( - <> - This page is a preview.{" "} - - Click here - {" "} - to exit preview mode. - - ) : ( - <> - The source code for this blog is{" "} - - available on GitHub - - . - - )} -
-
-
- ); -} diff --git a/examples/cms-sanity/components/avatar.js b/examples/cms-sanity/components/avatar.js deleted file mode 100644 index 2d21e70a53..0000000000 --- a/examples/cms-sanity/components/avatar.js +++ /dev/null @@ -1,23 +0,0 @@ -import Image from "next/image"; -import { urlForImage } from "../lib/sanity"; - -export default function Avatar({ name, picture }) { - return ( -
-
- {name} -
-
{name}
-
- ); -} diff --git a/examples/cms-sanity/components/container.js b/examples/cms-sanity/components/container.js deleted file mode 100644 index 92e559ece3..0000000000 --- a/examples/cms-sanity/components/container.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function Container({ children }) { - return
{children}
; -} diff --git a/examples/cms-sanity/components/cover-image.js b/examples/cms-sanity/components/cover-image.js deleted file mode 100644 index f6a47154d5..0000000000 --- a/examples/cms-sanity/components/cover-image.js +++ /dev/null @@ -1,38 +0,0 @@ -import cn from "classnames"; -import Image from "next/image"; -import Link from "next/link"; -import { urlForImage } from "../lib/sanity"; - -export default function CoverImage({ title, slug, image: source, priority }) { - const image = source?.asset?._ref ? ( -
- {`Cover -
- ) : ( -
- ); - - return ( -
- {slug ? ( - - {image} - - ) : ( - image - )} -
- ); -} diff --git a/examples/cms-sanity/components/date.js b/examples/cms-sanity/components/date.js deleted file mode 100644 index 21c2f0921b..0000000000 --- a/examples/cms-sanity/components/date.js +++ /dev/null @@ -1,8 +0,0 @@ -import { parseISO, format } from "date-fns"; - -export default function Date({ dateString }) { - if (!dateString) return null; - - const date = parseISO(dateString); - return ; -} diff --git a/examples/cms-sanity/components/footer.js b/examples/cms-sanity/components/footer.js deleted file mode 100644 index 81422c7c6b..0000000000 --- a/examples/cms-sanity/components/footer.js +++ /dev/null @@ -1,30 +0,0 @@ -import Container from "./container"; -import { EXAMPLE_PATH } from "../lib/constants"; - -export default function Footer() { - return ( - - ); -} diff --git a/examples/cms-sanity/components/header.js b/examples/cms-sanity/components/header.js deleted file mode 100644 index e6dea52099..0000000000 --- a/examples/cms-sanity/components/header.js +++ /dev/null @@ -1,12 +0,0 @@ -import Link from "next/link"; - -export default function Header() { - return ( -

- - Blog - - . -

- ); -} diff --git a/examples/cms-sanity/components/hero-post.js b/examples/cms-sanity/components/hero-post.js deleted file mode 100644 index cbbab8be35..0000000000 --- a/examples/cms-sanity/components/hero-post.js +++ /dev/null @@ -1,37 +0,0 @@ -import Avatar from "../components/avatar"; -import Date from "../components/date"; -import CoverImage from "../components/cover-image"; -import Link from "next/link"; - -export default function HeroPost({ - title, - coverImage, - date, - excerpt, - author, - slug, -}) { - return ( -
-
- -
-
-
-

- - {title} - -

-
- -
-
-
-

{excerpt}

- {author && } -
-
-
- ); -} diff --git a/examples/cms-sanity/components/intro.js b/examples/cms-sanity/components/intro.js deleted file mode 100644 index e8578bef66..0000000000 --- a/examples/cms-sanity/components/intro.js +++ /dev/null @@ -1,28 +0,0 @@ -import { CMS_NAME, CMS_URL } from "../lib/constants"; - -export default function Intro() { - return ( -
-

- Blog. -

-

- A statically generated blog example using{" "} - - Next.js - {" "} - and{" "} - - {CMS_NAME} - - . -

-
- ); -} diff --git a/examples/cms-sanity/components/landing-preview.js b/examples/cms-sanity/components/landing-preview.js deleted file mode 100644 index 3ee78d059b..0000000000 --- a/examples/cms-sanity/components/landing-preview.js +++ /dev/null @@ -1,8 +0,0 @@ -import { usePreview } from "../lib/sanity"; -import { indexQuery } from "../lib/queries"; -import Landing from "./landing"; - -export default function LandingPreview({ allPosts }) { - const previewAllPosts = usePreview(null, indexQuery); - return ; -} diff --git a/examples/cms-sanity/components/landing.js b/examples/cms-sanity/components/landing.js deleted file mode 100644 index fa2b91126b..0000000000 --- a/examples/cms-sanity/components/landing.js +++ /dev/null @@ -1,34 +0,0 @@ -import Layout from "./layout"; -import Head from "next/head"; -import { CMS_NAME } from "../lib/constants"; -import Container from "./container"; -import Intro from "./intro"; -import HeroPost from "./hero-post"; -import MoreStories from "./more-stories"; - -export default function Landing({ allPosts, preview }) { - const [heroPost, ...morePosts] = allPosts || []; - return ( - <> - - - {`Next.js Blog Example with ${CMS_NAME}`} - - - - {heroPost && ( - - )} - {morePosts.length > 0 && } - - - - ); -} diff --git a/examples/cms-sanity/components/layout.js b/examples/cms-sanity/components/layout.js deleted file mode 100644 index a2e161c793..0000000000 --- a/examples/cms-sanity/components/layout.js +++ /dev/null @@ -1,16 +0,0 @@ -import Alert from "../components/alert"; -import Footer from "../components/footer"; -import Meta from "../components/meta"; - -export default function Layout({ preview, children }) { - return ( - <> - -
- -
{children}
-
-