How to create nested folders in Sanity with Next.js
6 replies
Last updated: Oct 15, 2024
M
Is it possible to have nested folders in sanity nextjs? please help
Oct 3, 2024, 12:30 PM
Yes you can achieve this with a custom structure
https://www.sanity.io/docs/structure-builder-cheat-sheet#f48e79f54178
https://www.sanity.io/docs/structure-builder-cheat-sheet#f48e79f54178
Oct 3, 2024, 1:31 PM
You'll want to do something like this
// structure.ts export const structure = (S: any) => S.list() .title('Content') .items([ S.listItem() .title('FOLDER_NAME') .child( S.list() .title('FOLDER_NAME') .items([ S.listItem() .title("NESTED_FOLDER") .id('team1') .child( S.list() .title('Team 1 Resources') .items([ //your items here ]) ])
Oct 3, 2024, 1:35 PM
M
Okay,
user G
, I got it, Thank you for the response. If there is anything else I will get back to you.Oct 3, 2024, 1:55 PM
M
Hi
user G
I am getting one issue with schema and queryOct 5, 2024, 12:31 PM
M
Schema -import { UserIcon, } from '@sanity/icons';
import { defineField, defineType } from 'sanity';
export const socialproofType = defineType({
name: 'socialproof',
title: 'Socialproof',
type: 'document',
icon: UserIcon,
fields: [
defineField({
name: 'title',
type: 'string',
title: 'Title',
}),
defineField({
name: 'slug',
type: 'slug',
title: 'Slug',
options: {
source: 'title',
},
}),
defineField({
name: 'companies',
type: 'array',
title: 'Companies',
of: [
defineField({
name: 'image',
type: 'image',
title: 'Company Logo',
options: {
hotspot: true,
},
fields: [
defineField({
name: 'altText',
type: 'string',
title: 'Alt Text',
}),
],
}),
],
}),
],
preview: {
select: {
title: 'name',
media: 'image',
},
},
});
Query -
export const CUSTOMER_LOGOS_QUERY = defineQuery(
)
QueryType -
export type CUSTOMER_LOGOS_QUERYResult = {
title: string | null;
companies: Array<{
imageUrl: null;
altText: string | null;
}> | null;
} | null;
Why the imageUrl: null; is showing null should be string and null both right ? Can you please help me out.
import { defineField, defineType } from 'sanity';
export const socialproofType = defineType({
name: 'socialproof',
title: 'Socialproof',
type: 'document',
icon: UserIcon,
fields: [
defineField({
name: 'title',
type: 'string',
title: 'Title',
}),
defineField({
name: 'slug',
type: 'slug',
title: 'Slug',
options: {
source: 'title',
},
}),
defineField({
name: 'companies',
type: 'array',
title: 'Companies',
of: [
defineField({
name: 'image',
type: 'image',
title: 'Company Logo',
options: {
hotspot: true,
},
fields: [
defineField({
name: 'altText',
type: 'string',
title: 'Alt Text',
}),
],
}),
],
}),
],
preview: {
select: {
title: 'name',
media: 'image',
},
},
});
Query -
export const CUSTOMER_LOGOS_QUERY = defineQuery(
*[_type == "socialproof"]{ title, companies[] { "imageUrl": image.asset->url, altText } }[0]
QueryType -
export type CUSTOMER_LOGOS_QUERYResult = {
title: string | null;
companies: Array<{
imageUrl: null;
altText: string | null;
}> | null;
} | null;
Why the imageUrl: null; is showing null should be string and null both right ? Can you please help me out.
Oct 5, 2024, 12:33 PM
M
Hello
// service.schema.ts
import { TagIcon } from '
export const services_1 = defineType({
name: 'services1',
title: 'Service Page 1',
type: 'document',
icon: TagIcon,
groups: [
{
name: "content",
title: "Content",
},
{
name: "seo",
title: "SEO",
},
],
fields: [
defineField({
name: 'title',
type: 'string',
title: 'Title',
}),
// SEO Block
defineField({
name: "page_title",
title: "Page Title",
type: "string",
group: "seo",
validation: (rule) => rule.required(),
}),
defineField({
name: "meta_keywords",
title: "Meta Keywords",
type: "string",
group: "seo",
}),
defineField({
name: "page_meta_description",
title: "Page Meta Description",
type: "text",
rows: 4,
group: "seo",
}),
defineField({
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "page_title",
maxLength: 200,
slugify: (input) => input.toLowerCase().replace("page", "").replace(/\s+/g, "-").replace(/-$/, "").slice(0, 200),
},
description: "Define a custom slug or generate one from the 'Page Title'. Maximum character limit is 200.",
group: "seo",
validation: (rule) => rule.required(),
}),
defineField({
name: 'header',
type: 'reference',
title: 'Header',
to: [{ type: 'header1' }],
description: 'Select a header option.',
group: "content",
}),
defineField({
name: 'content',
title: 'Content Sections',
type: 'array',
of: [
{
type: 'reference',
name: 'heroSection',
to: [{ type: 'heroSection2' }],
title: 'Hero Section',
description: 'Select an existing Hero document.',
options: {
// Use the preview property to customize display
// Select the title and media to show in the preview
preview: {
select: {
title: 'title', // Field from the hero document
media: 'image', // Assuming there's an image field in the hero document
},
},
},
},
{
type: "reference",
name: 'feature2Section',
to: [{ type: 'feature2' }],
title: 'Feature Section',
description: 'Include Feature content.',
options: {
preview: {
select: {
title: 'title', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'feature1Section',
to: [{ type: 'process' }],
title: 'Feature Section',
description: 'Include Services content.',
options: {
preview: {
select: {
title: 'title', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'caseStudiesSection',
to: [{ type: 'caseStudies' }],
title: 'Case Studies Section',
description: 'Include Case Studies content.',
options: {
preview: {
select: {
title: 'title', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'productSection',
to: [{ type: 'cxfulSection' }],
title: 'Product Section',
description: 'Include Process content.',
options: {
preview: {
select: {
title: 'title', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'insightsSection',
to: [{ type: 'insights' }],
title: 'Insights Section',
description: 'Include Insight content.',
options: {
preview: {
select: {
title: 'Insights Section', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'faqsection',
to: [{ type: 'faq' }],
title: 'FAQ Section',
description: 'Include FAQ content.',
options: {
preview: {
select: {
title: 'FAQ Section', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'footerCTA',
to: [{ type: 'footerCTA' }],
title: 'FooterCTA Section',
description: 'Include FooterCTA content.',
options: {
preview: {
select: {
title: 'FooterCTA Section', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
],
description: 'Select one or more content sections to display on the home page.',
group: "content",
}),
defineField({
name: 'footer',
title: 'Footer',
type: 'reference',
to: [{ type: 'footer1' }],
description: 'Select a footer option.',
group: "content",
}),
],
})
// servicepage.tsx
import {
HERO_SECTION_2_QUERYResult,
SECONDARY_NAVBAR_QUERYResult,
OVERVIEW_QUERYResult,
CXFUL_QUERYResult,
CASE_STUDY_QUERYResult,
INSIGHTS_QUERYResult,
FAQ_QUERYResult,
FooterCTA_QUERYResult
} from '@/sanity.types'; import CaseStudiesWrapper from "@/app/sections/CaseStudy/CaseStudySliderWrapper";
import FAQWrapper from "@/app/sections/FAQSection/FAQSectionWrapper";
import InsightsSectionWrapper from "@/app/sections/Insights/InsightsSectionWrapper";
import FooterCTAWrapper from "@/app/sections/FooterCTA/FooterCTAWrapper";
import Product1 from "@/app/sections/ProductsSection/Product1";
import SecondaryNavbar from "../Navbar/SecondaryNavbar";
import HeroSection_2_Service from "@/app/sections/HeroSection/HeroSection_2_Service";
import Feature_2 from "@/app/sections/Features/Feature_2";
type Section =
| { _type: "heroSection"; data: HERO_SECTION_2_QUERYResult }
| { _type: "seconaryNavbar"; data: SECONDARY_NAVBAR_QUERYResult }
| { _type: "product"; data: CXFUL_QUERYResult }
| { _type: "overview"; data: OVERVIEW_QUERYResult }
| { _type: "casestudy"; data: CASE_STUDY_QUERYResult }
| { _type: "insights"; data: INSIGHTS_QUERYResult }
| { _type: "faq"; data: FAQ_QUERYResult }
| { _type: "footercta"; data: FooterCTA_QUERYResult };
interface ContentProps {
sections: Section[];
}
const ServiceContent: React.FC<ContentProps> = ({ sections }) => {
if (!sections || sections.length === 0) {
return <div>No content available</div>;
}
return (
<div>
{sections.map((section, index) => {
switch (section._type) {
case "heroSection":
return <HeroSection_2_Service key={index} data={section.data} />;
case "seconaryNavbar":
return <SecondaryNavbar key={index} data={section.data} />;
case "product":
return <Product1 key={index} data={section.data} />;
case "overview":
return <Feature_2 key={index} data={section.data} />;
case "casestudy":
return <CaseStudiesWrapper key={index} data={section.data} />;
case "insights":
return <InsightsSectionWrapper key={index} data={section.data} />;
case "faq":
return <FAQWrapper key={index} data={section.data} />;
case "footercta":
return <FooterCTAWrapper key={index} data={section.data} />;
default:
return null;
}
})}
</div>
);
};
if any file requried please let me know and please help me out of this.
user G
can you please help figure out how I can iterate in this content section. It is a service page sanity document and there a three different of them. But i am getting the same data for every page.// service.schema.ts
import { TagIcon } from '
user F
/icons'import { defineField, defineType } from 'sanity'export const services_1 = defineType({
name: 'services1',
title: 'Service Page 1',
type: 'document',
icon: TagIcon,
groups: [
{
name: "content",
title: "Content",
},
{
name: "seo",
title: "SEO",
},
],
fields: [
defineField({
name: 'title',
type: 'string',
title: 'Title',
}),
// SEO Block
defineField({
name: "page_title",
title: "Page Title",
type: "string",
group: "seo",
validation: (rule) => rule.required(),
}),
defineField({
name: "meta_keywords",
title: "Meta Keywords",
type: "string",
group: "seo",
}),
defineField({
name: "page_meta_description",
title: "Page Meta Description",
type: "text",
rows: 4,
group: "seo",
}),
defineField({
name: "slug",
title: "Slug",
type: "slug",
options: {
source: "page_title",
maxLength: 200,
slugify: (input) => input.toLowerCase().replace("page", "").replace(/\s+/g, "-").replace(/-$/, "").slice(0, 200),
},
description: "Define a custom slug or generate one from the 'Page Title'. Maximum character limit is 200.",
group: "seo",
validation: (rule) => rule.required(),
}),
defineField({
name: 'header',
type: 'reference',
title: 'Header',
to: [{ type: 'header1' }],
description: 'Select a header option.',
group: "content",
}),
defineField({
name: 'content',
title: 'Content Sections',
type: 'array',
of: [
{
type: 'reference',
name: 'heroSection',
to: [{ type: 'heroSection2' }],
title: 'Hero Section',
description: 'Select an existing Hero document.',
options: {
// Use the preview property to customize display
// Select the title and media to show in the preview
preview: {
select: {
title: 'title', // Field from the hero document
media: 'image', // Assuming there's an image field in the hero document
},
},
},
},
{
type: "reference",
name: 'feature2Section',
to: [{ type: 'feature2' }],
title: 'Feature Section',
description: 'Include Feature content.',
options: {
preview: {
select: {
title: 'title', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'feature1Section',
to: [{ type: 'process' }],
title: 'Feature Section',
description: 'Include Services content.',
options: {
preview: {
select: {
title: 'title', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'caseStudiesSection',
to: [{ type: 'caseStudies' }],
title: 'Case Studies Section',
description: 'Include Case Studies content.',
options: {
preview: {
select: {
title: 'title', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'productSection',
to: [{ type: 'cxfulSection' }],
title: 'Product Section',
description: 'Include Process content.',
options: {
preview: {
select: {
title: 'title', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'insightsSection',
to: [{ type: 'insights' }],
title: 'Insights Section',
description: 'Include Insight content.',
options: {
preview: {
select: {
title: 'Insights Section', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'faqsection',
to: [{ type: 'faq' }],
title: 'FAQ Section',
description: 'Include FAQ content.',
options: {
preview: {
select: {
title: 'FAQ Section', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
{
type: "reference",
name: 'footerCTA',
to: [{ type: 'footerCTA' }],
title: 'FooterCTA Section',
description: 'Include FooterCTA content.',
options: {
preview: {
select: {
title: 'FooterCTA Section', // Field from the socialproof document
media: 'image', // Assuming there's an image field
},
},
},
},
],
description: 'Select one or more content sections to display on the home page.',
group: "content",
}),
defineField({
name: 'footer',
title: 'Footer',
type: 'reference',
to: [{ type: 'footer1' }],
description: 'Select a footer option.',
group: "content",
}),
],
})
// servicepage.tsx
import {
HERO_SECTION_2_QUERYResult,
SECONDARY_NAVBAR_QUERYResult,
OVERVIEW_QUERYResult,
CXFUL_QUERYResult,
CASE_STUDY_QUERYResult,
INSIGHTS_QUERYResult,
FAQ_QUERYResult,
FooterCTA_QUERYResult
} from '@/sanity.types'; import CaseStudiesWrapper from "@/app/sections/CaseStudy/CaseStudySliderWrapper";
import FAQWrapper from "@/app/sections/FAQSection/FAQSectionWrapper";
import InsightsSectionWrapper from "@/app/sections/Insights/InsightsSectionWrapper";
import FooterCTAWrapper from "@/app/sections/FooterCTA/FooterCTAWrapper";
import Product1 from "@/app/sections/ProductsSection/Product1";
import SecondaryNavbar from "../Navbar/SecondaryNavbar";
import HeroSection_2_Service from "@/app/sections/HeroSection/HeroSection_2_Service";
import Feature_2 from "@/app/sections/Features/Feature_2";
type Section =
| { _type: "heroSection"; data: HERO_SECTION_2_QUERYResult }
| { _type: "seconaryNavbar"; data: SECONDARY_NAVBAR_QUERYResult }
| { _type: "product"; data: CXFUL_QUERYResult }
| { _type: "overview"; data: OVERVIEW_QUERYResult }
| { _type: "casestudy"; data: CASE_STUDY_QUERYResult }
| { _type: "insights"; data: INSIGHTS_QUERYResult }
| { _type: "faq"; data: FAQ_QUERYResult }
| { _type: "footercta"; data: FooterCTA_QUERYResult };
interface ContentProps {
sections: Section[];
}
const ServiceContent: React.FC<ContentProps> = ({ sections }) => {
if (!sections || sections.length === 0) {
return <div>No content available</div>;
}
return (
<div>
{sections.map((section, index) => {
switch (section._type) {
case "heroSection":
return <HeroSection_2_Service key={index} data={section.data} />;
case "seconaryNavbar":
return <SecondaryNavbar key={index} data={section.data} />;
case "product":
return <Product1 key={index} data={section.data} />;
case "overview":
return <Feature_2 key={index} data={section.data} />;
case "casestudy":
return <CaseStudiesWrapper key={index} data={section.data} />;
case "insights":
return <InsightsSectionWrapper key={index} data={section.data} />;
case "faq":
return <FAQWrapper key={index} data={section.data} />;
case "footercta":
return <FooterCTAWrapper key={index} data={section.data} />;
default:
return null;
}
})}
</div>
);
};
if any file requried please let me know and please help me out of this.
Oct 15, 2024, 5:54 AM
Sanity– build remarkable experiences at scale
Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.