Remix Blog
Words are nice... but code speaks louder. Dive into a fully commented project template, showcasing these techniques (and more) in action.
Rich-text in DatoCMS is stored in Structured Text fields, as it offers many advantages over regular HTML.
There's a lot to be said about Structured Text and the extensibility of it, but for now let's just say that it returns content in a particular JSON format called dast
which resembles this example:
{"schema": "dast","document": {"type": "root","children": [{"type": "heading","level": 1,"children": [{"type": "span","value": "Hello world!"}]}]}}
To make it easy to render Structured Text inside your Remix projects, we released a package called react-datocms
that exposes a <StructuredText />
component that performs all the tedious work for you.
To take advantage of it, install the react-datocms
package if you haven't already:
npm i --save react-datocms
Then, inside your page, make a GraphQL query to fetch a Structured Text field, and feed the result to the data
prop of a <StructuredText />
component:
import { load } from "~/lib/datocms";import { StructuredText } from "react-datocms";import { useLoaderData } from "remix";const HOMEPAGE_QUERY = `query HomePage($limit: IntType) {posts: allBlogPosts(first: $limit) {idtitlecontent {value}}}`;export async function loader() {return load(HOMEPAGE_QUERY, {variables: { limit: 10 }});}export default function Home() {const { posts } = useLoaderData();return (<div>{posts.map(blogPost => (<article key={blogPost.id}><h6>{blogPost.title}</h6><StructuredText data={blogPost.content} /></article>))}</div>);}
Other than “regular” formatting nodes — paragraphs, headings, lists, etc. — Structured Text documents can contain three special types of node:
itemLink
nodes are just like regular HTML hyperlinks, but point to other records instead of URLs;
inlineItem
nodes let you directly embed a reference to a record in-between regular text;
block
nodes let you embed a DatoCMS block record in-between regular paragraphs;
If a Structured Text document contains one of these nodes, then we need to change the GraphQL query, so that we also fetch all the records and blocks it references.
As an example, if the field can link to other Blog posts
, and can embed blocks of type Image block
, then the query should change like this:
const HOMEPAGE_QUERY = `query HomePage($limit: IntType) {posts: allBlogPosts(first: $limit) {idtitlecontent {valueblocks {... on RecordInterface {id__typename}... on ImageBlockRecord {image { url alt }}}links {... on RecordInterface {id__typename}... on BlogPostRecord {slugtitle}}}}}`;
We also need to tell <StructuredText />
how you want such nodes to be rendered:
return (<StructuredTextdata={blogPost.content}renderInlineRecord={({ record }) => {switch (record.__typename) {case "BlogPostRecord":return <a href={`/blog/${record.slug}`}>{record.title}</a>;default:return null;}}}renderLinkToRecord={({ record, children }) => {switch (record.__typename) {case "BlogPostRecord":return <a href={`/blog/${record.slug}`}>{children}</a>;default:return null;}}}renderBlock={({ record }) => {switch (record.__typename) {case "ImageBlockRecord":return <img src={record.image.url} alt={record.image.alt} />;default:return null;}}}/>);