+
{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 ? (
"
+```
+
+Your `.env.local` file should look something like this:
+
+```bash
+NEXT_PUBLIC_SANITY_PROJECT_ID="r0z1eifg"
+NEXT_PUBLIC_SANITY_DATASET="blog-vercel"
+SANITY_API_READ_TOKEN="sk..."
+```
+
+> [!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 ? (
+
+
+
+ ) : (
+ 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 ? (
+
+ ) : (
+
+ );
+
+ 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
zcmeHt=U-Dx)b6B!p-C?mqzH&~gd-{~s3=7URhmEm0TB=o0VyG&h&08cfb>KZDN3)R
zKr9gjDbhO_rT5-Ka<}Kb_xt?=-Vgch?AdF|-ZN{h=Xqx0OpLE`upVUv0C4E*T{Z&%
z4*i7#6f<<$xZkq}U08heZ2SRWk*;y!F~)b4Gbd$7pLww>qlpv&PLzt({mXAM3P+pLg8{cFniHD1Pi)93%p
z#9S!Qb3O7;!sUnry+T{=E(ep-#jFMja{qqg`oi==@MFP8bkC8bLb1aWM+8r%CwBQI
z#&YK6lt0qkue#neJzQC{y)gK&y3gU^^2)w`X0=;KPL$h^4~*sDoNZjCs#}Lhy%XNn
z_@PvrB!
zeyX4?=i{O?WxoOT*C9gO`Ru$U_X}*riVLc!1!<6V{yt8QMl=uz2WhWz#eG-Hn;W0R
zs9J#7kBwgMLLKQZIVAzs);K`0;`3(C$pPKd+Zy+%OO1^+M8ylZ
z74pm7Rt^>*U3=$3{q_sqhqe0^-tK_^~j
zbt(Q`Lvf>9w*dK_CY`7c(2@b~M=>|nI$e&HOdewO+<%EZ3NThXJFm1qC-DuK=fdIey
z;g+$G?>~R%hZ4_~!ntbqBonkjPup0}Y?c+1bukd4AqckWrK~HrnXtjTCKWpfw0$=8
zgag?0?HwRN&-K)b@!1ANINE`(oxDMZk(Rc!7-PNC0j<4O{ea9bG`fLB>tI
z1FY)BL24s`VtxX
z=)9Z${-@jQlPfes%>q|*Xa`F)|M%we)CSpcB0hX^elAIU`yg|+(RYvLLp<;Qao?E!
z&**OD=}D1M!S=V|<9i}($wUAo`4R%=y9~|a`<-7T`8d|p4&2H1G2M`Hjbg>rFxEGYPP!=(JMR^f9?T4U{ALl7~v
z9>tXHPPD-(3XwN%=5wByk-{!Gmv^k`YPAyo4DaTOU^Xo67p@kr_dF})jS%Hzmi>j6
zCg_{M2+FhaJBx2h^^5)47|eaA4X0
zD*a7K^+b|MG$HR9hOZYE0j^vE1mW8ayDikyO~MI{Pw4~vh?roMv<#A)b!wVd=^PBi
zl-18T29eF_$^5+$%^1GYhT=H#781;uYm9c>2*TZNT9--)*JnX=LXK2p;?>*Hfahf&{MJ-d
z&X%W45!^6I+wBxg0tQ$G=x%XEWa{VUHS~&gE8j#E%olRih~6M@~U6BQk*+AD{&p~mQvRr
z+&!z5&QzSW_ERPp3ql9L2o1UiZuct@7QldtK8Xi(B}4VkV2`
zm!Y|#r2@Yc?;Y{e)|3g$1qbS9_un8k=>6Xu)`zGs?bZ(x!)#vf4XVsd;*@BE6Ig-D3XXuupy+q
zA%wGlg>OGXIrFo>-98Gah4|GOweC+u{7=7PM7(zCF0I3RRlS)fO4;zEC=vwTTzahD
zZCo{0RvM^=_}X^`bD%EROQ^(eoww6HWOBUCD?t(mtuI?YzFAbx#IVmGan5;mm6t+Y
zF2t)+*&_;=`L=~SpWCQ0b)A9{UbKy!9a$L<3GhJ3O<~?D|GXpHcZ**>qM+c
zCeh4H!!qIj>?$hhc#QB6__^%ZRe9nKRaFQ773VR&Q-U1k20{kO^sDDTj)xET78VJt
zJNz_-x5Esu*WkV!^Im~X1X
z0m3uMX_Zr%-THwHxr>uNM0V7d3)_^1;OE8Rk7zF)fW@k~u!0v0xK?W~C|t{$9){oq
zRrBUS6A?mbr5F1qay;37B~j@LTo$`F=7*pg2(GS=v^TZp>PIHf#!}
z@algo&J?{%MJOMyo!{i(wcrdMob#$qZzA+Z|qM4tw_mkkPihAKsM)
z%uW_=VoLP7U%k4aX
faLScfyP!@$(yNe-7(=9KtYpa$h$)dhn%5R(CFHY0v@iSzFoCU
z3ey-mGo#3eeJ+U
zqE2$a4$}+s&$SQ!+wQ9cC4Si$_(^2q+$(Z>B}SHkcq9foYt1Y{`{|2h#fDIcVZ{3m
z&1WFvBJ}-S*@4(_rCM9#bf54;f@C)X9R&fHPq$G6I_jkTx2&M^I0REnqX}Z}a4K2o
z08i3Kg4?bGAIXpN@gyfC*y{`4%P(XN5yL+D99iF?nGG^715#={*Gi6^`TkJt_U0yp
zQjLI-IE16P!BD^OK;T1kziB!8Gt`m8zPDBW;HBl^y9%VxuRyd@4i0c{TG+`~awW^=
z`y9tMq;&WJ$>VQ7xLJyY2Nac!B2}91tq1oK2H>bpRRu(@j63DEA*uxjMde{3sn
z@>F0a2sJl8vyyF#TgQ_f)|crn+(@b>
zI{WW**sX)k?#8*HjXgz%YrQllA`+Nb4bDa#s%P1&n*R8^&_m!vop#%JkdM5(C}GMB
zh7~osfeacAd>;=?{)Pn`t=-GFWpqFRFXUVI=sHJkaDh2Az=~!wTYzCsaC
z+LWGj7Cfu86^^|gah@IfunKB~y@Yb{6^GJ!&xfc`Jl!xU93%Q)dabe!LlS0VS$_--
zMxc<&&&$tp4cG%OT{glC4@9yZGSWdh*2)r&m4^|8;}U!=U`0;e!z9!pX0{hUtH8j&
zLI%8`$O$(rdRcVFk|p;dc3w>NAuCYlNifI?S~ekt!~(7e(z!IfVQ||BV4DLOeaiyw
zqvqN%Y%dz2geG^VK0yYwbskell!>T;Sv&`1Jt1bV5{F^3ub4#wboYfSA_hJU{rZGL
z4l$c(L6GMqE7>heA522o;Y<z?zw(SyJMiUelyVnvnsjfX6AudFzHd8Aw+I%vDjI53>0a56H+PFM4&DERSA
zjf`$fq)EvG19^iy!*Q*OyO)N0b218&>crLQnGW=yj6NQ$%mSQrW=iY6`)UuL6POd1
zxuMO-9=DivrdEN1#9J(16krS!vr`SJI!LgLL>Wgu$9YR8qDG=I4f1^I`d-lrjj#k^d?6
z+5N*SJ6)_OcZd`qaAc0geu7Q~uLV+=;U(=(%
z^a4Knc0y~k!|ui@-oy-6P~D@BmfkB^<(rJrDUy+a4bD3fQ%?k+`zXx$WfQow5BR=#r;<(nX?E(XZVROi%m+UK{x
z)iqR9T@sK5s3tGO*+=dUczY*5et{0W2nbrm90+B_i6z);d0U{&C)DipnS=yFX3>Pf
z;X7(7WD5#aWF06;!@w0$V^7bkqTk*iDBrz{5Z5{f_m_;;iA`#MQLIo@!QsoIv2%pQ
z2iGoIz#tYD4FIpnjpYn0=8y>V1A=M7=LqK$U=JDvAD>l&1O0Wcj853Gz?KrgEC$dl
zmvu=frXu75dV%%+>gT2@g6N(&K(E627NQv;yr@DPZ6+0Q^7S@Vxat
zR=`vCZvnhcmH;7A6Q?4i*B9lF8<6mF)KrpmmH)p|{~~1DD^@ZDN^c2SMoyV9VP)Zf
zWME?c807~2^MzSPhWIJ~J8nQ|e9M8PoQ^3%rcB&JpFIh{rMP(Xa@R|TVRQ;9Y=rwS
zFvQ;MtL4ZwBX8(}SW)1~0+COs){FfiSW`w^M45X!@U@FU+3(Q|w62!=Y
z^i6q|z6^c9%i}uZaQea!LRYkI(l?{+66|+(W`8F$RvyaWeB#KBB81zaE0|-1O(Y=Q
zqk05v)eJv~;O=6^YRW?eeKoAn5kDbwO&E}_u682zOATzksXfKqF957Bv4MiDG4RXl
z1d^IUKgSna4YkoF&i-RGAW1-n=zN$w3Xm{R@Cedvaso&F@>Q5?CM?h)wk#y6zp1xj
z66;LtTBH4;>VZ_2?Gy&>J5^=`1<%gMXIOI=^Pp@2dT}#`d3vcaf7Kt}
zjv!rSf>hT6a5+>|23{Jr_v}##>Ps+4chY|ffNG-X6Lx(wEZPk@qt&GXP!tB&b-C{EYKP@+t;3X2C^#Tr
zP!@r_XYx1x+1xjF0PRr>pe=v^{=T?8z1Q*eQmZy5z(rORGL)*Yf4@#)Olly*H08Bd
zUs{~YyeATXvWNw$=CN=s
zTSzg}xUri`&_P2rIc|n--Pt_|@MP)KfO=rAcsDv6Zd591Lg6C834d#Ojl1G-_T`2X
zADawDRq}Zu%?T=Pa|36q>a3#)lTF$wB$>Z0Rn?@!2EMJWF83~d0CR?aU!zH$uqh5u
zFpCJNj&C>5V@_-CuCc?$-
zL3f_%gea`I&`-JY1Bh(+A_$o_w9JRuLVB|(z(@6{U5|{q)|VN)A91-dQF23Gp(INv
zt?8l-3cQ`89XyL$uP$knDnOxTl)Z%T|*KTEi2iGzELWq%Jy=R!g=&O{2`@}h>7OGBU`
zCjHoWij>pr&iFOl2{b^12@QL10!LfpJ~nrZy3u5cVfaW*!7iBJVY)}8d*luxL3`yr
zh-p2rfX$U2v}bln$TLRAjjQ?p&Ja1+0-VifHE~p?si{10>hP$6UyuQhP66MXc5_Sy
zVjx-ujcXgx$o93Wg0C5nzu-q{vyRXLA+X7LBD@UvN#Ub6UL;WH
z+>Ki#WX7?gLoW+bOb&O4>@CMtN~JdLFRJP*z{OGE2FZV|Ij*hk1f4%0(f6f_NDqyF
zN<$ukKYxr@gt9qPd?@TVYgOR_wZR;J`=)AO1LIPmT&h%^eA!DTG!GbvUL@EhAY9my
z*N1;RQZw=z$7@s5CiE>~3qTYFyb9KLT;hrs>lgd0;W93i+}`y8S^19oRAQin|v+c#}tM>*w|`rLm33W
zt2PQ?=<87X2FFO+<+5ZA^gmUl@3q>JX>t$S&GWy8T$rd323aRT*=cVAeKCX~tm~M*
z7T-ifdFDM=q;tc+QUuT;NKjL?lkF0>sH+_e$+O&W0~xvlWW=*|pw_kfaFu=5hGt_3
zu!c*_Gmh*`tgy+-`kiUKwxy{TOIYBBpufDz}8WB9a+{u@G%f6~W5|bYvN`=clpl6r_?1C`5PKyS`P`uenc^BN-j1
z8I{}+#YtltNZ&c6X$$ukIWX!a2+wJ5HWS_L4d};g!q=9&jHMZse^rJ8c;Xev#e)qe
zYS$_AxJWXA$~u)+STJwS2vOIJonOFhOAUOx775ek$3FM;z3M#mHHcr0RRPENw`v11
zE$<~MK&N)dF0c3R#^D>5*wooZ4l|toYACWrV3*grCVy}EZHuTIA~#VO;g!HCVwn95
z^J<9}=B#TjNViN#pp=t8XPquz+1$TcpYkLGl8#zFsWo2Ta6FQYf}7gJooXDod&G&f
z|0%u4;*sM{|4e(+(+bsdpYdLhEG^yO56kF`%C17e7w4(J(SG+lSO2xL&&b`JlI?~B
z!-LxDE7L#HX&o*k5$ByGZ%8?jcY{rpm@o2x|QlYYj{8hw*sd^{Iox6h)fA
zfj9d(ln{o|!tjDz;%e&SFj+_&^==8=KNM!>T)tq*e9^}Zdw%Flht{+Djz{zpyV>!e
zyPhgv7%=NfS$=xJ5SIY5{@8BpF^L02*{7WbkCTLvBsVbeC^^LK+4}A
z>DC>;FUj@xLsI@Xza@p({5{nm)5dR(8D_McE&CkX=fxkkqqX6|C_Nk!U>F;aZ!|kI
zLNt1h49O(PIfl8wmMmF9#LFOfxIk;!r&U+J{_tTkXFZI2S^iJ4KW1exN0ZV#wCti$
z-`a{>M6PULnjp61{Og-q_fk^0oar5kc=n;FYBqg!Q>M{wZNu3|K0|As8V4$2gNk_h_hCY44_+Wjb144XPZi_Jv-O7Ld90(3A
z>5cDpgzmX3K}?Rnm>>yl>NfOEbst?1o*hqT{FRIUI9s8GF>j(gT854>{Ds*VHW&sw
zL~P{*8uwCqG%G7TuSHk|IEHw9?jRn#)c#M3E|1lm@^cYYIyy>uz@}xzPRH%*MLPYZ
zYkql)|N2N_r~5>z`10^k_=RYQ?@=_+VL-h$uyZ*DgYTLUQHPY~!1hCgpYer^yx-%7
zWxNoTv-*Ju+w9oH38@+IGK*1X4wHsxt-IFr|XLIec|PAg1K
z9yfmH=oM(@WW}0`UEdU7Ku#73l}JB0r!asOeQQH=JogNn1(COt%!m&9;B*!*+Qo*J
zE&(H$#reahAT7F?x%->|Udjl5_;k2llper_ZlrjbazYrat{wS_sEa`Ju7Z|tlY@3jE*!n6Ylr5-9#7scJOgp6
zSWzWN!79X2Qdk0Wt(F|=bjRU-j4KOJHJ0TB(2k)rN=b=LJ3t(CT1SF|iWT##&~~W%
z6+)Z!PN516w9hg{Y-N^K;mUr=(NrM-cP!Q)>2@c~>b8A|P44*%kmi>fzGFS75&r2D
zW|0MyJ*W%toyt}#WbL`J@Y`{(U3qx-79R}k@mWUnS@yq9r$x=pfAFn8r(}>$gf?ff
zXk}S0brhrwmPL`Ab8DMu|Ffl|x!8Rl4i*SXCgGG^qT){ZO7rH2oGqsZ0Gxc<);8&c
z#g9qVef>dBF2;4$&V2p~F|}txqKR{8Q?XcdS!j@RnDdjrq_=Q?M5SfF^AHS3NkpO*
z`Ks2f_#?C9n3|w?m9yrg2M5>mxtZ6vI-!uayWeUqdoe=hV@&*0IwnUhXW=)?cqFI#
zDS(dKxTy2N?fKNQ;@EKA=UC@Zhv`A=I5~&TRLepZ?k;x9GTaVS0XKI%Aows#bWSMl)v1pSI9on_?
zxyh4GFLp9e9Pa~@{*k+!-uT|P#(LoW`tH7pyP55MNd}GfPM7|pWqQi7{M#iKh#LA)
zoQK#@IGKx|M|5SEJ03(wgtvv2V;XcvA`hoX05pQ1ZR?$`9bP!2Fa_cv-2Z#NJqG+eajv?byav1qB5$KLc_+;4+BhGpMj$h#4TM%@2NUi9fr?U(Zyk)x_{Qq
zF}DBhc3k%%P7Pwy0%T&JJrug|#qM|8oGCf@*hD3C>Zka6i`M4ces555heSRc4w*PO
z=y7f}mLCNQ=R;^!G63|LtGN;%%`(PnJDNC5_yL%kv9Q%6I_}4KLaj7
zy4qsUDKPV_jas8Yd~qWJ{S8mW1iT+S^;90x)CK%v$4{;Ov-dlSMrw2eeWCPDj{QpG
zZ9RG*9u8igT;2{6tbDIFJXf3Y0*>xjuO0EVN)5nkv%rCpKIP|uo%i~1`(rpd_BML%
zo>ussHxpw>kk~pqvpLN{>-ch@>2WCVI0%UsEz3GwhyvtqkGy^@AF%<}^Sj$)zS7kf
zf+j5^?4BtB@cCS?(P-&w<9S7e-L2J^9$&ulfW8Y>N9x2}vyWBW2^3}UZ|A36|42uI
wEQ9j*C7FslHrwBOyoE6dvolIjxTqaQ#MAHx6Ct^fi~xYXuJPrPi`dBj19Fm?l>h($
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{pki6&-$D3
zyt$IE^|w5$B$AaP+DMBu_V96mMq3Ci%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}
-
- );
-}
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 ? (
-
-
-
- ) : (
-
- );
-
- 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}
-
-
- >
- );
-}
diff --git a/examples/cms-sanity/components/markdown-styles.module.css b/examples/cms-sanity/components/markdown-styles.module.css
deleted file mode 100644
index 95d4f8b041..0000000000
--- a/examples/cms-sanity/components/markdown-styles.module.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.markdown {
- @apply text-lg leading-relaxed;
-}
-
-.markdown p,
-.markdown ul,
-.markdown ol,
-.markdown blockquote {
- @apply my-6;
-}
-
-.markdown h2 {
- @apply text-3xl mt-12 mb-4 leading-snug;
-}
-
-.markdown h3 {
- @apply text-2xl mt-8 mb-4 leading-snug;
-}
diff --git a/examples/cms-sanity/components/meta.js b/examples/cms-sanity/components/meta.js
deleted file mode 100644
index b228ae821c..0000000000
--- a/examples/cms-sanity/components/meta.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import Head from "next/head";
-import { CMS_NAME, HOME_OG_IMAGE_URL } from "../lib/constants";
-
-export default function Meta() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/examples/cms-sanity/components/more-stories.js b/examples/cms-sanity/components/more-stories.js
deleted file mode 100644
index af68ac0f2c..0000000000
--- a/examples/cms-sanity/components/more-stories.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import PostPlug from "./post-plug";
-
-export default function MoreStories({ posts }) {
- return (
-
-
- More Stories
-
-
- {posts.map((post) => (
-
- ))}
-
-
- );
-}
diff --git a/examples/cms-sanity/components/post-body.js b/examples/cms-sanity/components/post-body.js
deleted file mode 100644
index 9fd971ba60..0000000000
--- a/examples/cms-sanity/components/post-body.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import markdownStyles from "./markdown-styles.module.css";
-import { PortableText } from "@portabletext/react";
-
-export default function PostBody({ content }) {
- return (
-
-
-
- );
-}
diff --git a/examples/cms-sanity/components/post-header.js b/examples/cms-sanity/components/post-header.js
deleted file mode 100644
index 3c38dcac6c..0000000000
--- a/examples/cms-sanity/components/post-header.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import Avatar from "../components/avatar";
-import Date from "../components/date";
-import CoverImage from "../components/cover-image";
-import PostTitle from "../components/post-title";
-
-export default function PostHeader({ title, coverImage, date, author }) {
- return (
- <>
- {title}
-
- {author && }
-
-
-
-
-
-
- {author && }
-
-
-
-
-
- >
- );
-}
diff --git a/examples/cms-sanity/components/post-plug.js b/examples/cms-sanity/components/post-plug.js
deleted file mode 100644
index b49e286b0a..0000000000
--- a/examples/cms-sanity/components/post-plug.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import Avatar from "../components/avatar";
-import Date from "../components/date";
-import CoverImage from "./cover-image";
-import Link from "next/link";
-
-export default function PostPlug({
- title,
- coverImage,
- date,
- excerpt,
- author,
- slug,
-}) {
- return (
-
-
-
-
-
-
- {title}
-
-
-
-
-
- {excerpt}
- {author && }
-
- );
-}
diff --git a/examples/cms-sanity/components/post-preview.js b/examples/cms-sanity/components/post-preview.js
deleted file mode 100644
index 02691ef3ea..0000000000
--- a/examples/cms-sanity/components/post-preview.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { usePreview } from "../lib/sanity";
-import { postQuery } from "../lib/queries";
-import Post from "./post";
-
-export default function PostPreview({ data }) {
- const slug = data?.post?.slug;
- const previewData = usePreview(null, postQuery, { slug });
- return ;
-}
diff --git a/examples/cms-sanity/components/post-title.js b/examples/cms-sanity/components/post-title.js
deleted file mode 100644
index a15f06616f..0000000000
--- a/examples/cms-sanity/components/post-title.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function PostTitle({ children }) {
- return (
-
- {children}
-
- );
-}
diff --git a/examples/cms-sanity/components/post.js b/examples/cms-sanity/components/post.js
deleted file mode 100644
index 16f67e3498..0000000000
--- a/examples/cms-sanity/components/post.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { useRouter } from "next/router";
-import { urlForImage } from "../lib/sanity";
-import ErrorPage from "next/error";
-import Layout from "./layout";
-import Container from "./container";
-import Header from "./header";
-import PostTitle from "./post-title";
-import Head from "next/head";
-import { CMS_NAME } from "../lib/constants";
-import PostHeader from "./post-header";
-import PostBody from "./post-body";
-import SectionSeparator from "./section-separator";
-import MoreStories from "./more-stories";
-
-export default function Post({ data = {}, preview = false }) {
- const router = useRouter();
-
- const { post, morePosts } = data;
- const slug = post?.slug;
-
- if (!router.isFallback && !slug) {
- return ;
- }
-
- return (
-
-
-
- {router.isFallback ? (
- Loading…
- ) : (
- <>
-
-
-
- {`${post.title} | Next.js Blog Example with ${CMS_NAME}`}
-
- {post.coverImage?.asset?._ref && (
-
- )}
-
-
-
-
-
- {morePosts.length > 0 && }
- >
- )}
-
-
- );
-}
diff --git a/examples/cms-sanity/components/section-separator.js b/examples/cms-sanity/components/section-separator.js
deleted file mode 100644
index b86fb50b7d..0000000000
--- a/examples/cms-sanity/components/section-separator.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function SectionSeparator() {
- return
;
-}
diff --git a/examples/cms-sanity/lib/config.js b/examples/cms-sanity/lib/config.js
deleted file mode 100644
index c8e58e7850..0000000000
--- a/examples/cms-sanity/lib/config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-export const sanityConfig = {
- // Find your project ID and dataset in `sanity.json` in your studio project
- dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || "production",
- projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
- useCdn:
- typeof document !== "undefined" && process.env.NODE_ENV === "production",
- // useCdn == true gives fast, cheap responses using a globally distributed cache.
- // When in production the Sanity API is only queried on build-time, and on-demand when responding to webhooks.
- // Thus the data need to be fresh and API response time is less important.
- // When in development/working locally, it's more important to keep costs down as hot reloading can incurr a lot of API calls
- // And every page load calls getStaticProps.
- // To get the lowest latency, lowest cost, and latest data, use the Instant Preview mode
- apiVersion: "2022-03-13",
- // see https://www.sanity.io/docs/api-versioning for how versioning works
-};
diff --git a/examples/cms-sanity/lib/constants.js b/examples/cms-sanity/lib/constants.js
deleted file mode 100644
index 3db98b7134..0000000000
--- a/examples/cms-sanity/lib/constants.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export const EXAMPLE_PATH = "cms-sanity";
-export const CMS_NAME = "Sanity";
-export const CMS_URL = "https://sanity.io/";
-export const HOME_OG_IMAGE_URL =
- "https://og-image.vercel.app/Next.js%20Blog%20Example%20with%20**Sanity**.png?theme=light&md=1&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB2aWV3Qm94PSIwIDAgMTA1IDIyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMWVtIj48dGl0bGU%2BU2FuaXR5PC90aXRsZT48cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03OC4xNzkzIDcuOTkyNjFWMjEuMDAyOEg3My45MDMxVjEwLjIxMzhMNzguMTc5MyA3Ljk5MjYxWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNMjAuOTUxMSAyMS4zM0wzMC45NDQgMTYuMTA1MUwyOS43MTIxIDEyLjkxNDFMMjMuMTMzMiAxNS45ODIxTDIwLjk1MTEgMjEuMzNaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik03My45MDMxIDEwLjIwMjdMODQuNzQ0MyA0LjY1NDc3TDgyLjkxMjYgMS41NTcxTDczLjkwMzEgNS45NTk5N1YxMC4yMDI3WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNNDMuMzcwNSA2Ljk2MjMzVjIxLjAwMjhIMzkuMjkyN1YxLjAwNzE0TDQzLjM3MDUgNi45NjIzM1oiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTI3LjEyOTkgNi4xODYxN0wyMC45NTExIDIxLjMzTDE3Ljc3MzEgMTguNTk0M0wyNS4xMzUzIDEuMDA3MTRMMjcuMTI5OSA2LjE4NjE3WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggZD0iTTI1LjEzNTMgMS4wMDcxNEgyOS4zNDc3TDM3LjEzODYgMjEuMDAyOEgzMi44MjY5TDI1LjEzNTMgMS4wMDcxNFoiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIGQ9Ik00NC4wMDEyIDEuMDA3MTRMNTIuOTgyNCAxNC42NjgyVjIxLjAwMjhMMzkuMjkyNyAxLjAwNzE0SDQ0LjAwMTJaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNNjQuOTE4MyAxLjAwNzE0SDYwLjY3MzlWMjEuMDA2M0g2NC45MTgzVjEuMDA3MTRaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNNzMuOTAzMSA0LjY1NDc0SDY3LjM3VjEuMDA3MTRIODIuNTg2N0w4NC43NDQzIDQuNjU0NzRINzguMTc5M0g3My45MDMxWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNOTcuMjc1NCAxMy40MTUzVjIxLjAwMjhIOTMuMDYyOVYxMy40MTUzIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNOTMuMDYyOSAxMy40MTUyTDEwMC4xOTEgMS4wMDcxNEgxMDQuNjY2TDk3LjI3NTQgMTMuNDE1Mkg5My4wNjI5WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNOTMuMDYzIDEzLjQxNTJMODUuNzM2MyAxLjAwNzE0SDkwLjM0NTZMOTUuMzA5MiA5LjUxMDA4TDkzLjA2MyAxMy40MTUyWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggZD0iTTEuOTYxMjYgMy4zMTQ3OUMxLjk2MTI2IDYuMDk5MjEgMy43MTE0NSA3Ljc1NTk1IDcuMjE1MzYgOC42Mjk1NkwxMC45MjgzIDkuNDc1MzNDMTQuMjQ0NCAxMC4yMjM2IDE2LjI2MzkgMTIuMDgyMiAxNi4yNjM5IDE1LjExMDNDMTYuMjg5NyAxNi40Mjk1IDE1Ljg1MzEgMTcuNzE3MyAxNS4wMjc0IDE4Ljc1NzlDMTUuMDI3NCAxNS43MzY4IDEzLjQzNjcgMTQuMTA0NCA5LjU5OTcyIDEzLjEyMjlMNS45NTQwOSAxMi4zMDg1QzMuMDM0NzUgMTEuNjU0MSAwLjc4MTQ3OCAxMC4xMjYyIDAuNzgxNDc4IDYuODM3MDlDMC43NjYxMjMgNS41NjY5MyAxLjE4MTE2IDQuMzI3ODEgMS45NjEyNiAzLjMxNDc5IiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik01Mi45ODI0IDEzLjY0MTVWMS4wMDcxNEg1Ny4wNjAyVjIxLjAwMjhINTIuOTgyNFYxMy42NDE1WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNMTIuNzQ1OCAxNC4zNjg5QzE0LjMyOTQgMTUuMzY0MyAxNS4wMjM4IDE2Ljc1NjUgMTUuMDIzOCAxOC43NTQ0QzEzLjcxMyAyMC40MDQxIDExLjQxMDEgMjEuMzMgOC43MDMzMyAyMS4zM0M0LjE0NzE4IDIxLjMzIDAuOTU4NTc3IDE5LjEyNjggMC4yNSAxNS4yOTgySDQuNjI1NDdDNS4xODg3OCAxNy4wNTU5IDYuNjgwMzQgMTcuODcwMyA4LjY3MTQ0IDE3Ljg3MDNDMTEuMTAxOSAxNy44NzAzIDEyLjcxNzQgMTYuNTk2NCAxMi43NDkzIDE0LjM2MTkiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIG9wYWNpdHk9IjAuNyIgZD0iTTQuMjM1NjcgNy40NDI2N0MzLjUxMjUgNy4wMjA0NSAyLjkxOTIgNi40MTM3NSAyLjUxODczIDUuNjg2OTdDMi4xMTgyNyA0Ljk2MDE5IDEuOTI1NTggNC4xNDA0NSAxLjk2MTEzIDMuMzE0NzZDMy4yMjU5NCAxLjY3ODkxIDUuNDI2MDggMC42Nzk5OTMgOC4xMDgwNCAwLjY3OTk5M0MxMi43NDkyIDAuNjc5OTkzIDE1LjQzNDcgMy4wODg1MiAxNi4wOTcyIDYuNDc4NTZIMTEuODg4M0MxMS40MjQyIDUuMTQyMDMgMTAuMjYyMSA0LjEwMTM2IDguMTQzNDcgNC4xMDEzNkM1Ljg3OTU3IDQuMTAxMzYgNC4zMzQ4NyA1LjM5NjExIDQuMjQ2MjkgNy40NDI2NyIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPC9zdmc%2B&widths=undefined&widths=auto&heights=250&heights=150";
diff --git a/examples/cms-sanity/lib/queries.js b/examples/cms-sanity/lib/queries.js
deleted file mode 100644
index 89330900c4..0000000000
--- a/examples/cms-sanity/lib/queries.js
+++ /dev/null
@@ -1,37 +0,0 @@
-const postFields = `
- _id,
- name,
- title,
- date,
- excerpt,
- coverImage,
- "slug": slug.current,
- "author": author->{name, picture},
-`;
-
-export const indexQuery = `
-*[_type == "post"] | order(date desc, _updatedAt desc) {
- ${postFields}
-}`;
-
-export const postQuery = `
-{
- "post": *[_type == "post" && slug.current == $slug] | order(_updatedAt desc) [0] {
- content,
- ${postFields}
- },
- "morePosts": *[_type == "post" && slug.current != $slug] | order(date desc, _updatedAt desc) [0...2] {
- content,
- ${postFields}
- }
-}`;
-
-export const postSlugsQuery = `
-*[_type == "post" && defined(slug.current)][].slug.current
-`;
-
-export const postBySlugQuery = `
-*[_type == "post" && slug.current == $slug][0] {
- ${postFields}
-}
-`;
diff --git a/examples/cms-sanity/lib/sanity.js b/examples/cms-sanity/lib/sanity.js
deleted file mode 100644
index 2492667bea..0000000000
--- a/examples/cms-sanity/lib/sanity.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import createImageUrlBuilder from "@sanity/image-url";
-import { definePreview } from "next-sanity/preview";
-import { sanityConfig } from "./config";
-
-export const imageBuilder = createImageUrlBuilder(sanityConfig);
-
-export const urlForImage = (source) =>
- imageBuilder.image(source).auto("format").fit("max");
-
-export const usePreview = definePreview({
- projectId: sanityConfig.projectId,
- dataset: sanityConfig.dataset,
-});
diff --git a/examples/cms-sanity/lib/sanity.server.js b/examples/cms-sanity/lib/sanity.server.js
deleted file mode 100644
index 4a0d7be3bf..0000000000
--- a/examples/cms-sanity/lib/sanity.server.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * Server-side Sanity utilities. By having these in a separate file from the
- * utilities we use on the client side, we are able to tree-shake (remove)
- * code that is not used on the client side.
- */
-import { createClient } from "next-sanity";
-import { sanityConfig } from "./config";
-
-export const sanityClient = createClient(sanityConfig);
-
-export const previewClient = createClient({
- ...sanityConfig,
- useCdn: false,
- // Fallback to using the WRITE token until https://www.sanity.io/docs/vercel-integration starts shipping a READ token.
- // As this client only exists on the server and the token is never shared with the browser, we don't risk escalating permissions to untrustworthy users
- token:
- process.env.SANITY_API_READ_TOKEN || process.env.SANITY_API_WRITE_TOKEN,
-});
-
-export const getClient = (preview) => (preview ? previewClient : sanityClient);
-
-export function overlayDrafts(docs) {
- const documents = docs || [];
- const overlayed = documents.reduce((map, doc) => {
- if (!doc._id) {
- throw new Error("Ensure that `_id` is included in query projection");
- }
-
- const isDraft = doc._id.startsWith("drafts.");
- const id = isDraft ? doc._id.slice(7) : doc._id;
- return isDraft || !map.has(id) ? map.set(id, doc) : map;
- }, new Map());
-
- return Array.from(overlayed.values());
-}
diff --git a/examples/cms-sanity/next.config.js b/examples/cms-sanity/next.config.js
index 553cc9b836..0a9e8f6022 100644
--- a/examples/cms-sanity/next.config.js
+++ b/examples/cms-sanity/next.config.js
@@ -1,9 +1,10 @@
/** @type {import('next').NextConfig} */
module.exports = {
- images: {
- remotePatterns: [
- { hostname: "cdn.sanity.io" },
- { hostname: "source.unsplash.com" },
- ],
+ experimental: {
+ // Used to guard against accidentally leaking SANITY_API_READ_TOKEN to the browser
+ taint: true,
+ },
+ logging: {
+ fetches: { fullUrl: false },
},
};
diff --git a/examples/cms-sanity/package.json b/examples/cms-sanity/package.json
index 22addbb2a2..0f1e6a4974 100644
--- a/examples/cms-sanity/package.json
+++ b/examples/cms-sanity/package.json
@@ -4,23 +4,38 @@
"dev": "next",
"build": "next build",
"start": "next start",
- "studio:dev": "npm --prefix studio run start",
- "studio:deploy": "npx vercel env pull && npm --prefix studio run deploy"
+ "lint": "next lint",
+ "presetup": "echo 'about to setup env variables, follow the guide here: https://github.com/vercel/next.js/tree/canary/examples/cms-sanity#using-the-sanity-cli'",
+ "setup": "npx sanity@latest init --env .env.local",
+ "postsetup": "echo 'create the read token by following the rest of the guide: https://github.com/vercel/next.js/tree/canary/examples/cms-sanity#creating-a-read-token'"
},
"dependencies": {
- "@portabletext/react": "^2.0.1",
+ "@sanity/assist": "^2.0.1",
+ "@sanity/icons": "^2.10.3",
"@sanity/image-url": "^1.0.2",
- "@sanity/webhook": "^2.0.0",
- "classnames": "^2.3.1",
- "date-fns": "^2.29.3",
+ "@sanity/vision": "^3.32.0",
+ "@tailwindcss/typography": "^0.5.10",
+ "@types/node": "^20.11.25",
+ "@types/react": "^18.2.64",
+ "@types/react-dom": "^18.2.21",
+ "@vercel/speed-insights": "^1.0.10",
+ "autoprefixer": "^10.4.18",
+ "date-fns": "^3.3.1",
"next": "latest",
- "next-sanity": "^4.1.2",
- "react": "^18",
- "react-dom": "^18"
+ "next-sanity": "8.3.0",
+ "postcss": "^8.4.35",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "rxjs": "^7.8.1",
+ "sanity": "^3.32.0",
+ "sanity-plugin-asset-source-unsplash": "^1.1.2",
+ "server-only": "^0.0.1",
+ "styled-components": "^6.1.8",
+ "tailwindcss": "^3.4.1",
+ "typescript": "5.3.3"
},
"devDependencies": {
- "autoprefixer": "^10.4.13",
- "postcss": "^8.4.21",
- "tailwindcss": "^3.2.4"
+ "eslint": "^8.57.0",
+ "eslint-config-next": "latest"
}
}
diff --git a/examples/cms-sanity/pages/_app.js b/examples/cms-sanity/pages/_app.js
deleted file mode 100644
index aa7931ca32..0000000000
--- a/examples/cms-sanity/pages/_app.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import "../styles/index.css";
-
-function MyApp({ Component, pageProps }) {
- return ;
-}
-
-export default MyApp;
diff --git a/examples/cms-sanity/pages/_document.js b/examples/cms-sanity/pages/_document.js
deleted file mode 100644
index b2fff8b426..0000000000
--- a/examples/cms-sanity/pages/_document.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Html, Head, Main, NextScript } from "next/document";
-
-export default function Document() {
- return (
-
-
-
-
-
-
-
- );
-}
diff --git a/examples/cms-sanity/pages/api/exit-preview.js b/examples/cms-sanity/pages/api/exit-preview.js
deleted file mode 100644
index 16b744e88e..0000000000
--- a/examples/cms-sanity/pages/api/exit-preview.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export default async function exit(_, res) {
- // Exit Draft Mode by removing the cookie
- res.setDraftMode({ enable: false });
-
- // Redirect the user back to the index page.
- res.writeHead(307, { Location: "/" });
- res.end();
-}
diff --git a/examples/cms-sanity/pages/api/preview.js b/examples/cms-sanity/pages/api/preview.js
deleted file mode 100644
index 8035d1564c..0000000000
--- a/examples/cms-sanity/pages/api/preview.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { postBySlugQuery } from "../../lib/queries";
-import { previewClient } from "../../lib/sanity.server";
-
-function redirectToPreview(res, Location) {
- // Enable Draft Mode by setting the cookie
- res.setDraftMode({ enable: true });
- // Redirect to a preview capable route
- res.writeHead(307, { Location });
- res.end();
-}
-
-export default async function preview(req, res) {
- const secret = process.env.SANITY_STUDIO_PREVIEW_SECRET;
- // Only require a secret when in production
- if (!secret && process.env.NODE_ENV === "production") {
- throw new TypeError(`Missing SANITY_STUDIO_PREVIEW_SECRET`);
- }
- // Check the secret if it's provided, enables running preview mode locally before the env var is setup
- if (secret && req.query.secret !== secret) {
- return res.status(401).json({ message: "Invalid secret" });
- }
- // If no slug is provided open preview mode on the frontpage
- if (!req.query.slug) {
- return redirectToPreview(res, "/");
- }
-
- // Check if the post with the given `slug` exists
- const post = await previewClient.fetch(postBySlugQuery, {
- slug: req.query.slug,
- });
-
- // If the slug doesn't exist prevent preview mode from being enabled
- if (!post) {
- return res.status(401).json({ message: "Invalid slug" });
- }
-
- // Redirect to the path from the fetched post
- // We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
- redirectToPreview(res, `/posts/${post.slug}`);
-}
diff --git a/examples/cms-sanity/pages/api/revalidate.js b/examples/cms-sanity/pages/api/revalidate.js
deleted file mode 100644
index b5add334c7..0000000000
--- a/examples/cms-sanity/pages/api/revalidate.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import { isValidSignature, SIGNATURE_HEADER_NAME } from "@sanity/webhook";
-import { sanityClient } from "../../lib/sanity.server";
-
-// Next.js will by default parse the body, which can lead to invalid signatures
-export const config = {
- api: {
- bodyParser: false,
- },
-};
-
-const AUTHOR_UPDATED_QUERY = /* groq */ `
- *[_type == "author" && _id == $id] {
- "slug": *[_type == "post" && references(^._id)].slug.current
- }["slug"][]`;
-const POST_UPDATED_QUERY = /* groq */ `*[_type == "post" && _id == $id].slug.current`;
-
-const getQueryForType = (type) => {
- switch (type) {
- case "author":
- return AUTHOR_UPDATED_QUERY;
- case "post":
- return POST_UPDATED_QUERY;
- default:
- throw new TypeError(`Unknown type: ${type}`);
- }
-};
-
-const log = (msg, error) =>
- console[error ? "error" : "log"](`[revalidate] ${msg}`);
-
-async function readBody(readable) {
- const chunks = [];
- for await (const chunk of readable) {
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
- }
- return Buffer.concat(chunks).toString("utf8");
-}
-
-export default async function revalidate(req, res) {
- const signature = req.headers[SIGNATURE_HEADER_NAME];
- const body = await readBody(req); // Read the body into a string
- if (
- !isValidSignature(
- body,
- signature,
- process.env.SANITY_REVALIDATE_SECRET?.trim(),
- )
- ) {
- const invalidSignature = "Invalid signature";
- log(invalidSignature, true);
- res.status(401).json({ success: false, message: invalidSignature });
- return;
- }
-
- const jsonBody = JSON.parse(body);
- const { _id: id, _type } = jsonBody;
- if (typeof id !== "string" || !id) {
- const invalidId = "Invalid _id";
- log(invalidId, true);
- return res.status(400).json({ message: invalidId });
- }
-
- log(`Querying post slug for _id '${id}', type '${_type}' ..`);
- const slug = await sanityClient.fetch(getQueryForType(_type), { id });
- const slugs = (Array.isArray(slug) ? slug : [slug]).map(
- (_slug) => `/posts/${_slug}`,
- );
- const staleRoutes = ["/", ...slugs];
-
- try {
- await Promise.all(staleRoutes.map((route) => res.revalidate(route)));
- const updatedRoutes = `Updated routes: ${staleRoutes.join(", ")}`;
- log(updatedRoutes);
- return res.status(200).json({ message: updatedRoutes });
- } catch (err) {
- log(err.message, true);
- return res.status(500).json({ message: err.message });
- }
-}
diff --git a/examples/cms-sanity/pages/index.js b/examples/cms-sanity/pages/index.js
deleted file mode 100644
index 682af4bbcf..0000000000
--- a/examples/cms-sanity/pages/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { indexQuery } from "../lib/queries";
-import { getClient, overlayDrafts } from "../lib/sanity.server";
-import { PreviewSuspense } from "next-sanity/preview";
-import { lazy } from "react";
-import Landing from "../components/landing";
-
-const LandingPreview = lazy(() => import("../components/landing-preview"));
-
-export default function IndexPage({ allPosts, preview }) {
- if (preview) {
- return (
-
-
-
- );
- }
-
- return ;
-}
-
-export async function getStaticProps({ preview = false }) {
- const allPosts = overlayDrafts(await getClient(preview).fetch(indexQuery));
- return {
- props: { allPosts, preview },
- // If webhooks isn't setup then attempt to re-generate in 1 minute intervals
- revalidate: process.env.SANITY_REVALIDATE_SECRET ? undefined : 60,
- };
-}
diff --git a/examples/cms-sanity/pages/posts/[slug].js b/examples/cms-sanity/pages/posts/[slug].js
deleted file mode 100644
index 2332dd291e..0000000000
--- a/examples/cms-sanity/pages/posts/[slug].js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { lazy } from "react";
-import { PreviewSuspense } from "next-sanity/preview";
-import { postQuery, postSlugsQuery } from "../../lib/queries";
-import {
- getClient,
- overlayDrafts,
- sanityClient,
-} from "../../lib/sanity.server";
-import Post from "../../components/post";
-
-const PostPreview = lazy(() => import("../../components/post-preview"));
-
-export default function PostPage({ preview, data }) {
- if (preview) {
- return (
-
-
-
- );
- }
-
- return ;
-}
-
-export async function getStaticProps({ params, preview = false }) {
- const { post, morePosts } = await getClient(preview).fetch(postQuery, {
- slug: params.slug,
- });
-
- return {
- props: {
- preview,
- data: {
- post,
- morePosts: overlayDrafts(morePosts),
- },
- },
- // If webhooks isn't setup then attempt to re-generate in 1 minute intervals
- revalidate: process.env.SANITY_REVALIDATE_SECRET ? undefined : 60,
- };
-}
-
-export async function getStaticPaths() {
- const paths = await sanityClient.fetch(postSlugsQuery);
- return {
- paths: paths.map((slug) => ({ params: { slug } })),
- fallback: true,
- };
-}
diff --git a/examples/cms-sanity/postcss.config.js b/examples/cms-sanity/postcss.config.js
index 3687d286ec..12a703d900 100644
--- a/examples/cms-sanity/postcss.config.js
+++ b/examples/cms-sanity/postcss.config.js
@@ -1,5 +1,3 @@
-// If you want to use other PostCSS plugins, see the following:
-// https://tailwindcss.com/docs/using-with-preprocessors
module.exports = {
plugins: {
tailwindcss: {},
diff --git a/examples/cms-sanity/public/favicon/android-chrome-192x192.png b/examples/cms-sanity/public/favicon/android-chrome-192x192.png
deleted file mode 100644
index 2f07282a59cdadaf579b129d650f588d89bef63e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 4795
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
View screenshot ✨
+ +![screenshot](https://github.com/vercel/next.js/assets/81981/07cbc580-4a03-4837-9aa4-90b632c95630) + +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) + +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) + +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) + +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) + +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) + +View screenshot ✨
- -![screenshot](https://user-images.githubusercontent.com/81981/182991870-7a0f6e54-b35e-4728-922b-409fcf1d6cc3.png) - -View screenshot ✨
- - ![screenshot](https://user-images.githubusercontent.com/81981/182993687-b6313086-f60a-4b36-b038-4c1c63b53c54.png) - -View screenshot ✨
- - ![screenshot](https://user-images.githubusercontent.com/81981/182994571-f204c41c-e1e3-44f4-82b3-99fefbd25bec.png) - -View screenshot ✨
- - ![screenshot](https://user-images.githubusercontent.com/81981/182995772-33d63e45-4920-48c5-aa47-ccb7ce10170c.png) - -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`. - -+ + {title} + +
++ {excerpt} +
+ )} + {author &&No posts
++ Get started by creating a new post. +
++ {title || demo.title} +
+
+
+
+ + + {title} + +
++ {excerpt} +
+ )} + {author &&{children}
+ ), + h6: ({ children }) => ( +{children}
+ ), + }, + marks: { + link: ({ children, value }) => { + return ( + + {children} + + ); + }, + }, + }; + + return ( ++ + {settings?.title || demo.title} + +
++ {post.title} +
+faLScfyP!@$(yNe-7(=9KtYpa$h$)dhn%5R(CFHY0v@iSzFoCU z3ey-mGo#3ee
- - 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 &&- Blog. -
-- A statically generated blog example using{" "} - - Next.js - {" "} - and{" "} - - {CMS_NAME} - - . -
-- More Stories -
-- - {title} - -
-{excerpt}
- {author &&- {children} -
- ); -} diff --git a/examples/cms-sanity/components/post.js b/examples/cms-sanity/components/post.js deleted file mode 100644 index 16f67e3498..0000000000 --- a/examples/cms-sanity/components/post.js +++ /dev/null @@ -1,65 +0,0 @@ -import { useRouter } from "next/router"; -import { urlForImage } from "../lib/sanity"; -import ErrorPage from "next/error"; -import Layout from "./layout"; -import Container from "./container"; -import Header from "./header"; -import PostTitle from "./post-title"; -import Head from "next/head"; -import { CMS_NAME } from "../lib/constants"; -import PostHeader from "./post-header"; -import PostBody from "./post-body"; -import SectionSeparator from "./section-separator"; -import MoreStories from "./more-stories"; - -export default function Post({ data = {}, preview = false }) { - const router = useRouter(); - - const { post, morePosts } = data; - const slug = post?.slug; - - if (!router.isFallback && !slug) { - return; -} diff --git a/examples/cms-sanity/lib/config.js b/examples/cms-sanity/lib/config.js deleted file mode 100644 index c8e58e7850..0000000000 --- a/examples/cms-sanity/lib/config.js +++ /dev/null @@ -1,15 +0,0 @@ -export const sanityConfig = { - // Find your project ID and dataset in `sanity.json` in your studio project - dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || "production", - projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID, - useCdn: - typeof document !== "undefined" && process.env.NODE_ENV === "production", - // useCdn == true gives fast, cheap responses using a globally distributed cache. - // When in production the Sanity API is only queried on build-time, and on-demand when responding to webhooks. - // Thus the data need to be fresh and API response time is less important. - // When in development/working locally, it's more important to keep costs down as hot reloading can incurr a lot of API calls - // And every page load calls getStaticProps. - // To get the lowest latency, lowest cost, and latest data, use the Instant Preview mode - apiVersion: "2022-03-13", - // see https://www.sanity.io/docs/api-versioning for how versioning works -}; diff --git a/examples/cms-sanity/lib/constants.js b/examples/cms-sanity/lib/constants.js deleted file mode 100644 index 3db98b7134..0000000000 --- a/examples/cms-sanity/lib/constants.js +++ /dev/null @@ -1,5 +0,0 @@ -export const EXAMPLE_PATH = "cms-sanity"; -export const CMS_NAME = "Sanity"; -export const CMS_URL = "https://sanity.io/"; -export const HOME_OG_IMAGE_URL = - "https://og-image.vercel.app/Next.js%20Blog%20Example%20with%20**Sanity**.png?theme=light&md=1&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB2aWV3Qm94PSIwIDAgMTA1IDIyIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMWVtIj48dGl0bGU%2BU2FuaXR5PC90aXRsZT48cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03OC4xNzkzIDcuOTkyNjFWMjEuMDAyOEg3My45MDMxVjEwLjIxMzhMNzguMTc5MyA3Ljk5MjYxWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNMjAuOTUxMSAyMS4zM0wzMC45NDQgMTYuMTA1MUwyOS43MTIxIDEyLjkxNDFMMjMuMTMzMiAxNS45ODIxTDIwLjk1MTEgMjEuMzNaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik03My45MDMxIDEwLjIwMjdMODQuNzQ0MyA0LjY1NDc3TDgyLjkxMjYgMS41NTcxTDczLjkwMzEgNS45NTk5N1YxMC4yMDI3WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNNDMuMzcwNSA2Ljk2MjMzVjIxLjAwMjhIMzkuMjkyN1YxLjAwNzE0TDQzLjM3MDUgNi45NjIzM1oiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTI3LjEyOTkgNi4xODYxN0wyMC45NTExIDIxLjMzTDE3Ljc3MzEgMTguNTk0M0wyNS4xMzUzIDEuMDA3MTRMMjcuMTI5OSA2LjE4NjE3WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggZD0iTTI1LjEzNTMgMS4wMDcxNEgyOS4zNDc3TDM3LjEzODYgMjEuMDAyOEgzMi44MjY5TDI1LjEzNTMgMS4wMDcxNFoiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIGQ9Ik00NC4wMDEyIDEuMDA3MTRMNTIuOTgyNCAxNC42NjgyVjIxLjAwMjhMMzkuMjkyNyAxLjAwNzE0SDQ0LjAwMTJaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNNjQuOTE4MyAxLjAwNzE0SDYwLjY3MzlWMjEuMDA2M0g2NC45MTgzVjEuMDA3MTRaIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNNzMuOTAzMSA0LjY1NDc0SDY3LjM3VjEuMDA3MTRIODIuNTg2N0w4NC43NDQzIDQuNjU0NzRINzguMTc5M0g3My45MDMxWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNOTcuMjc1NCAxMy40MTUzVjIxLjAwMjhIOTMuMDYyOVYxMy40MTUzIiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBkPSJNOTMuMDYyOSAxMy40MTUyTDEwMC4xOTEgMS4wMDcxNEgxMDQuNjY2TDk3LjI3NTQgMTMuNDE1Mkg5My4wNjI5WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNOTMuMDYzIDEzLjQxNTJMODUuNzM2MyAxLjAwNzE0SDkwLjM0NTZMOTUuMzA5MiA5LjUxMDA4TDkzLjA2MyAxMy40MTUyWiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggZD0iTTEuOTYxMjYgMy4zMTQ3OUMxLjk2MTI2IDYuMDk5MjEgMy43MTE0NSA3Ljc1NTk1IDcuMjE1MzYgOC42Mjk1NkwxMC45MjgzIDkuNDc1MzNDMTQuMjQ0NCAxMC4yMjM2IDE2LjI2MzkgMTIuMDgyMiAxNi4yNjM5IDE1LjExMDNDMTYuMjg5NyAxNi40Mjk1IDE1Ljg1MzEgMTcuNzE3MyAxNS4wMjc0IDE4Ljc1NzlDMTUuMDI3NCAxNS43MzY4IDEzLjQzNjcgMTQuMTA0NCA5LjU5OTcyIDEzLjEyMjlMNS45NTQwOSAxMi4zMDg1QzMuMDM0NzUgMTEuNjU0MSAwLjc4MTQ3OCAxMC4xMjYyIDAuNzgxNDc4IDYuODM3MDlDMC43NjYxMjMgNS41NjY5MyAxLjE4MTE2IDQuMzI3ODEgMS45NjEyNiAzLjMxNDc5IiBmaWxsPSJjdXJyZW50Q29sb3IiPjwvcGF0aD48cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik01Mi45ODI0IDEzLjY0MTVWMS4wMDcxNEg1Ny4wNjAyVjIxLjAwMjhINTIuOTgyNFYxMy42NDE1WiIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPHBhdGggb3BhY2l0eT0iMC43IiBkPSJNMTIuNzQ1OCAxNC4zNjg5QzE0LjMyOTQgMTUuMzY0MyAxNS4wMjM4IDE2Ljc1NjUgMTUuMDIzOCAxOC43NTQ0QzEzLjcxMyAyMC40MDQxIDExLjQxMDEgMjEuMzMgOC43MDMzMyAyMS4zM0M0LjE0NzE4IDIxLjMzIDAuOTU4NTc3IDE5LjEyNjggMC4yNSAxNS4yOTgySDQuNjI1NDdDNS4xODg3OCAxNy4wNTU5IDYuNjgwMzQgMTcuODcwMyA4LjY3MTQ0IDE3Ljg3MDNDMTEuMTAxOSAxNy44NzAzIDEyLjcxNzQgMTYuNTk2NCAxMi43NDkzIDE0LjM2MTkiIGZpbGw9ImN1cnJlbnRDb2xvciI%2BPC9wYXRoPjxwYXRoIG9wYWNpdHk9IjAuNyIgZD0iTTQuMjM1NjcgNy40NDI2N0MzLjUxMjUgNy4wMjA0NSAyLjkxOTIgNi40MTM3NSAyLjUxODczIDUuNjg2OTdDMi4xMTgyNyA0Ljk2MDE5IDEuOTI1NTggNC4xNDA0NSAxLjk2MTEzIDMuMzE0NzZDMy4yMjU5NCAxLjY3ODkxIDUuNDI2MDggMC42Nzk5OTMgOC4xMDgwNCAwLjY3OTk5M0MxMi43NDkyIDAuNjc5OTkzIDE1LjQzNDcgMy4wODg1MiAxNi4wOTcyIDYuNDc4NTZIMTEuODg4M0MxMS40MjQyIDUuMTQyMDMgMTAuMjYyMSA0LjEwMTM2IDguMTQzNDcgNC4xMDEzNkM1Ljg3OTU3IDQuMTAxMzYgNC4zMzQ4NyA1LjM5NjExIDQuMjQ2MjkgNy40NDI2NyIgZmlsbD0iY3VycmVudENvbG9yIj48L3BhdGg%2BPC9zdmc%2B&widths=undefined&widths=auto&heights=250&heights=150"; diff --git a/examples/cms-sanity/lib/queries.js b/examples/cms-sanity/lib/queries.js deleted file mode 100644 index 89330900c4..0000000000 --- a/examples/cms-sanity/lib/queries.js +++ /dev/null @@ -1,37 +0,0 @@ -const postFields = ` - _id, - name, - title, - date, - excerpt, - coverImage, - "slug": slug.current, - "author": author->{name, picture}, -`; - -export const indexQuery = ` -*[_type == "post"] | order(date desc, _updatedAt desc) { - ${postFields} -}`; - -export const postQuery = ` -{ - "post": *[_type == "post" && slug.current == $slug] | order(_updatedAt desc) [0] { - content, - ${postFields} - }, - "morePosts": *[_type == "post" && slug.current != $slug] | order(date desc, _updatedAt desc) [0...2] { - content, - ${postFields} - } -}`; - -export const postSlugsQuery = ` -*[_type == "post" && defined(slug.current)][].slug.current -`; - -export const postBySlugQuery = ` -*[_type == "post" && slug.current == $slug][0] { - ${postFields} -} -`; diff --git a/examples/cms-sanity/lib/sanity.js b/examples/cms-sanity/lib/sanity.js deleted file mode 100644 index 2492667bea..0000000000 --- a/examples/cms-sanity/lib/sanity.js +++ /dev/null @@ -1,13 +0,0 @@ -import createImageUrlBuilder from "@sanity/image-url"; -import { definePreview } from "next-sanity/preview"; -import { sanityConfig } from "./config"; - -export const imageBuilder = createImageUrlBuilder(sanityConfig); - -export const urlForImage = (source) => - imageBuilder.image(source).auto("format").fit("max"); - -export const usePreview = definePreview({ - projectId: sanityConfig.projectId, - dataset: sanityConfig.dataset, -}); diff --git a/examples/cms-sanity/lib/sanity.server.js b/examples/cms-sanity/lib/sanity.server.js deleted file mode 100644 index 4a0d7be3bf..0000000000 --- a/examples/cms-sanity/lib/sanity.server.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Server-side Sanity utilities. By having these in a separate file from the - * utilities we use on the client side, we are able to tree-shake (remove) - * code that is not used on the client side. - */ -import { createClient } from "next-sanity"; -import { sanityConfig } from "./config"; - -export const sanityClient = createClient(sanityConfig); - -export const previewClient = createClient({ - ...sanityConfig, - useCdn: false, - // Fallback to using the WRITE token until https://www.sanity.io/docs/vercel-integration starts shipping a READ token. - // As this client only exists on the server and the token is never shared with the browser, we don't risk escalating permissions to untrustworthy users - token: - process.env.SANITY_API_READ_TOKEN || process.env.SANITY_API_WRITE_TOKEN, -}); - -export const getClient = (preview) => (preview ? previewClient : sanityClient); - -export function overlayDrafts(docs) { - const documents = docs || []; - const overlayed = documents.reduce((map, doc) => { - if (!doc._id) { - throw new Error("Ensure that `_id` is included in query projection"); - } - - const isDraft = doc._id.startsWith("drafts."); - const id = isDraft ? doc._id.slice(7) : doc._id; - return isDraft || !map.has(id) ? map.set(id, doc) : map; - }, new Map()); - - return Array.from(overlayed.values()); -} diff --git a/examples/cms-sanity/next.config.js b/examples/cms-sanity/next.config.js index 553cc9b836..0a9e8f6022 100644 --- a/examples/cms-sanity/next.config.js +++ b/examples/cms-sanity/next.config.js @@ -1,9 +1,10 @@ /** @type {import('next').NextConfig} */ module.exports = { - images: { - remotePatterns: [ - { hostname: "cdn.sanity.io" }, - { hostname: "source.unsplash.com" }, - ], + experimental: { + // Used to guard against accidentally leaking SANITY_API_READ_TOKEN to the browser + taint: true, + }, + logging: { + fetches: { fullUrl: false }, }, }; diff --git a/examples/cms-sanity/package.json b/examples/cms-sanity/package.json index 22addbb2a2..0f1e6a4974 100644 --- a/examples/cms-sanity/package.json +++ b/examples/cms-sanity/package.json @@ -4,23 +4,38 @@ "dev": "next", "build": "next build", "start": "next start", - "studio:dev": "npm --prefix studio run start", - "studio:deploy": "npx vercel env pull && npm --prefix studio run deploy" + "lint": "next lint", + "presetup": "echo 'about to setup env variables, follow the guide here: https://github.com/vercel/next.js/tree/canary/examples/cms-sanity#using-the-sanity-cli'", + "setup": "npx sanity@latest init --env .env.local", + "postsetup": "echo 'create the read token by following the rest of the guide: https://github.com/vercel/next.js/tree/canary/examples/cms-sanity#creating-a-read-token'" }, "dependencies": { - "@portabletext/react": "^2.0.1", + "@sanity/assist": "^2.0.1", + "@sanity/icons": "^2.10.3", "@sanity/image-url": "^1.0.2", - "@sanity/webhook": "^2.0.0", - "classnames": "^2.3.1", - "date-fns": "^2.29.3", + "@sanity/vision": "^3.32.0", + "@tailwindcss/typography": "^0.5.10", + "@types/node": "^20.11.25", + "@types/react": "^18.2.64", + "@types/react-dom": "^18.2.21", + "@vercel/speed-insights": "^1.0.10", + "autoprefixer": "^10.4.18", + "date-fns": "^3.3.1", "next": "latest", - "next-sanity": "^4.1.2", - "react": "^18", - "react-dom": "^18" + "next-sanity": "8.3.0", + "postcss": "^8.4.35", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rxjs": "^7.8.1", + "sanity": "^3.32.0", + "sanity-plugin-asset-source-unsplash": "^1.1.2", + "server-only": "^0.0.1", + "styled-components": "^6.1.8", + "tailwindcss": "^3.4.1", + "typescript": "5.3.3" }, "devDependencies": { - "autoprefixer": "^10.4.13", - "postcss": "^8.4.21", - "tailwindcss": "^3.2.4" + "eslint": "^8.57.0", + "eslint-config-next": "latest" } } diff --git a/examples/cms-sanity/pages/_app.js b/examples/cms-sanity/pages/_app.js deleted file mode 100644 index aa7931ca32..0000000000 --- a/examples/cms-sanity/pages/_app.js +++ /dev/null @@ -1,7 +0,0 @@ -import "../styles/index.css"; - -function MyApp({ Component, pageProps }) { - return