This content originally appeared on DEV Community and was authored by Dibas Dauliya
Strapi is a free and open-source headless CMS. It gives us an API and lets us choose whatever language we use.
In this tutorial we are going to use NextJS because of its SEO, image optimization, functions to fetch data for pre-rendering which comes out of the box with it. If you didn’t get these terms don’t worry, I’ll explain about it more below.
We’ll create a simple blog where you can post different articles with different categories and also let the user post the articles from the website. And at the end we’ll also deploy the strapi app in heroku and the main NextJS website in vercel for free.
This is the final blog that we’ll develop in this tutorial.
Link: https://my-nextjs-blog-phi.vercel.app/
Final code: https://github.com/DibasDauliya/nextJS-blog-with-stapi-api
I assume you don't know about Strapi and are just getting started to practice NextJS.
So let’s start with STRAPI:
Run this command in your terminal.
$ npx create-strapi-app strapi --quickstart
It is recommended to use npx
instead of npm
because npm
installs the packages and npx
only executes the packages. You can read more about it in this free code camp’s blog.
Also we added --quickstart
at the end of command because we want to use SQLite database; you can also use MongoDB
, SQL
or Postgres
as your main database. And strapi in the above command is the folder name where we want to keep our project.
After running above command it will give this url “http://localhost:1337/admin”
Open it and register your name, email and password which you can use to access the Strapi admin panel.
Let's make a user so we can connect it with our posts as an author.
Click on “Users” of collection types and click “+ Add new users”. Fill in the details and save it.
After that go to ‘Content-Types Builder’ then ‘+ Create new collection type’ as shown in picture.
Then enter the display name as “post” (strapi will convert that into Posts). After that click the add field option. Select three text fields, a rich text field, and UID then name them as title, category, description, content, and slug respectively. Rich text will allow us to write markdown and we’ll extract it using react-markdown
npm package from NextJS.
UID is useful for slug and URL. Make sure to choose ‘title’ from the dropdown of attached field in the UID. It will automatically remove the space of title and concatenate them with dash.
After that, select the relation field and choose “User (from-permission)” from the dropdown. Then among six options choose fourth one i.e. User has many Posts and name it as author.
Then click finish and save it (green button at R.H.S). It might say an error occurred or something like that, just make sure you clicked that save button and if such error occurs do refresh the page and you must see “Posts” in collection types below the strapi logo.
Yay, now let’s add some posts. Go to Posts and click “+Add New Posts”.
Fill the fields and choose the author from the dropdown at the right side as shown in figure.
Now click save and publish it.
We can access those posts from the “/posts” route but at this point it will show 403 forbidden error because we should give permission.
So, let’s go to settings and then “Roles” under “USERS & PERMISSIONS PLUGIN”. Click on “Public” then go to the application section under permissions. After that, choose “find”, “find one” and “create” and save it.
Utilizing data using NextJS
Run this command in your terminal.
$ npx create-next-app my-blog
After running this command go to the my-blog folder, open the project in your preferred code editor, run npm run dev
command and go to this link (http://localhost:3000/) to view the website.
I edited the default code into something like this (just changed some text and added basic stylings). ?
Now let’s use the getStaticProps
function to fetch data for pre-rendering. It will build all our posts during build time in the server and serve those posts to users fastly. Learn more at Basic Features: Data Fetching | Next.js (nextjs.org).
The following code may clarify you more. You should refresh your site or re-run the server after implementing getStraticProps
.
export default function Home(props) {
console.log(props.data)
console.log('everyone can get me')
return (
{/* ... */}
)
}
export async function getStaticProps() {
console.log("I'm executed by server. Browser can't get me. ?")
return {
props: { data: 'Data returned by getStaticProps' }
}
}
This function only runs in the files inside pages folder.
And in this case we are utilizing Incremental Static Regeneration (ISR) by adding revialte key with value in seconds at return of the geStaticProps
function because it helps to rebuild our required page at a certain interval of given time without having to rebuild the whole site.
Even if we don't use revialte property in geyStaticProps
while development and after doing refresh it will show the updated content but it is not the same during production. If you don't use ISR we should rebuild our site every time we make changes to see the updates.
So, let's make “.env.local” file in the root path and add strapi url.
NEXT_PUBLIC_STRAPI_API_URL=http://localhost:1337
Now, we will be using a “category text field” to filter the posts and fill data in different sections.
export default function Home(props) {
const { htmlCatRes, jsCatRes } = props
return (
<div className={styles.container}>
<Head>
<title>My Blog</title>
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Welcome to My Blog!</h1>
{/* ... */}
{/* category one */}
<div className={styles.catCard}>
<h2>HTML and CSS Category</h2>
{htmlCatRes.map(({ title, description, id, slug }) => (
<a href={`/${slug}`} className={styles.card} key={id}>
<h2>{title}</h2>
<p>{description}</p>
</a>
))}
</div>
{/* category two */}
<div className={styles.catCard>...
</main>
<footer className={styles.footer}>My Blog</footer>
</div>
)
}
export async function getStaticProps() {
const htmlCat = await fetch(
`${process.env.NEXT_PUBLIC_STRAPI_API_URL}/posts?category=html-css`
)
const htmlCatRes = await htmlCat.json()
const jsCat = await fetch(
`${process.env.NEXT_PUBLIC_STRAPI_API_URL}/posts?category=javascript`
)
const jsCatRes = await jsCat.json()
return {
props: { htmlCatRes, jsCatRes },
revalidate: 10 //will regenerate page when request comes at every 10 seconds
}
}
We can use the meta titles, descriptions, images, open graph tags for the specific page using the Head
component of nextJS to improve our SEO.
Now let’s make a dynamic page [post].js
under the same pages folder where we can get more info about our posts. The word post in [post].js
will be replaced by our post’s slug.
This time, to fetch data, we should also use the getStaticPaths
function because it is a dynamic route and nextJS doesn't know which word will replace that [post]
in [post].js
we made. We should tell nextJS that these are the words that will replace [post]
beforehand. So it can make all the possible pages for use during build time.
To parse the markdown data into html let’s install the react-markdown
npm package.
$ npm i react-markdown
Now, we will get the slug passed in the href
of the link and use it to filter the posts and get the data of that specific slug.
export default function Post({ finalData }) {
const { title, author, category, content, created_at } = finalData
return (
<>
<Head>
<title>{title}</title>
</Head>
<style>
{` ...
`}
</style>
<div className='container'>
<article className='flow article'>
<header className='header'>
<h1>{title}</h1>
<div className='author'>
<span>By {author?.username}</span>
<span>{new Date(created_at).toLocaleDateString()}</span>
<span>{category}</span>
</div>
</header>
<main className='flow-sm'>
<ReactMarkdown>{content}</ReactMarkdown>
</main>
</article>
</div>
</>
)
}
export async function getStaticProps({ params }) {
const { post } = params
const data = await fetch(
`${process.env.NEXT_PUBLIC_STRAPI_API_URL}/posts?slug=${post}`
)
const resData = await data.json()
const finalData = resData[0]
return {
props: { finalData },
revalidate: 10 //will regenerate page when request comes at every 10 seconds
}
}
export async function getStaticPaths() {
const res = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_API_URL}/posts`)
const posts = await res.json()
const paths = posts?.map(({ slug }) => ({
params: { post: slug }
}))
return {
paths,
fallback: 'blocking'
}
}
To create a post from website
Make a new page called create.js
in the pages folder and Add /create
in the value of href
attribute in the link of the home page.
Then create a simple form with controlled input fields. If the value is driven by the react state then we can call the input fields as controlled components.
After that, I fetched the strapi API and used it to post our content.
export default function Create() {
const [formData, setFormData] = useState({
title: '',
description: '',
content: '',
category: 'html-css',
slug: ''
})
const [isLoading, setLoading] = useState(false)
async function handleSubmit(e) {
e.preventDefault()
setLoading(true)
const add = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_API_URL}/posts`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
const addRes = await add.json()
console.log(addRes)
setFormData({
title: '',
description: '',
content: '',
category: 'html-css',
slug: ''
})
setLoading(false)
}
function handleChange(e) {
const name = e.target.name
setFormData((prev) => {
return { ...prev, [name]: e.target.value }
})
}
return (
<>
<Head>
<title>Create post</title>
</Head>
<style jsx>
{` ...
`}
</style>
<main className='container'>
<Link href='/'>
<a>
<h1>Home</h1>
</a>
</Link>
<h1>Create Post</h1>
<form>
<div className='form-group'>
<label htmlFor='title'>Title</label>
<input
type='text'
name='title'
id='title'
required
placeholder='Enter title'
value={formData.title}
onChange={(e) => handleChange(e)}
/>
</div>
<div className='form-group'>
<label htmlFor='category'>Choose category</label>
<select
name='category'
id='category'
onChange={(e) => handleChange(e)}
>
<option value='html-css'>HTML and CSS</option>
<option value='javascript'>Javascript </option>
</select>
</div>
<div className='form-group'>
<label htmlFor='slug'>Slug</label>
<input
type='text'
name='slug'
id='slug'
onChange={(e) => handleChange(e)}
value={formData.slug}
placeholder='Enter a slug'
/>
</div>
<div className='form-group'>
<label htmlFor='description'>Description</label>
<input
type='text'
name='description'
id='description'
required
placeholder='Enter description'
value={formData.description}
onChange={(e) => handleChange(e)}
/>
</div>
<div className='form-group'>
<label htmlFor='content'>Content (Markdown is supported)</label>
<textarea
rows={5}
type='text'
name='content'
id='content'
required
placeholder='Enter content'
value={formData.content}
onChange={(e) => handleChange(e)}
></textarea>
</div>
<div className='form-group'>
<button onClick={(e) => handleSubmit(e)}>
{isLoading ? 'Loading...' : 'Submit'}
</button>
</div>
</form>
</main>
</>
)
}
So, this is it. ?
Let’s Deploy
Deploying Strapi app in Heroku
If you haven’t made a heroku account then you can make it from this link. It is free for hobby projects and doesn't ask for credit card details.
After that go to https://dashboard.heroku.com/apps and click the “new” button in the R.H.S. and then click “Create new app”. Fill the name of the app and it will take you to the deploy section.
Heroku gives different options to deploy your apps. In this tutorial, let's use the Heroku CLI method. You’ll need to download Heroku CLI for this The Heroku CLI | Heroku Dev Center
You may need to restart your system after installation and can check it by running this command in terminal.
$ heroku --version
After installing it, open your terminal and locate into the strapi project that we built and login to the heroku.
$ heroku login
After that exit from it by pressing CTRL + C
and create a git repository.
$ git init
$ heroku git:remote -a your-app-name-in-heroku
And then commit and push the code into the heroku main branch.
$ git add .
$ git commit -am "first commit"
$ git push heroku main
It might take a few minutes and after its completion it will give the deployed url.
After that, go to the heroku dashboard and click at “Configure addons” and search “Heroku Postgres” and add it in your project.
Then open your URL and go to ‘/admin’ route to access the dashboard.
After that add some data and replace the localhost URl in your .env.local file of nextJS with the deployed URL.
Also make sure to check the “find”, “finOne”, “create” of the post for the public under roles section of user & permission plugin.
Deploying nextJS app in Vercel.
NextJS apps can be deployed using any hosting that supports nodeJS but in this tutorial we are going to deploy in Vercel because it is also free for hobby projects and it is personalized for nextJS as nextJS is made by Vercel. If you haven’t registered you can do so via this link.
After that, let's upload our project on github. Make a new project in github and open the terminal and locate the nextJS app then run these commands.
$ git add .
$ git commit -m “first commit”
$ git remote add origin your-url
$ git push origin main
After uploading in gitHub let’s go back to Vercel via this link.
Then click “New Project” button in the R.H.S, then choose your project from github by connecting vercel with github
Make sure to add environment variables with the name “NEXT_PUBLIC_STRAPI_API_URL” and the value is the URL given by heroku.
Then click on deploy and it will give you the live URL with celebration. ?
Thank you. ?
This content originally appeared on DEV Community and was authored by Dibas Dauliya
Dibas Dauliya | Sciencx (2021-06-24T18:42:45+00:00) NextJS blog with Strapi | Deploy to Heroku and Vercel. Retrieved from https://www.scien.cx/2021/06/24/nextjs-blog-with-strapi-deploy-to-heroku-and-vercel/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.