Security News
vlt Debuts New JavaScript Package Manager and Serverless Registry at NodeConf EU
vlt introduced its new package manager and a serverless registry this week, innovating in a space where npm has stagnated.
next-sanity
Advanced tools
Sanity.io toolkit for Next.js.
Features:
next-sanity
Running groq queriesnext-sanity/preview
Live real-time preview
next-sanity/studio
(dev-preview)
$ npm install next-sanity @portabletext/react @sanity/image-url
// or
$ yarn add next-sanity @portabletext/react @sanity/image-url
next-sanity
Running groq queriesimport {createClient, groq} from 'next-sanity'
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2022-11-16"
const client = createClient({
projectId,
dataset,
apiVersion,
useCdn: typeof document !== 'undefined',
})
const data = await client.fetch(groq`*[]`)
next-sanity/preview
Live real-time previewYou can implement real-time client side preview using definePreview
. It works by streaming the whole dataset to the browser, which it keeps updated using listeners and Mendoza patches. When it receives updates, then the query is run against the client-side datastore using groq-js.
It uses @sanity/preview-kit
under the hood, which can be used in frameworks other than Nextjs if it supports React 18 Suspense APIs.
When running next dev
locally these examples start and exit preview mode by opening localhost:3000/api/preview and localhost:3000/api/exit-preview.
Pros:
Cons:
dual
loginMethod in Sanity Studio:
pages/api/preview.js
:
export default function preview(req, res) {
res.setPreviewData({})
res.writeHead(307, {Location: '/'})
res.end()
}
pages/api/exit-preview.js
:
export default function exit(req, res) {
res.clearPreviewData()
res.writeHead(307, {Location: '/'})
res.end()
}
components/DocumentsCount.js
:
import groq from 'groq'
export const query = groq`count(*[])`
export function DocumentsCount({data}) {
return (
<>
Documents: <strong>{data}</strong>
</>
)
}
lib/sanity.client.js
import {createClient} from 'next-sanity'
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2022-11-16"
export const client = createClient({projectId, dataset, apiVersion, useCdn: false})
lib/sanity.preview.js
'use client'
import {definePreview} from 'next-sanity/preview'
import {projectId, dataset} from 'lib/sanity.client'
function onPublicAccessOnly() {
throw new Error(`Unable to load preview as you're not logged in`)
}
export const usePreview = definePreview({projectId, dataset, onPublicAccessOnly})
components/PreviewDocumentsCount.js
:
'use client'
import {usePreview} from 'lib/sanity.preview'
import {query, DocumentsCount} from 'components/DocumentsCount'
export default function PreviewDocumentsCount() {
const data = usePreview(null, query)
return <DocumentsCount data={data} />
}
pages/index.js
:
import {PreviewSuspense} from 'next-sanity/preview'
import {lazy} from 'react'
import {DocumentsCount, query} from 'components/DocumentsCount'
import {client} from 'lib/sanity.client'
const PreviewDocumentsCount = lazy(() => import('components/PreviewDocumentsCount'))
export const getStaticProps = async ({preview = false}) => {
if (preview) {
return {props: {preview}}
}
const data = await client.fetch(query)
return {props: {preview, data}}
}
export default function IndexPage({preview, data}) {
if (preview) {
return (
<PreviewSuspense fallback="Loading...">
<PreviewDocumentsCount />
</PreviewSuspense>
)
}
return <DocumentsCount data={data} />
}
appDir
components/PreviewSuspense.js
:
'use client'
// Once rollup supports 'use client' module directives then 'next-sanity' will include them and this re-export will no longer be necessary
export {PreviewSuspense as default} from 'next-sanity/preview'
app/page.js
:
import {lazy} from 'react'
import {previewData} from 'next/headers'
import PreviewSuspense from 'components/PreviewSuspense'
import {DocumentsCount, query} from 'components/DocumentsCount'
import {client} from 'lib/sanity.client'
const PreviewDocumentsCount = lazy(() => import('components/PreviewDocumentsCount'))
export default async function IndexPage() {
if (previewData()) {
return (
<PreviewSuspense fallback="Loading...">
<PreviewDocumentsCount />
</PreviewSuspense>
)
}
const data = await client.fetch(query)
return <DocumentsCount data={data} />
}
By providing a read token (Sanity API token with viewer
rights) you override the built-in auth and get more control and flexibility.
Pros:
Cons:
token
in your js bundle, or preventing the /api/preview?secret=${secret}
from being easily guessable.@sanity/groq-store
currently requires event-source-polyfill
since native window.EventSource
does not support setting Authorization
headers needed for the token auth.pages/api/preview.js
:
import getSecret from 'lib/getSecret'
export default async function preview(req, res) {
// The secret can't be stored in an env variable with a NEXT_PUBLIC_ prefix, as it would make you vulnerable to leaking the token to anyone.
// If you don't have an custom API with authentication that can handle checking secrets, you may use https://github.com/sanity-io/sanity-studio-secrets to store the secret in your dataset.
const secret = await getSecret()
// This is the most common way to check for auth, but we encourage you to use your existing auth infra to protect your token and securely transmit it to the client
if (!req.query.secret || req.query.secret !== secret) {
return res.status(401).json({message: 'Invalid secret'})
}
res.setPreviewData({token: process.env.SANITY_API_READ_TOKEN})
res.writeHead(307, {Location: '/'})
res.end()
}
pages/api/exit-preview.js
:
export default function exit(req, res) {
res.clearPreviewData()
res.writeHead(307, {Location: '/'})
res.end()
}
components/DocumentsCount.js
:
import groq from 'groq'
export const query = groq`count(*[])`
export function DocumentsCount({data}) {
return (
<>
Documents: <strong>{data}</strong>
</>
)
}
lib/sanity.client.js
import {createClient} from 'next-sanity'
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2022-11-16"
export const client = createClient({projectId, dataset, apiVersion, useCdn: false})
lib/sanity.preview.js
'use client'
import {definePreview} from 'next-sanity/preview'
import {projectId, dataset} from 'lib/sanity.client'
export const usePreview = definePreview({projectId, dataset})
components/PreviewDocumentsCount.js
:
'use client'
import {usePreview} from 'lib/sanity.preview'
import {query, DocumentsCount} from 'components/DocumentsCount'
export default function PreviewDocumentsCount({token}) {
const data = usePreview(token, query)
return <DocumentsCount data={data} />
}
pages/index.js
:
import {PreviewSuspense} from 'next-sanity/preview'
import {lazy} from 'react'
import {DocumentsCount, query} from 'components/DocumentsCount'
import {client} from 'lib/sanity.client'
const PreviewDocumentsCount = lazy(() => import('components/PreviewDocumentsCount'))
export const getStaticProps = async ({preview = false, previewData = {}}) => {
if (preview && previewData?.token) {
return {props: {preview, token: previewData.token}}
}
const data = await client.fetch(query)
return {props: {preview, data}}
}
export default function IndexPage({preview, token, data}) {
if (preview) {
return (
<PreviewSuspense fallback="Loading...">
<PreviewDocumentsCount token={token} />
</PreviewSuspense>
)
}
return <DocumentsCount data={data} />
}
appDir
components/PreviewSuspense.js
:
'use client'
// Once rollup supports 'use client' module directives then 'next-sanity' will include them and this re-export will no longer be necessary
export {PreviewSuspense as default} from 'next-sanity/preview'
app/page.js
:
import {lazy} from 'react'
import {previewData} from 'next/headers'
import PreviewSuspense from 'components/PreviewSuspense'
import {DocumentsCount, query} from 'components/DocumentsCount'
import {client} from 'lib/sanity.client'
const PreviewDocumentsCount = lazy(() => import('components/PreviewDocumentsCount'))
export default async function IndexPage() {
if (previewData()?.token) {
return (
<PreviewSuspense fallback="Loading...">
<PreviewDocumentsCount token={previewData().token} />
</PreviewSuspense>
)
}
const data = await client.fetch(query)
return <DocumentsCount data={data} />
}
The real-time preview isn't optimized and comes with a configured limit of 3000 documents. You can experiment with larger datasets by configuring the hook with documentLimit: <Integer>
. Be aware that this might significantly affect the preview performance.
You may use the includeTypes
option to reduce the amount of documents and reduce the risk of hitting the documentLimit
:
import {definePreview} from 'next-sanity/preview'
export const usePreview = definePreview({
projectId,
dataset,
documentLimit: 10000,
includeTypes: ['page', 'product', 'sanity.imageAsset'],
})
We have plans for optimizations in the roadmap.
next-sanity/studio
(dev-preview)The latest version of Sanity Studio allows you to embed a near-infinitely configurable content editing interface into any React application. This opens up many possibilities:
The basic setup is two files:
pages/[[...index]].tsx
// Import your sanity.config.ts file
import config from '../sanity.config'
import {NextStudio} from 'next-sanity/studio'
export default function StudioPage() {
// Loads the Studio, with all the needed meta tags and global CSS required for it to render correctly
return <NextStudio config={config} />
}
The <NextStudio />
wraps <Studio />
component and supports forwarding all its props:
import {Studio} from 'sanity'
pages/_document.tsx
import {ServerStyleSheetDocument} from 'next-sanity/studio'
// Set up SSR for styled-components, ensuring there's no missing CSS when deploying a Studio in Next.js into production
export default class Document extends ServerStyleSheetDocument {}
StudioProvider
and StudioLayout
If you want to go lower level and have more control over the studio you can pass StudioProvider
and StudioLayout
from sanity
as children
:
import {NextStudio} from 'next-sanity/studio'
import {StudioProvider, StudioLayout} from 'sanity'
import config from '../sanity.config'
function StudioPage() {
return (
<NextStudio config={config}>
<StudioProvider config={config}>
{/* Put components here and you'll have access to the same React hooks as Studio gives you when writing plugins */}
<StudioLayout />
</StudioProvider>
</NextStudio>
)
}
<ServerStyleSheetDocument />
You can still customize _document.tsx
, the same way you would the default <Document />
component from next/document
:
import {ServerStyleSheetDocument} from 'next-sanity/studio'
export default class Document extends ServerStyleSheetDocument {
static async getInitialProps(ctx: DocumentContext) {
// You can still override renderPage:
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => <App {...props} />,
})
const initialProps = await ServerStyleSheetDocument.getInitialProps(ctx)
const extraStyles = await getStyles()
return {
...initialProps,
// Add to the default styles if you want
styles: [initialProps.styles, extraStyles],
}
}
render() {
// do the same stuff as in `next/document`
}
}
If you only need parts of what <NextStudio />
does for you, but not all of it.
No problem. You can import any which one of the components that <NextStudio />
is importing and assemble them in any way you want.
import {Studio, type Config} from 'sanity'
import {NextStudioGlobalStyle, NextStudioHead} from 'next-sanity/studio'
// This implementation will only load the bare minimum of what's required for the Studio to render correctly. No favicons, fancy <meta name="theme-color"> tags or the like
export default function CustomNextStudio({config}: {config: Config}) {
return (
<>
<Studio config={config} />
<NextStudioHead>{/* Custom extra stuff in <head> */}</NextStudioHead>
<NextStudioGlobalStyle />
</>
)
}
And while <NextStudio />
have all features enabled by default allowing you to opt-out by giving it props, the inner components <NextStudioHead />
and <NextStudioGlobalStyle />
are opt-in.
This means that these two StudioPage
components are functionally identical:
import {
NextStudio,
NextStudioGlobalStyle,
NextStudioHead,
useTheme,
useBackgroundColorsFromTheme,
} from 'next-sanity/studio'
import {Studio} from 'sanity'
import config from '../sanity.config'
// Turning all the features off, leaving only bare minimum required meta tags and styling
function StudioPage() {
return (
<NextStudio
config={config}
// an empty string turns off the CSS that sets a background on <html>
unstable__bg=""
unstable__noTailwindSvgFix
unstable__noFavicons
// an empty string turns off the <title> tag
unstable__document_title=""
/>
)
}
// Since no features are enabled it works the same way
function Studiopage() {
const theme = useTheme(config)
const {themeColorLight, themeColorDark} = useBackgroundColorsFromTheme(theme)
return (
<>
<Studio config={config} />
<NextStudioHead themeColorLight={themeColorLight} themeColorDark={themeColorDark} />
<NextStudioGlobalStyle />
</>
)
}
v1
createPreviewSubscriptionHook
is replaced with definePreview
There are several differences between the hooks. First of all, definePreview
requires React 18 and Suspense. And as it's designed to work with React Server Components you provide token
in the hook itself instead of in the definePreview
step. Secondly, definePreview
encourages code-splitting using React.lazy
and that means you only call the usePreview
hook in a component that is lazy loaded. Quite different from usePreviewSubscription
which was designed to be used in both preview mode, and in production by providing initialData
.
The files that are imported here are the same as the Next 12 example.
pages/index.js
import {createPreviewSubscriptionHook} from 'next-sanity'
import {DocumentsCount, query} from 'components/DocumentsCount'
import {client, projectId, dataset} from 'lib/sanity.client'
export const getStaticProps = async ({preview = false}) => {
const data = await client.fetch(query)
return {props: {preview, data}}
}
const usePreviewSubscription = createPreviewSubscriptionHook({projectId, dataset})
export default function IndexPage({preview, data: initialData}) {
const {data} = usePreviewSubscription(indexQuery, {initialData, enabled: preview})
return <DocumentsCount data={data} />
}
components/PreviewDocumentsCount.js
import {definePreview} from 'next-sanity/preview'
import {projectId, dataset} from 'lib/sanity.client'
const usePreview = definePreview({projectId, dataset})
export default function PreviewDocumentsCount() {
const data = usePreview(null, query)
return <DocumentsCount data={data} />
}
pages/index.js
import {lazy} from 'react'
import {PreviewSuspense} from 'next-sanity/preview'
import {DocumentsCount, query} from 'components/DocumentsCount'
import {client} from 'lib/sanity.client'
const PreviewDocumentsCount = lazy(() => import('components/PreviewDocumentsCount'))
export const getStaticProps = async ({preview = false}) => {
const data = await client.fetch(query)
return {props: {preview, data}}
}
export default function IndexPage({preview, data}) {
if (preview) {
return (
<PreviewSuspense fallback={<DocumentsCount data={data} />}>
<PreviewDocumentsCount />
</PreviewSuspense>
)
}
return <DocumentsCount data={data} />
}
createCurrentUserHook
is removedIf you used this hook to check if the user is cookie authenticated:
import {createCurrentUserHook} from 'next-sanity'
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
const useCurrentUser = createCurrentUserHook({projectId})
const useCheckAuth = () => {
const {data, loading} = useCurrentUser()
return loading ? false : !!data
}
export default function Page() {
const isAuthenticated = useCheckAuth()
}
Then you can achieve the same functionality using @sanity/preview-kit
and suspend-react
:
import {suspend} from 'suspend-react'
import {_checkAuth} from '@sanity/preview-kit'
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
const useCheckAuth = () =>
suspend(() => _checkAuth(projectId, null), ['@sanity/preview-kit', 'checkAuth', projectId])
export default function Page() {
const isAuthenticated = useCheckAuth()
}
v0.4
createPortableTextComponent
is removedThis utility used to wrap @sanity/block-content-to-react
. It's encouraged to upgrade to @portabletext/react
.
$ npm install @portabletext/react
// or
$ yarn add @portabletext/react
-import { createPortableTextComponent } from 'next-sanity'
+import { PortableText as PortableTextComponent } from '@portabletext/react'
-export const PortableText = createPortableTextComponent({ serializers: {} })
+export const PortableText = (props) => <PortableTextComponent components={{}} {...props} />
Please note that the serializers
and components
are not 100% equivalent.
Check the full migration guide.
createImageUrlBuilder
is removedThis utility is no longer wrapped by next-sanity
and you'll need to install the dependency yourself:
$ npm install @sanity/image-url
// or
$ yarn add @sanity/image-url
-import { createImageUrlBuilder } from 'next-sanity'
+import createImageUrlBuilder from '@sanity/image-url'
Run "CI & Release" workflow. Make sure to select the main branch and check "Release new version".
Semantic release will only release on configured branches, so it is safe to run release on any branch.
MIT-licensed. See LICENSE.
FAQs
Sanity.io toolkit for Next.js
The npm package next-sanity receives a total of 68,006 weekly downloads. As such, next-sanity popularity was classified as popular.
We found that next-sanity demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 63 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
vlt introduced its new package manager and a serverless registry this week, innovating in a space where npm has stagnated.
Security News
Research
The Socket Research Team uncovered a malicious Python package typosquatting the popular 'fabric' SSH library, silently exfiltrating AWS credentials from unsuspecting developers.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.