Contents
- Setup Strapi locally and deploy it to heroku
- Data fetching strategies with Next.js
- Create dynamic routes
- Basic GraphQl
- Static SSR
- Deploy frontend to vercel
What you need first
Github account
Create a github account and generate a new ssh key. Then add it to your github account and you are ready.Code editor
Heroku account
Create an account if you don't have one
postgresql
I I installed it for Arch but you can google how to install it for your env
Rendering strategy
Check this out.
On this article i will focus on Static SSR strategy for this blog. I like it because it will fit in 90% use cases and i think its really performant.
Install Strapi with CLI
1. Login to Heroku from your CLI
Next, you need to login to Heroku from your computer.
heroku login
Follow the instructions and return to your command line.
2. Create a new Strapi project
yarn create strapi-app strapi-blog
When prompted, answer, on this order:
- custom
- postgres
- my-blog-db
3. Init a Git repository and commit your project
Init the Git repository
cd my-project
git init
Add heroku remote
heroku git:remote -a blog-strapi-app
Your local development environment is now set-up and configured to work with Heroku. You have a new Strapi project and a new Heroku app ready to be configured to work with a database and with each other.
4. Heroku Database set-up
heroku addons:create heroku-postgresql:hobby-dev
5. Database credentials
The add-on automatically exposes the database credentials into a single environment variable accessible by your app. To retrieve it, type:
heroku config
This should print something like this: DATABASE_URL: postgres://ebitxebvixeeqd:dc59b16dedb3a1eef84d4999sb4baf@ec2-50-37-231-192.compute-2.amazonaws.com: 5432/d516fp1u21ph7b
.
(This url is read like so: postgres:// USERNAME : PASSWORD @ HOST : PORT : DATABASE_NAME)
6. Set environment variables
Strapi expects a variable for each database connection configuration (host, username, etc.). So, from the url above, you have to set several environment variables in the Heroku config:
heroku config:set DATABASE_USERNAME=ebitxebvixeeqd
heroku config:set DATABASE_PASSWORD=dc59b16dedb3a1eef84d4999a0be041bd419c474cd4a0973efc7c9339afb4baf
heroku config:set DATABASE_HOST=ec2-50-37-231-192.compute-2.amazonaws.com
heroku config:set DATABASE_PORT=5432
heroku config:set DATABASE_NAME=d516fp1u21ph7b
Replace these above values with your actual values.
7. Update your database config file.
Replace the contents of config/database.js
with the following:
module.exports = ({ env }) => ({
defaultConnection: 'default',
connections: {
default: {
connector: 'bookshelf',
settings: {
client: 'postgres',
host: env('DATABASE_HOST', '127.0.0.1'),
port: env.int('DATABASE_PORT', 27017),
database: env('DATABASE_NAME', 'my-blog-db'),
username: env('DATABASE_USERNAME', ''),
password: env('DATABASE_PASSWORD', ''),
},
options: {
ssl: false,
},
},
},
});
8. Deploy.
git add --all
git commit -m "update config"
git push heroku master
The deployment may take a few minutes. At the end, logs will display the url of your project (e.g. https://mighty-taiga-80884.herokuapp.com
). You can also open your project using the command line:
heroku open
If you see the Strapi Welcome page, you have correctly set-up, configured and deployed your Strapi project on Heroku. You will now need to set-up your
Frontend
1. Create our Next.js app
We move to the parent folder as we dont want to mix strapi and frontend files and we create our app
cd ..
yarn create next-app
2. Routing
- We create a file
pages/article/[id].js
which will be our article item page:
import Head from "next/head";
export default function ArticlePage() {
return (
<div>
<Head>
<title>This is an article title</title>
</Head>
<main>
This is an article body
</main>
</div>
);
}
- We empty the contents of
pages/index.js
as its our home page and it will contiain ourArticleList
in the future.
import styles from "../styles/Home.module.scss";
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Our great blog</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
The article list
</main>
</div>
);
}
- We create a 404 custom page by adding a
pages/404.js
with a PageNotFound component.
export default function PageNotFound() {
return <div>oh oh...</div>
}
3. Create some structure
- Create a
modules
folder at the root of our Next.js project - Create a
modules/core
module with two subfolderscomponents
andservices
. - Inside
modules/core/components
create aHeader.js
and aFooter.js
files like:
import Link from "next/link";
export default function Header() {
return (
<header>
<Link href="/">
<a>Our awesome blog</a>
</Link>
</header>
);
}
export default function Footer() {
return <footer>® Awesome friends company</footer>;
}
- Consume your components in
app.js
import "../styles/globals.scss";
import Header from "../modules/core/components/Header";
import Footer from "../modules/core/components/Footer";
function MyApp({ Component, pageProps }) {
return (
<div>
<Header></Header>
<div className="route">
<Component {...pageProps} />
</div>
<Footer></Footer>
</div>
);
}
export default MyApp;
Remove the folder
routes/api
as we are not using microservices for our example, it will be fully static.Install SASS (optional)
We are using sass because we want to be able of reusing variables for colors.
yarn add sass
Change all .css files to .scss (omit if you did not add sass)
Create a reset.scss file next to global.scss with your favourite css reset. (use .css if you didn't add sass)
Empty the contents of global.scss and import reset.scss like:
@import 'reset'
- Create a file
variables.scss
next toglobal.scss
and define your colors, breakpoints or whatever variables you need to share here. For our case, i picked a random palette from here
variables.scss
$charcoal: #264653;
$persianGreen: #2A9D8F;
$orangeYellowCrayola: #E9C46A;
$sandyBrown: #F4A261;
$burntSienna: #E76F51;
- Empty the contents of
Home.module.scss
to leave it like
.container {
display: block;
}
.main {
display: block;
}
You can style it from here in the future.
Data fetching
Launch your strapi in local. For this, go to the folder where you installed strapi and type:
yarn develop
- Log into your admin account in
http://localhost:1337/admin
create an article
content type and add the following fields:
- canonical (will be our permalink or pathname)
- description (long text)
- title (short text)
- body (rich text)
save everything and open the strapi folder in your text editor.
- Create a schema.graphql.js inside
api/article/config
with the following:
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
query: `
articleByCanonical(canonical: ID): Article
`,
resolver: {
Query: {
articleByCanonical: {
resolverOf: 'Article.findOne',
async resolver(_, { canonical }) {
const entity = await strapi.services.article.findOne({ canonical });
return sanitizeEntity(entity, { model: strapi.models.article });
},
},
},
},
};
And save everything.
- Now, on your frontend project add the apollo client dependency (remember to
cd frontend dir
to install the dependency in the frontend package.json)
yarn add @apollo/client
- Create a .env.local file with:
STRAPI_HOST="http://localhost:1337"
- Create a service inside
core/services/apolloClient.service.js
where we will export our apollo client instance.
import { ApolloClient, InMemoryCache } from '@apollo/client'
export default new ApolloClient({
uri: `${process.env.STRAPI_HOST}/graphql`,
cache: new InMemoryCache()
})
- Create a new module
article
withcomponents
andservices
folders as we did with core.
Create ArticleList
and Article
components inside article/components/ArticleList.js
and article/components/Article.js
with the following:
export default function Article({ title, body }) {
return <div>
<h1>{title}</h1>
<div>{body}</div>
</div>;
}
And
import Link from "next/link";
export default function ArticleList({ articles }) {
return articles.length ? (
<ol>
{articles.map((article) => (
<li key={article.id}>
<Link href={`article/${article.canonical}`}>
<h2>{article.title}</h2>
</Link>
<p>{article.description}...</p>
</li>
))}
</ol>
) : (
<div>There are no articles</div>
);
}
- Create the articles service in
article/services/article.service.js
import { gql } from "@apollo/client";
import apolloClient from "../../core/services/apolloClient.service";
export async function getAll() {
return apolloClient
.query({
query: gql`
{
articles {
canonical
description
id
created_at
title
}
}
`,
})
.then((result) => result.data.articles);
}
export async function getByCanonical(canonical) {
return apolloClient
.query({
query: gql`
{
articleByCanonical(canonical: "${canonical}") {
title
body
}
}
`,
})
.then((result) => {
console.log(result.data)
return result.data.articleByCanonical;
});
}
- Finally, we add the fetching logic inside our routes and we consume these components:
pages/index.js
import Head from "next/head";
import ArticleList from "../modules/article/components/ArticleList";
import { getAll } from "../modules/article/services/article.service";
export default function HomePage({ articles }) {
return (
<div className={styles.container}>
<Head>
<title>Our great blog</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<ArticleList articles={articles}></ArticleList>
</main>
</div>
);
}
export async function getStaticProps(context) {
return {
props: { articles: await getAll() },
};
}
And change pages/article/[id].js
import marked from "marked";
import Article from "../../modules/article/components/Article";
import {
getAll,
getByCanonical,
} from "../../modules/article/services/article.service";
import Head from "next/head";
export default function ArticlePage({ article }) {
return (
<div>
<Head>
<title>{article.title}</title>
</Head>
<main>
<Article {...article}></Article>
</main>
</div>
);
}
export async function getStaticPaths() {
const articles = await getAll();
const paths = articles.map((article) => ({
params: { id: article.canonical },
}));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
const article = await getByCanonical(params.id);
return { props: { article } };
}
Add styles
- You can create any file .module.scss next to your component and your css classes will be mapped to an object you can import like
// foo.module.scss
.container {
display: block;
}
// foo.js
import styles from './foo.module.scss'
function Foo() {
return <div className={styles.container}>My Foo styled component</div>
}
Deploy
- For strapi deployment just go to your strapi project in a terminal and commit and push everything:
git add --all
git commit -m "my changes"
git push heroku master
- For the frontend: frontend path and push your changes.
- In
package.json
change the build command fornext build && next export
. This will generate a static build into/out
folder. That's all we need to serve our site. To test it locally just runnpx serve out/
- Then create a github repo and add your remote
- Then go to vercel.com and import your github project
- Remember to add your STRAPI_HOST env variable in vercel to point to your heroku database
- Follow the wizard and your app will be deployed
Updates
- Create a webhook in vercel that will provide you an url
- Log into your production strapi environment and in settings > webhooks create a new webhook with the provided url.
- Now every time you update your production database your blog will be rebuilt and redeployed.
Top comments (0)