Quick JAMStack Integration: Prismic CMS and NextJS
Prismic tools abstract the CMS queries making development a bit faster
Jamstack is an architecture designed to make the web faster, more secure, and easier to scale. It builds on many of the tools and workflows which developers love, and which bring maximum productivity. The core principles of pre-rendering, and decoupling, enable sites and applications to be delivered with greater confidence and resilience than ever before. Explore more of the benefits of Jamstack. From: jamstack.org/what-is-jamstack
Just tried Prismic.io, which is a headless CMS similar to other competitors, you name them, DatoCMS , GraphCMS , or Strapi.io. But the first impression when I looked at the documentation , was like wow..., these guys have many tools and clients to speed up the development time ๐ ... And it was there, the javascript library and NextJS hooks. Of course a CLI tool, we will see all of them. Maybe just one downside the hooks library is still not supporting typescript OOTB but we can fix it.
Contents
- Requirements and what we are building
- Prismic repository
- Setup NextJS and Prismic client
- Cleanup
- Create API endpoint on NextJS and consume from home
- Query what you need
- Setup static generated pages
- Conclusions
Requirements and what we are building
- Nodejs 12+
- Create a Prismic.io account (free plan is more than enough for small projects)
- Prismic CLI
What we are building
Kind of movie landing and movie detail pages, where landing page data will be filled from the FE and only the shell will be static, but the detail pages will be full statically generated:
Prismic repository
Like mentioned in the requirements section, we need a Prismic.io account.
Create a new repository, I called it ticoflix
, I'm bad at giving names.
Once you get created, go inside that repository and click custom types
on the left side panel and begin creating a new content type. It should be a repeatable type, will be many of them and I used movie
as the type name.
Fields
On the main tab we place:
- UID type. Name: uid.
- Image type. Name: thumbnail. Size: 250x250
- Title type. Name: title. Allow only h1
- Rich Text type. Name: synopsis. Allow paragram, anchors, lists
- Number type. Name: year
We create another tab for SEO, on this one we place couple of fields:
- Key Text type. Name: meta_title.
- Key Text type. Name: meta_description.
Now, let's save the type, go to documents
on your left side panel, and create a couple of documents using our brand new content type.
Setup NextJS and Prismic client
Now back to our local computer, I'll create first the NextJS application supporting typescript
npx create-next-app --typescript
Name the app, e.g ticoflix
, once created, get inside this new folder containing the generated code.
It's the moment to set up Prismic.io, as we mentioned in requirements, we need the Prismic CLI installed, so after that we can setup the project running:
prismic sm --setup --no-prismic
This will add the prismic dependencies to our app, but won't create any repo (--no-prismic), because we already created it before, also. will add the prismic configuration files to our app in order to connect to Prismic.io .
Clean prismic.js
We won't need code generated on this file, mostly for links generation, so after cleanup should look like:
import Prismic from "prismic-javascript";
import smConfig from "./sm.json";
export const apiEndpoint = smConfig.apiEndpoint;
// -- Access Token if the repository is not public
// Generate a token in your dashboard and configure it here if your repository is private
export const accessToken = "";
export const Client = (req = null, options = {}) =>
Prismic.client(apiEndpoint, { req, ...options });
As you see we have the option to use accessToken
in order to make the repo not accessible by anybody. For us, not going to use it. But is important for real production scenario.
Configure the prismic endpoint
Go to prismic.io on the dashboard and copy your repo endpoint:
When you grab it, return back to your local and modify the sm.json
file:
{
"apiEndpoint": "ENDPOINT_HERE",
"libraries": ["@/slices", "essential-slices"],
"_latest": "0.0.46"
}
Configure images
As we are going to consume images from prismic.io, we have to let NextJS allow to use the Image component properly, we set next.config.js
accordingly, also from NextJS 11, using Image
is enforced with linting rules :
module.exports = withTM({
images: {
domains: ["images.prismic.io"],
},
});
Clean up
Create slices
folder in the root of the app and add .keepdir
inside (if you omit, only you get a warning on terminal when running), by default prismic hook library looks this folder to find slices components. Note: However we are not going to cover on this post the slices. But you can use them anytime via SliceZone component we kept sm-resolver.js
file just in case.
Finally on this step we get rid of files *.js
files from pages
folder since we are using typescript.
Time to run to discard any issue, on terminal: npm run dev
and open your browser at http://localhost:3000
Create API endpoint on NextJS and consume from home
API endpoint
Will create an API endpoint on the app, the purpose is to encapsulate the query to Prismic.io for retrieving movies. This endpoint will return an array in JSON format.
Under pages/api
I created a pelis.ts
, and using the Prismic client we query the movies and return the result, as you see our code uses async/await
to keep clean:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";
import Prismic from "prismic-javascript";
import { Client } from "../../prismic";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<any>
) {
const client = Client();
const queryResults = await client.query([
Prismic.Predicates.at("document.type", "movie"),
]);
res.status(200).json(queryResults.results);
}
Client
is the one that Prismic CLI helped us to generate. By using Prismic.Predicates.at
, we query only type movies
documents.
Ok now try this endpoint on your browser and confirm you are getting a JSON response with all the movies previously created on Prismic.io repository: http://localhost:3000/api/pelis
.
Fetch API endpoint from Home
The Home as you see on What we are building section, will have code running on client-side to fetch async any time this page is loaded, so we get fresh list of movies.
Update the index.ts
file, adding useEffect
and the fetch
for making the http request to the API endpoint:
const [pelis, setPelis] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
const loadPelis = async () => {
setLoading(true);
const response = await fetch('/api/pelis');
const pelis = await response.json();
setPelis(pelis);
setLoading(false)
}
loadPelis();
}, [])
I'm telling, every time home component page is mounted, we request our API, and place in state the result, also loading status is switched. Now we will use this status to render the list.
return (
<div className={styles.container}>
<Head>
<title>Ticoflix</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Ticoflix</h1>
{loading ? <h3>Cargando peliculas...</h3> : null}
<div className={styles.grid}>
{pelis.map((p: Document) => (
<Link key={p.uid} href={`/p/${p.uid}`}>
<a className={styles.card}>
<RichText render={p.data.title} />
<Image
src={p.data.thumbnail.url}
alt="Movie thumbnail"
width={250}
height={250}
/>
</a>
</Link>
))}
</div>
</main>
<footer className={styles.footer}>
</footer>
</div>
);
We do here a simple loop through the pelis
array and render Link
component, this component is optimized for incremental builds, so it's good to use it instead of plain anchors. Also added a loading indicator, as this will be fetched in the browser, there is RichText
component is required to render the movie title, because Prismic creates special structure for Rich Text values, is good to rely on that component to handle rendering.
Let's check again from the browser http://localhost:3000/
and let's check the list working properly.
Query what you need
Just found something interesting, you are able to query only what you need, for example for listing, this is really helpful, in this case for our pages/api/pelis.ts
endpoint we just need title
and thumbnail
, then query
function provides a second param where we can pass different query options such as fetch
:
const queryResults = await client.query([
Prismic.Predicates.at("document.type", "movie"),
], {
fetch: ['movie.title', 'movie.thumbnail'],
});
Setup static generated pages
Now it's time to work the movies pages on pages/p/[uid].tsx
file, these pages will be generated statically, first, we are going to rely on the prismic hooks :
export const getStaticPaths = useGetStaticPaths({
client: Client(),
type: 'movie',
formatPath: ({ uid }: Document) => ({ params: { uid } }),
fallback: true
})
export const getStaticProps = useGetStaticProps({
client: Client(),
type: 'movie',
uid: ({ params }: any) => params.uid,
getStaticPropsParams: {
revalidate: 5
}
});
getStaticPaths
and getStaticProps
are part of NextJS, then what are doing these prismic hooks is to encapsulate the queries.
For getStaticPaths
, we configure the type
, pass the Client
, formatPath
builds the params that getStaticProps will receieve, fallback
enables incremental builds .
For getStaticProps
, we configure the type
, pass the Client
, uid
helps to make the query to retrieve the movie data. And revalidate
, this keeps pulling fresh data every 5 seconds after any page request and regenerates the page.
On the component side, I will use the data retrieved from static props to render the page:
export default function Pelicula(props: Document) {
const { data } = props;
const router = useRouter();
if (router.isFallback) {
return <h3>Cargando...</h3>
}
return (
<>
<Head>
<title>{data.meta_title}</title>
</Head>
<div>
<RichText render={data.title} />
<RichText render={data.synopsis} />
<strong>{data.year}</strong>
</div>
</>
)
}
router.isFallback
is a boolean that let us know when the page is being generated incrementally, we display a loading indicator. Otherwise we statically generated the page already.
Conclusions
Please comment if there is any question or if was helpful. Also found CLI and node dependencies provided by Prismic.io are great, but I would like to see they support typescript and also keep updated the packages and documentation.
IMPORTANT: Also is known that hooks packages will be deprecated soon prismic.io/docs/technologies/technical-refe.., so will try to keep this post updated once they come with a different module.
Full source code is here .