-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Content Layer #946
Comments
Just wondering how this will work (if at all) for components, like in MDX? So the use case I'm thinking is when a CMS is used, they typically have a WYSIWYG rich text editor where custom components can be inserted as shown here: https://www.storyblok.com/tp/create-custom-components-in-storyblok-and-astro Will this new API support this concept? |
This is cool! I do a sort-of version of this for my review site (david.reviews), where all the data is stored in Airtable. I load objects from the Airtable API based on a schema and cache responses in JSON locally (since loading and paging takes a while and is too slow for local development). It sort of feels like what an API-backed custom content collection could look like. The whole thing is strongly typed, which is cool! I wrote about it in more detail here: https://xavd.id/blog/post/static-review-site-with-airtable/ |
Is this enacted somewhere yet/available as an experimental feature or on a beta version? I've been trying to build collections from WordPress content fetched via GraphQL queries and I think this'll fix exactly what I want. |
How would this work with dependency files like images? Right now, the I'm personally excited to separate my content folder and astro theme into separate GitHub repos, so that's where my my perspective comes from. |
Coming from Gatsby and loving the data layer. A few points that I always found hard:
|
I like the idea of being able to store content in a single file. I explored adding CSV support to the existing content collection APIs, and found there were too many assumptions around a directory of files. Providing a higher-level of abstraction around how the data is accessed/retrieved while keeping the simple APIs and excellent typing would be ideal. In essence, separating the client usage (the query APIs, generated schemas, metadata (frontmatter) and representation) from how the source is retrieved (single file, flat-file directory structure, database, remote service) would be really helpful. |
No, still at the planning stage right now. We'll share experimental builds once they're available. |
Love the initial look of this. Will is still be possible to include a (zod) schema of some kind in the |
Yes, we'd want that to generate types too. Hopefully content sources could define this automatically in some scenarios. |
my wishlist for astro content layer:
|
Is Zod still going to be the official validator for Content Collections or can people use something else. |
Love this concept! In the current implementation Is there a spec yet for what the data property has to be? It would be nice to remove the limitation of only having a single For example, you could load data from a CSV into a MDAST with a table structure - and then it would render the CSV as if it had been created as a table in markdown (only the data is much more manageable in the CSV for large data sets). |
@reasonadmin I think support for implementing custom renderers is a must, and it would then make sense to allow these to be more flexible than a single defineCollection({
name: 'accounts',
data: csvLoader("data/accounts/**/*.csv"),
}); ..and then: import { getEntry, getEntries } from 'astro:content';
const account = await getEntry('accounts', '2024-20');
// Access the parsed CSV data
const { rows } = account;
// Render a table. Pass options to render, or maybe make them props for `<Content />`
const { Content } = account.render({ /* typesafe filter, sort options etc */ }); |
@ascorbic Is it possible to join these two ideas together: For example: defineCollection({
name: 'my-data',
data: async (db: DB, watcher: FileSystemWatcher) => {
const hash = sha256(content);
await db.addContent(hash, content);
// Only build updates to files if the hash is different from the one in the DB
// DB is accessible between static builds for incremental SSG rendering
},
}) How about something like this for rendering: defineCollection({
name: 'accounts',
data: Astro.autoDBWrapper( [{page: 1},{page: 2}] ),
render: async(entry, options) => {
//Return an object that has minimum fields {metadata : Object , content : String}
//As this content will not be directly loaded via an import statement (e.g. const {Content} = import ('myfile.md');
//We don't need a loader and therefore don't need to Stringify things as JS for the loader?
}
});
/* -- */
import { getEntry } from 'astro:content';
const { entry, render } = await getEntry('accounts', '2024-20');
const {metadata, content} = render(entry, { 'fileType: 'HTML' });
const {metadata, content} = render(entry, { 'fileType: 'XML' }); |
In addition to the proposed singletons, here my thoughts. Rich query APII like the ideas proposed in #574 or #518. Here another format possible: const welcomePost = await queryEntry('posts', {where: {slug: "welcome", lang: "en"}});
const frenchPosts = await queryCollection("posts", {
first: 10,
where: { lang: "fr" },
sort: {key: "publicationDate", order: "DESC"},
}); Sub-collections / Nested data-typesIdeaIt would be nice to allow "sub-collections". The idea would be to improve content organization and to share some commons data-types between a same collection while sub-collections could have additional data-types. Then we could:
ExampleMaybe an example will help describe my proposal, so imagine a collection named All the formats share common data-types:
Then each format could have additional data-types:
The collection could be defined as follow: const posts = defineCollection({
type: 'content',
schema: z.object({
isDraft: z.boolean(),
publicationDate: z.string().transform((str) => new Date(str)),
}),
subCollections: {
changelog: {
type: 'content',
},
thought: {
type: 'content',
schema: z.object({
subject: z.string()
}),
},
tutorial: {
type: 'content',
schema: z.object({
difficulty: z.enum(["easy", "medium", "hard"]),
software: z.string(),
}),
},
}
});
export const collections = {
posts
} The generated types would be: type Changelog = {
isDraft: boolean;
publicationDate: Date;
subCollection: "changelog";
}
type Tutorial = {
isDraft: boolean;
publicationDate: Date;
subCollection: "tutorial";
software: string;
difficulty: "easy" | "medium" | "hard"
}
type Thought = {
isDraft: boolean;
publicationDate: Date;
subCollection: "thought";
subject: string;
}
type Post = Changelog | Tutorial | Thought; When validating data-types, an error is thrown with the following examples:
If the Then it would be possible to get all the posts (with mixed formats) using It would also be possible to query a sub-collection directly with, for example, For the organization, I don't know what would be best:
|
I wonder if this will allow to render pure .md with an Astro component (my use case is pure markdown, e.g. existing github repo, and not .mdx that is not as tolerant as md parser). e.g. I have Heading.astro that takes props, and Code.astro,... if so how would that look like ? |
We have a preview release available, so I'd love if you can give it a try and share your feedback. Full details are in the PR, including changes in the API: withastro/astro#11334 |
Will the new Content Layer support loading custom components? |
What would they look like? Genuinely asking. We're considering various ways to render a new content collection, and components aren't off the table, but how would you run the schema against them? |
Honestly I don't know, but this is surely an important thing to think about as in the Storyblok example - content writers are likely to want to use them somehow. If a content loader doesn't support that, would that be a feature regression from what is avaliable this way? https://www.storyblok.com/tp/create-custom-components-in-storyblok-and-astro |
@lloydjatkinson you'll still be able to use mdx with this new API, so yes you can still use components. |
will this support multiple markdown/richtext fields per collection? for example, would it be possible to express something like: const schema = object({
title: string(),
sections: array(object({
title: string(),
content: mdx(),
}))
}) |
One thing that comes to mind that was a huge pain in the ass for Gatsby (and any other content aggregation abstraction I've worked with) is relationships between content. A major motivator for using a content abstraction like this is centralizing data access. However, if there's no way to define relationships between the data, then teams are still defaulting to creating userland data merging and manipulation, which is (in my experience, at least) one of the key pain points that leads to wanting an aggregation layer in the first place. I may have missed it in other discussion, but is there any plan or initial thoughts around how this would be managed? Example Use CaseFor example:
Idea 1: Explicit API for creating relationshipsI don't know that I like this API, but for some pseudo-code to show how this might work: import { defineCollection, file, z, createRelationship } from 'astro:content';
import { contentful, contentfulSchema } from '../loaders/contentful';
import { likes, likesSchema } from '../loaders/likes';
const blog = defineCollection({
type: "experimental_data",
loader: contentful(/* some config */),
schema: z.object({
...contentfulSchema,
likes: createRelationship({
collection: 'likes',
type: z.number(), // <-- (optional) type for the linked data — could be inferred?
key: 'id', // <-- the Contentful schema field to link on
foreignKey: 'blogId', // <-- the 'likes' schema field to link on
resolver: (entry) => entry.count, // <-- (optional) how to link data in (full entry if omitted),
}),
}),
});
const likes = defineCollection({
type: "experimental_data",
loader: likes(),
schema: z.object({
...likesSchema,
blog: createRelationship({ collection: 'likes', key: 'blog_id', foreignKey: 'id' }),
}),
});
export const collections = { blog, likes }; Idea 2: Joins and an optional projection APII like the way GraphQL and Sanity's GROQ allow you to dig into a referenced entry and get just the fields you need. Maybe something like that is possible? import { defineCollection, file, z, reference } from 'astro:content';
import { contentful, contentfulSchema } from '../loaders/contentful';
import { comments, commentsSchema } from '../loaders/comments';
import { likes, likesSchema } from '../loaders/likes';
const blog = defineCollection({
type: "experimental_data",
loader: contentful(/* some config */),
references: {
// an optional resolver allows for custom projections of linked content
comments: reference(contentfulSchema.id, commentsSchema.blogId, (comment) => ({
author: comment.author.displayName,
content: comment.content,
date: new Date(comment.date).toLocaleString(),
})),
// returning a single value is also possible
likes: reference(contentfulSchema.id, likesSchema.blogId, (entry) => entry.count),
},
});
const comments = defineCollection({
type: "experimental_data",
loader: comments(),
references: {
// by default the full blog post entry is added as the `blog` key value
blog: reference(commentsSchema.blogId, contentfulSchema.id),
},
});
const likes = defineCollection({
type: "experimental_data",
loader: likes(),
references: {
blog: reference(likesSchema.blogId, contentfulSchema.id),
},
});
export const collections = { blog, comments, likes }; I don't have strong opinions about the specifics, but I do think it's really important to talk through how cross-collection relationships fit into the content layer. Dropping this more to start the conversation than to try and assert a "right way" to do anything. (Also, let me know if I should move this to a separate discussion.) |
Really excited to see this, as a former Gatsby resolver addict 😅 For background: what worked quite well for us with Gatsby was creating our own unifying content schema, based on our components and page types, and then just using resolvers to massage the incoming data into the format defined by those (e.g. Appearances pulled from Contentful): This is still a pretty simplified description of it., page and component definitions can actually be generic here, everything gets generated based off JSON Schema definitions that describe said pages and components. But that whole setup ended up being way to complex, tbh! This is also interesting to me / us btw, because we're currently thinking about adding Astro starters in addition to our existing Next.js based ones. |
I think that a lot of what @jlengstorf and @julrich are suggesting in terms of projections and resolvers could be achieved with the zod schema, particularly with things like |
I think you're right @ascorbic. Really need to further dig into Zod, but it sure looks like it could do a lot of the heavy lifting! |
Are there any restrictions around For example, if I'm using a future Astro Studio connector, I would like to be able to update values in the Astro Studio web UI and then have local process watching for changes and trigger a local dev preview update without me needing to refresh the page. This may be possible already with what's created or in mind, but didn't seem to be the case explicitly. |
Will file types still be determined by extensions? I've got a bunch of .md.j2 files that I would love to be recognized as basic md files. It looks like Content Layer is taking in a glob of files, so the only way to determine file type would be extension. |
@lorenzolewis The watcher is optional, and you can handle watching however you want. I'm hoping to expose hooks to integrations to enable them to do updates in their own way. |
@rambleraptor yes, currently that's how it's handled. I'd be interested in ideas for how they could be specified otherwise. |
Today content collection entries have a render method. This makes it annoying to simply pass entries to framework components. Instead the render method should just be a library function taking a content entry. That's to say, collection entries should be serializable by default. |
Pardon for my question, and I don't mean to cause any pressure, but do we have any estimate on when we might be able to see an experimental release? Our current flow with Jekyll is: Excel Spreadsheet with hundreds of lines and many dozen columns each -> macro that generates YAML and JSON files -> copies (& transforms) the images to the right folders too -> we ingest everything into the project and load it all up with a mix of Liquid (Jekyll's language) and JS... since we started building this jekyll site back in the day, we've already developed almost 10 other websites with Astro and it's been a blessing, so we can't wait for this migration... Edit: I see we're gonna get a discussion going tomorrow! Exciting. I'll def. try and attend. I see there's a Draft PR already open too... so if I'd had to take a wild guess, in about 2-3 weeks or a month at most we should be seeing an experimental release 😍 |
I haven't dug into the code at all to support this idea. Right now, it looks like data takes in an array of File objects. We could potentially create a Markdown object, JSON object, etc that has a reference to an underlying file. If an array object is a normal File, the extension check occurs. Otherwise, it defaults to whatever object type the user specified. |
@rambleraptor the file list comes from the filesystem glob, so there's no realistic way to know automatically what type it is except via the file extension. It would need to be configured somewhere as an additional extension for that type. |
Got it! That makes a lot of sense. Something I've been thinking about is creating a loader that acts as a preprocessor (could take a md.j2 file, do some preprocessing, return a .md file) One way or another, I wouldn't call this a major use case. I think the existing API could allow for this use case |
Maybe this will solve the problem pointed in #434. |
Yes, it will |
This has moved to stage 3. Please continue the discussion on the stage 3 PR |
Summary
Background & Motivation
Content Collections are a key primitive that brings people to Astro. Content Collections make it easy to work with local content (MD, MDX, Markdoc, etc) inside of your Astro project. They give you structure (
src/content/[collection-name]/*
), schema validation for frontmatter, and querying APIs.Goals
Example
The text was updated successfully, but these errors were encountered: