next-sanity 9.8.6 → 9.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +428 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -37,6 +37,9 @@ The all-in-one [Sanity][sanity] toolkit for production-grade content-editable Ne
|
|
|
37
37
|
- [Debugging caching and revalidation](#debugging-caching-and-revalidation)
|
|
38
38
|
- [Example implementation](#example-implementation)
|
|
39
39
|
- [Visual Editing](#visual-editing)
|
|
40
|
+
- [Live Content API](#live-content-api)
|
|
41
|
+
- [Setup](#setup)
|
|
42
|
+
- [How does it revalidate and refresh in real time](#how-does-it-revalidate-and-refresh-in-real-time)
|
|
40
43
|
- [Embedded Sanity Studio](#embedded-sanity-studio)
|
|
41
44
|
- [Creating a Studio route](#creating-a-studio-route)
|
|
42
45
|
- [Automatic installation of embedded Studio](#automatic-installation-of-embedded-studio)
|
|
@@ -589,6 +592,430 @@ Interactive live previews of draft content are the best way for authors to find
|
|
|
589
592
|
|
|
590
593
|
An end-to-end tutorial of [how to configure Sanity and Next.js for Visual Editing](https://www.sanity.io/guides/nextjs-app-router-live-preview) using the same patterns demonstrated in this README is available on the Sanity Exchange.
|
|
591
594
|
|
|
595
|
+
## Live Content API
|
|
596
|
+
|
|
597
|
+
[The Live Content API][live-content-api] can be used to receive real time updates in your application when viewing both draft content in contexts like Presentation tool, and published content in your user-facing production application.
|
|
598
|
+
|
|
599
|
+
> [!NOTE]
|
|
600
|
+
> The Live Content API is currently considered experimental and may change in the future.
|
|
601
|
+
|
|
602
|
+
### Setup
|
|
603
|
+
|
|
604
|
+
#### 1. Configure `defineLive`
|
|
605
|
+
|
|
606
|
+
Use `defineLive` to enable automatic revalidation and refreshing of your fetched content.
|
|
607
|
+
|
|
608
|
+
```tsx
|
|
609
|
+
// src/sanity/lib/live.ts
|
|
610
|
+
|
|
611
|
+
import {createClient, defineLive} from 'next-sanity'
|
|
612
|
+
|
|
613
|
+
const client = createClient({
|
|
614
|
+
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
|
|
615
|
+
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
|
|
616
|
+
useCdn: true,
|
|
617
|
+
apiVersion: 'vX', // Target the experimental API version
|
|
618
|
+
stega: {studioUrl: '/studio'},
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
const token = process.env.SANITY_API_READ_TOKEN
|
|
622
|
+
if (!token) {
|
|
623
|
+
throw new Error('Missing SANITY_API_READ_TOKEN')
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export const {sanityFetch, SanityLive} = defineLive({
|
|
627
|
+
client,
|
|
628
|
+
serverToken: token,
|
|
629
|
+
browserToken: token,
|
|
630
|
+
})
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
The `token` passed to `defineLive` needs [Viewer rights](https://www.sanity.io/docs/roles#e2daad192df9) in order to fetch draft content.
|
|
634
|
+
|
|
635
|
+
The same token can be used as both `browserToken` and `serverToken`, as the `browserToken` is only shared with the browser when Draft Mode is enabled. Draft Mode can only be initiated by either Sanity's Presentation Tool or the Vercel Toolbar.
|
|
636
|
+
|
|
637
|
+
> Good to know:
|
|
638
|
+
> Enterprise plans allow the creation of custom roles with more resticted access rights than the `Viewer` role, enabling the use of a `browserToken` specifically for authenticating the Live Content API. We're working to extend this capability to all Sanity price plans.
|
|
639
|
+
|
|
640
|
+
#### 2. Render `<SanityLive />` in the root `layout.tsx`
|
|
641
|
+
|
|
642
|
+
```tsx
|
|
643
|
+
// src/app/layout.tsx
|
|
644
|
+
|
|
645
|
+
import {VisualEditing} from 'next-sanity'
|
|
646
|
+
import {SanityLive} from '@/sanity/lib/live'
|
|
647
|
+
|
|
648
|
+
export default function RootLayout({children}: {children: React.ReactNode}) {
|
|
649
|
+
return (
|
|
650
|
+
<html lang="en">
|
|
651
|
+
<body>
|
|
652
|
+
{children}
|
|
653
|
+
<SanityLive />
|
|
654
|
+
{(await draftMode()).isEnabled && <VisualEditing />}
|
|
655
|
+
</body>
|
|
656
|
+
</html>
|
|
657
|
+
)
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
The `<SanityLive>` component is responsible for making all `sanityFetch` calls in your application _live_, so should always be rendered. This differs from the `<VisualEditing />` component, which should only be rendered when Draft Mode is enabled.
|
|
662
|
+
|
|
663
|
+
#### 3. Fetching data with `sanityFetch`
|
|
664
|
+
|
|
665
|
+
Use `sanityFetch` to fetch data in any server component.
|
|
666
|
+
|
|
667
|
+
```tsx
|
|
668
|
+
// src/app/products.tsx
|
|
669
|
+
|
|
670
|
+
import {defineQuery} from 'next-sanity'
|
|
671
|
+
import {sanityFetch} from '@/sanity/lib/live'
|
|
672
|
+
|
|
673
|
+
const PRODUCTS_QUERY = defineQuery(`*[_type == "product" && defined(slug.current)][0...$limit]`)
|
|
674
|
+
|
|
675
|
+
export default async function Page() {
|
|
676
|
+
const {data: products} = await sanityFetch({
|
|
677
|
+
query: PRODUCTS_QUERY,
|
|
678
|
+
params: {limit: 10},
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
return (
|
|
682
|
+
<section>
|
|
683
|
+
{products.map((product) => (
|
|
684
|
+
<article key={product._id}>
|
|
685
|
+
<a href={`/product/${product.slug}`}>{product.title}</a>
|
|
686
|
+
</article>
|
|
687
|
+
))}
|
|
688
|
+
</section>
|
|
689
|
+
)
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Using `generateMetadata`, `generateStaticParams` and more
|
|
694
|
+
|
|
695
|
+
`sanityFetch` can also be used in functions like `generateMetadata` in order to make updating the page title, or even its favicon, _live_.
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
import {sanityFetch} from '@/sanity/lib/live'
|
|
699
|
+
import type {Metadata} from 'next'
|
|
700
|
+
|
|
701
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
702
|
+
const {data} = await sanityFetch({
|
|
703
|
+
query: SETTINGS_QUERY,
|
|
704
|
+
// Metadata should never contain stega
|
|
705
|
+
stega: false,
|
|
706
|
+
})
|
|
707
|
+
return {
|
|
708
|
+
title: {
|
|
709
|
+
template: `%s | ${data.title}`,
|
|
710
|
+
default: data.title,
|
|
711
|
+
},
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
> Good to know:
|
|
717
|
+
> Always set `stega: false` when calling `sanityFetch` within these:
|
|
718
|
+
>
|
|
719
|
+
> - `generateMetadata`
|
|
720
|
+
> - `generateViewport`
|
|
721
|
+
> - `generateSitemaps`
|
|
722
|
+
> - `generateImageMetadata`
|
|
723
|
+
|
|
724
|
+
```ts
|
|
725
|
+
import {sanityFetch} from '@/sanity/lib/live'
|
|
726
|
+
|
|
727
|
+
export async function generateStaticParams() {
|
|
728
|
+
const {data} = await sanityFetch({
|
|
729
|
+
query: POST_SLUGS_QUERY,
|
|
730
|
+
// Use the published perspective in generateStaticParams
|
|
731
|
+
perspective: 'published',
|
|
732
|
+
stega: false,
|
|
733
|
+
})
|
|
734
|
+
return data
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### 4. Integrating with Next.js Draft Mode and Vercel Toolbar's Edit Mode
|
|
739
|
+
|
|
740
|
+
To support previewing draft content when Draft Mode is enabled, the `serverToken` passed to `defineLive` should be assigned the Viewer role, which has the ability to fetch content using the `previewDrafts` perspective.
|
|
741
|
+
|
|
742
|
+
Click the Draft Mode button in the Vercel toolbar to enable draft content:
|
|
743
|
+
|
|
744
|
+

|
|
745
|
+
|
|
746
|
+
With drafts enabled, you'll see the Edit Mode button show up if your Vercel plan is eligible:
|
|
747
|
+
|
|
748
|
+

|
|
749
|
+
|
|
750
|
+
Ensure that `browserToken` is setup if you want draft content that isn't yet published to also update live.
|
|
751
|
+
|
|
752
|
+
### 5. Integrating with Sanity Presentation Tool & Visual Editing
|
|
753
|
+
|
|
754
|
+
The `defineLive` API also supports Presentation Tool and Sanity Visual Editing.
|
|
755
|
+
|
|
756
|
+
Setup an API route that uses `defineEnableDraftMode` in your app:
|
|
757
|
+
|
|
758
|
+
```ts
|
|
759
|
+
// src/app/api/draft-mode/enable/route.ts
|
|
760
|
+
|
|
761
|
+
import {client} from '@/sanity/lib/client'
|
|
762
|
+
import {token} from '@/sanity/lib/token'
|
|
763
|
+
import {defineEnableDraftMode} from 'next-sanity/draft-mode'
|
|
764
|
+
|
|
765
|
+
export const {GET} = defineEnableDraftMode({
|
|
766
|
+
client: client.withConfig({token}),
|
|
767
|
+
})
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
The main benefit of `defineEnableDraftMode` is that it fully implements all of Sanity Presentation Tool's features, including the perspective switcher:
|
|
771
|
+
<img width="530" alt="image" src="https://github.com/user-attachments/assets/774d8f92-527f-4478-8089-2fb7e6a5c618">
|
|
772
|
+
|
|
773
|
+
And the Preview URL Sharing feature:
|
|
774
|
+
<img width="450" alt="image" src="https://github.com/user-attachments/assets/d11b38eb-389b-448f-862c-b39b3adbb7e3">
|
|
775
|
+
|
|
776
|
+
In your `sanity.config.ts`, set the `previewMode.enable` option for `presentationTool`:
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
// sanity.config.ts
|
|
780
|
+
|
|
781
|
+
import {defineConfig} from 'sanity'
|
|
782
|
+
import {presentationTool} from 'next-sanity'
|
|
783
|
+
|
|
784
|
+
export default defineConfig({
|
|
785
|
+
// ...
|
|
786
|
+
plugins: [
|
|
787
|
+
// ...
|
|
788
|
+
presentationTool({
|
|
789
|
+
previewUrl: {
|
|
790
|
+
// ...
|
|
791
|
+
previewMode: {
|
|
792
|
+
enable: '/api/draft-mode/enable',
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
}),
|
|
796
|
+
],
|
|
797
|
+
})
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
Ensuring you have a valid viewer token setup for `defineLive.serverToken` and `defineEnableDraftMode` allows Presentation Tool to auto enable Draft Mode, and your application to pull in draft content that refreshes in real time.
|
|
801
|
+
|
|
802
|
+
The `defineLive.browserToken` option isn't required, but is recommended as it enables a faster live preview experience, both standalone and when using Presentation Tool.
|
|
803
|
+
|
|
804
|
+
### 6. Enabling standalone live preview of draft content
|
|
805
|
+
|
|
806
|
+
Standalone live preview has the following requirements:
|
|
807
|
+
|
|
808
|
+
- `defineLive.serverToken` must be defined, otherwise only published content is fetched.
|
|
809
|
+
- At least one integration (Sanity Presentation Tool or Vercel Toolbar) must be setup, so Draft Mode can be enabled in your application on demand.
|
|
810
|
+
- `defineLive.browserToken` must be defined with a valid token.
|
|
811
|
+
|
|
812
|
+
You can verify if live preview is enabled with the `useIsLivePreview` hook:
|
|
813
|
+
|
|
814
|
+
```tsx
|
|
815
|
+
'use client'
|
|
816
|
+
|
|
817
|
+
import {useIsLivePreview} from 'next-sanity/hooks'
|
|
818
|
+
|
|
819
|
+
export function DebugLivePreview() {
|
|
820
|
+
const isLivePreview = useIsLivePreview()
|
|
821
|
+
if (isLivePreview === null) return 'Checking Live Preview...'
|
|
822
|
+
return isLivePreview ? 'Live Preview Enabled' : 'Live Preview Disabled'
|
|
823
|
+
}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
The following hooks can also be used to provide information about the application's current environment:
|
|
827
|
+
|
|
828
|
+
```ts
|
|
829
|
+
import {
|
|
830
|
+
useIsPresentationTool,
|
|
831
|
+
useDraftModeEnvironment,
|
|
832
|
+
useDraftModePerspective,
|
|
833
|
+
} from 'next-sanity/hooks'
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### Handling Layout Shift
|
|
837
|
+
|
|
838
|
+
Live components will re-render automatically as content changes. This can cause jarring layout shifts in production when items appear or disappear from a list.
|
|
839
|
+
|
|
840
|
+
To provide a better user experience, we can animate these layout changes. The following example uses `framer-motion@12.0.0-alpha.1`, which supports React Server Components:
|
|
841
|
+
|
|
842
|
+
```tsx
|
|
843
|
+
// src/app/products.tsx
|
|
844
|
+
|
|
845
|
+
import {AnimatePresence} from 'framer-motion'
|
|
846
|
+
import * as motion from 'framer-motion/client'
|
|
847
|
+
import {defineQuery} from 'next-sanity'
|
|
848
|
+
import {sanityFetch} from '@/sanity/lib/live'
|
|
849
|
+
|
|
850
|
+
const PRODUCTS_QUERY = defineQuery(`*[_type == "product" && defined(slug.current)][0...$limit]`)
|
|
851
|
+
|
|
852
|
+
export default async function Page() {
|
|
853
|
+
const {data: products} = await sanityFetch({
|
|
854
|
+
query: PRODUCTS_QUERY,
|
|
855
|
+
params: {limit: 10},
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
return (
|
|
859
|
+
<section>
|
|
860
|
+
<AnimatePresence mode="popLayout">
|
|
861
|
+
{products.map((product) => (
|
|
862
|
+
<motion.article
|
|
863
|
+
key={product._id}
|
|
864
|
+
layout="position"
|
|
865
|
+
animate={{opacity: 1}}
|
|
866
|
+
exit={{opacity: 0}}
|
|
867
|
+
>
|
|
868
|
+
<a href={`/product/${product.slug}`}>{product.title}</a>
|
|
869
|
+
</motion.article>
|
|
870
|
+
))}
|
|
871
|
+
</AnimatePresence>
|
|
872
|
+
</section>
|
|
873
|
+
)
|
|
874
|
+
}
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
Whilst this is an improvement, it may still lead to users attempting to click on an item as it shifts position, potentially resulting in the selection of an unintended item. We can instead require users to opt-in to changes before a layout update is triggered.
|
|
878
|
+
|
|
879
|
+
To preserve the ability to render everything on the server, we can make use of a Client Component wrapper. This can defer showing changes to the user until they've explicitly clicked to "Refresh". The example below uses `sonner` to provide toast functionality:
|
|
880
|
+
|
|
881
|
+
```tsx
|
|
882
|
+
// src/app/products/products-layout-shift.tsx
|
|
883
|
+
|
|
884
|
+
'use client'
|
|
885
|
+
|
|
886
|
+
import {useCallback, useState, useEffect} from 'react'
|
|
887
|
+
import isEqual from 'react-fast-compare'
|
|
888
|
+
import {toast} from 'sonner'
|
|
889
|
+
|
|
890
|
+
export function ProductsLayoutShift(props: {children: React.ReactNode; ids: string[]}) {
|
|
891
|
+
const [children, pending, startViewTransition] = useDeferredLayoutShift(props.children, props.ids)
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* We need to suspend layout shift for user opt-in.
|
|
895
|
+
*/
|
|
896
|
+
useEffect(() => {
|
|
897
|
+
if (!pending) return
|
|
898
|
+
|
|
899
|
+
toast('Products have been updated', {
|
|
900
|
+
action: {
|
|
901
|
+
label: 'Refresh',
|
|
902
|
+
onClick: () => startViewTransition(),
|
|
903
|
+
},
|
|
904
|
+
})
|
|
905
|
+
}, [pending, startViewTransition])
|
|
906
|
+
|
|
907
|
+
return children
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function useDeferredLayoutShift(children: React.ReactNode, dependencies: unknown[]) {
|
|
911
|
+
const [pending, setPending] = useState(false)
|
|
912
|
+
const [currentChildren, setCurrentChildren] = useState(children)
|
|
913
|
+
const [currentDependencies, setCurrentDependencies] = useState(dependencies)
|
|
914
|
+
|
|
915
|
+
if (!pending) {
|
|
916
|
+
if (isEqual(currentDependencies, dependencies)) {
|
|
917
|
+
if (currentChildren !== children) {
|
|
918
|
+
setCurrentChildren(children)
|
|
919
|
+
}
|
|
920
|
+
} else {
|
|
921
|
+
setCurrentDependencies(dependencies)
|
|
922
|
+
setPending(true)
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const startViewTransition = useCallback(() => {
|
|
927
|
+
setCurrentDependencies(dependencies)
|
|
928
|
+
setPending(false)
|
|
929
|
+
}, [dependencies])
|
|
930
|
+
|
|
931
|
+
return [pending ? currentChildren : children, pending, startViewTransition] as const
|
|
932
|
+
}
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
This Client Component is used to wrap the layout that should only be updated after the user has clicked the refresh button:
|
|
936
|
+
|
|
937
|
+
```diff
|
|
938
|
+
// src/app/products/page.tsx
|
|
939
|
+
|
|
940
|
+
import { AnimatePresence } from "framer-motion";
|
|
941
|
+
import * as motion from "framer-motion/client";
|
|
942
|
+
import {defineQuery} from 'next-sanity'
|
|
943
|
+
import { sanityFetch } from "@/sanity/lib/live";
|
|
944
|
+
+import {ProductsLayoutShift} from './products-page-layout-shift.tsx'
|
|
945
|
+
|
|
946
|
+
const PRODUCTS_QUERY = defineQuery(`*[_type == "product" && defined(slug.current)][0...$limit]`)
|
|
947
|
+
|
|
948
|
+
export default async function Page() {
|
|
949
|
+
const {data: products} = await sanityFetch({ query: PRODUCTS_QUERY, params: {limit: 10} });
|
|
950
|
+
+ // If the list over ids change, it'll trigger the toast asking the user to opt-in to refresh
|
|
951
|
+
+ // but if a product title has changed, perhaps to fix a typo, we update that right away
|
|
952
|
+
+ const ids = products.map((product) => product._id)
|
|
953
|
+
return (
|
|
954
|
+
<section>
|
|
955
|
+
+ <ProductsLayoutShift ids={ids}>
|
|
956
|
+
<AnimatePresence mode="popLayout">
|
|
957
|
+
{products.map((product) => (
|
|
958
|
+
<motion.article
|
|
959
|
+
key={product._id}
|
|
960
|
+
layout="position"
|
|
961
|
+
animate={{ opacity: 1 }}
|
|
962
|
+
exit={{ opacity: 0 }}
|
|
963
|
+
>
|
|
964
|
+
<a href={`/product/${product.slug}`}>{product.title}</a>
|
|
965
|
+
</motion.article>
|
|
966
|
+
))}
|
|
967
|
+
</AnimatePresence>
|
|
968
|
+
+ </ProductsLayoutShift>
|
|
969
|
+
</section>
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
With this approach we've limited the use of client components to just a single component. All the server components within `<ProductsLayoutShift>` remain as server components, with all their benefits.
|
|
975
|
+
|
|
976
|
+
## How does the Live Content API revalidate and refresh in real-time?
|
|
977
|
+
|
|
978
|
+
The architecture for `defineLive` works as follows:
|
|
979
|
+
|
|
980
|
+
1. `sanityFetch` automatically sets `fetch.next.tags` for you using opaque tags generated by our backend, prefixed with `sanity:`.
|
|
981
|
+
2. `<SanityLive />` listens to change events using the Sanity Live Content API (LCAPI).
|
|
982
|
+
3. When the LCAPI emits an event, `<SanityLive />` invokes a Server Function that calls `revalidateTag(`sanity:${tag}`)`.
|
|
983
|
+
4. Since it's a Server Function, Next.js will evict data fetches associated with the revalidated tag. The page is seamlessly updated with fresh content, which future visitors will also see thanks to `revalidateTag` integrating with ISR.
|
|
984
|
+
|
|
985
|
+
With this setup, as long as one visitor accesses your Next.js app after a content change, the cache is updated globally for all users, regardless of the specific URL they visit.
|
|
986
|
+
|
|
987
|
+
### Revalidating content changes from automations
|
|
988
|
+
|
|
989
|
+
If your content operations involve scenarios where you might not always have a visitor to trigger the `revalidateTag` event, there are two ways to ensure your content is never stale:
|
|
990
|
+
|
|
991
|
+
#### A) Use a GROQ powered webhook to call `revalidateTag(sanity)`
|
|
992
|
+
|
|
993
|
+
All queries made using `sanityFetch` include the `sanity` tag in their `fetch.next.tags` array. You can use this to call `revalidateTag('sanity')` in an API route that handles a GROQ webhook payload.
|
|
994
|
+
|
|
995
|
+
This approach can be considered a "heavy hammer" so it's important to limit the webhook events that trigger it. You could also implement this in a custom component to manually purge the cache if content gets stuck.
|
|
996
|
+
|
|
997
|
+
#### B) Setup a server-side `<SanityLive />` alternative
|
|
998
|
+
|
|
999
|
+
You can setup your own long-running server, using Express for example, to listen for change events using the Sanity Live Content API. Then, create an API route in your Next.js app:
|
|
1000
|
+
|
|
1001
|
+
```ts
|
|
1002
|
+
// src/app/api/revalidate-tag/route.ts
|
|
1003
|
+
import {revalidateTag} from 'next/cache'
|
|
1004
|
+
|
|
1005
|
+
export const POST = async (request) => {
|
|
1006
|
+
const {tags, isValid} = await validateRequest(request)
|
|
1007
|
+
if (!isValid) return new Response('No no no', {status: 400})
|
|
1008
|
+
for (const _tag of tags) {
|
|
1009
|
+
const tag = `sanity:${_tag}`
|
|
1010
|
+
revalidateTag(tag)
|
|
1011
|
+
// eslint-disable-next-line no-console
|
|
1012
|
+
console.log(`revalidated tag: ${tag}`)
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
Your Express app can then forward change events to this endpoint, ensuring your content is always up-to-date. This method guarantees that stale content is never served, even if no browser is actively viewing your app!
|
|
1018
|
+
|
|
592
1019
|
## Embedded Sanity Studio
|
|
593
1020
|
|
|
594
1021
|
Sanity Studio is a near-infinitely configurable content editing interface that can be embedded into any React application. For Next.js, you can embed the Studio on a route (like `/studio`). The Studio will still require authentication and be available only for members of your Sanity project.
|
|
@@ -774,3 +1201,4 @@ MIT-licensed. See [LICENSE][LICENSE].
|
|
|
774
1201
|
[vercel-content-link]: https://vercel.com/docs/workflow-collaboration/edit-mode#content-link?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
|
|
775
1202
|
[sanity-next-clean-starter]: https://www.sanity.io/templates/nextjs-sanity-clean
|
|
776
1203
|
[sanity-next-featured-starter]: https://www.sanity.io/templates/personal-website-with-built-in-content-editing
|
|
1204
|
+
[live-content-api]: https://www.sanity.io/docs/live-content-api?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-sanity",
|
|
3
|
-
"version": "9.8.
|
|
3
|
+
"version": "9.8.8",
|
|
4
4
|
"description": "Sanity.io toolkit for Next.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -139,11 +139,11 @@
|
|
|
139
139
|
"dependencies": {
|
|
140
140
|
"@portabletext/react": "^3.1.0",
|
|
141
141
|
"@sanity/client": "^6.22.2",
|
|
142
|
-
"@sanity/next-loader": "1.0
|
|
143
|
-
"@sanity/preview-kit": "5.1.
|
|
142
|
+
"@sanity/next-loader": "1.1.0",
|
|
143
|
+
"@sanity/preview-kit": "5.1.10",
|
|
144
144
|
"@sanity/preview-url-secret": "2.0.0",
|
|
145
|
-
"@sanity/visual-editing": "2.
|
|
146
|
-
"groq": "^3.62.
|
|
145
|
+
"@sanity/visual-editing": "2.4.2",
|
|
146
|
+
"groq": "^3.62.2",
|
|
147
147
|
"history": "^5.3.0"
|
|
148
148
|
},
|
|
149
149
|
"devDependencies": {
|