Creating a very simple blog with Next.js and Strapi
Published: 2020-09-07 01:04:46

## 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

1. Github account
[Create a github account](https://github.com/join) and [generate a new ssh key](https://docs.github.com/es/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). Then [add it to your github account](https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account) and you are ready.

2. Code editor
3. Heroku account

[Create an account](https://signup.heroku.com/) if you don't have one

4. [Install heroku CLI](https://strapi.io/documentation/v3.x/deployment/heroku.html)

5. [nodejs](https://nodejs.org/es/)

6. postgresql 

I I [installed](https://wiki.archlinux.org/index.php/PostgreSQL) it for Arch but you can google how to install it for your env

## Rendering strategy
Check [this](https://developers.google.com/web/updates/2019/02/rendering-on-the-web) 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](https://strapi.io/documentation/v3.x/deployment/heroku.html#_1-heroku-cli-installation)

### 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](https://strapi.io/documentation/v3.x/getting-started/quick-start.html)

```
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](https://strapi.io/documentation/v3.x/deployment/heroku.html#_5-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](https://strapi.io/documentation/v3.x/deployment/heroku.html#_7-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](https://strapi.io/documentation/v3.x/deployment/heroku.html#_3-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](https://strapi.io/documentation/v3.x/deployment/heroku.html#_4-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](https://strapi.io/documentation/v3.x/deployment/heroku.html#_9-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 our `ArticleList` 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 subfolders `components` and `services`.
- Inside `modules/core/components` create a `Header.js` and a `Footer.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](https://gist.github.com/DavidWells/18e73022e723037a50d6). (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 to `global.scss` and define your colors, breakpoints or whatever variables you need to share here. For our case, i picked a random palette from [here](https://coolors.co/)

`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` with `components` and `services` 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 <Component>.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.
1. In `package.json` change the build command for `next 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 run `npx serve out/`
2. Then [create a github repo](https://docs.github.com/en/github/getting-started-with-github/create-a-repo) and [add your remote](https://docs.github.com/en/github/using-git/adding-a-remote)
3. Then go to vercel.com and import your github project
4. Remember to add your STRAPI_HOST env variable in vercel to point to your heroku database
5. 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.

[Demo](https://nextjs-csr-blog.vercel.app/) and [Code](https://github.com/dieguezz/nextjs-static-blog)