How to build a Marketing Site with React and Strapi
Tags: react, jamstack, javascript, opensource
Published: 2022-03-08 16:13:58

---

canonical_url: https://strapi.io/blog/how-to-build-a-marketing-site-with-react-and-strapi?utm_source=dev.to&utm_medium=blog&utm_id=blog

---

# Goal

In this article, we will build a startup site with forms functionality. The startup will be an estate site that helps us find new homes. We will use Strapi for the backend and React for the frontend. 

Also, we will have a newsletter form for users who want to receive monthly emails about our products. On signup, the user will also receive a confirmation email that the signup was successful.

# An Introduction to Strapi

Strapi is an open-source headless CMS solution that is self-hosted to allow customization and flexibility for your backend API service. One advantage of Strapi is the ease of scaffolding an API to expose endpoints that can be used as micro-services in your frontend. Strapi supports both a RESTful and GraphQL API.

# Prerequisites

Before we continue this article, it is pertinent you know;

- React: This article will not be an introduction to React but about using Strapi for your backend services. [Head over to React’s official documentation to get started with React](http://https://reactjs.org/)[.](http://https://reactjs.org/)
    

Strapi’s CLI installation guide requires two things to be installed in your computer:


- [Nodejs](https://nodejs.org/): versions between v14 and higher are supported. To use v14, run the commands below in your terminal.
    nvm ls // to see all versions available
    nvm use 14 // to use version 14
    nvm alias default v14 // to make v14 your default version


- [Npm](https://docs.npmjs.com/cli/v6/commands/npm-install): Only v6 of npm is suported by Strapi. To downgrade to v6, run `npm install npm@6`.
![Checking Node and Npm versions](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639117124113_1.png)

- Postman: We will be using postman to test our Strapi endpoints before connecting it to our React Frontend.
# Backend Setup

We will be using Strapi for our backend setup; hence, we will install and scaffold a Strapi project.

**Note:** This article is written with the latest version of Strapi(v4.1.2) and tested with Strapi 4.1.2(Node 16 and yarn).

## Strapi Setup

 To install and scaffold a Strapi project, create a file called Marketing-Site, cd into it, and run either of the following commands below:

```shell
    cd Marketing-Site
    npx create-strapi-app@latest backend --quickstart
```
    
or
   
```shelll
    cd Marketing-Site
    yarn create strapi-app backend --quickstart
```

From above, we are creating a project called `backend` inside our Marketing-Site folder and using the --quickstart flag to choose the quickstart installation type.

After installation, run `yarn develop` to start the new project. The command will open up a tab in your browser with a page to register your new admin. Fill out the form and submit it to log in to your Strapi dashboard.

![Strapi’s dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639117634234_3.png)


After registering, we will be directed to the admin homepage to set up our backend content and APIs.


## Create a Collection

Let us create an *Estate* collection type. On your admin homepage, click on *Content-Type Builder* and then **Create a new collection type**.*

A modal will prompt you to create your content type and enter “Estate” as your Content-Type name because that is what we are building. Click on *Continue* and add the different fields you want on your website.

![Strapi Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639685364685_12.png)


 For this tutorial, we only need the Name, Description, Image and Price of our houses.

![Strapi Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639685382152_13.png)


After you have selected the fields, click on Finish to leave the modal. There are *Save* and *Publish* buttons at the top-right side of our page. Click on *Save* to save the collection and *Publish* to publish our collection. You should have something like the image below;

![Strapi Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1646347163605_1.png)



## Populate the Collection

Click on *Content Manager* at the top-left side of the admin page. This will navigate you to the page where we will populate our database. 

![Strapi Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639685639309_14.png)


Click on *Add new entry* to add different houses. We will be needing the name, description, pictures and price of the house so go ahead and populate the collection with your data. After you have done that, click on the publish button on the top-right side of your page to publish the collection.

Now, let’s make the estate route available to consume it in our React frontend. Click on Settings to go to the settings page and click on Roles under *Users and Permissions* Plugins.

![Strapi Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639685824598_15.png)


Next, click on *Public* and then check the `find` and `findOne` options under the *Estate* dropdown. This will make these two estate routes publicly available.

![Strapi Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639685944646_16.png)

## Test the Routes with Postman

You can test the routes with Postman to see how and what our Strapi API returns. Below is what the `http://localhost:1337/api/estates` will return:

![Postman Workspace](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639686085835_17.png)

# Frontend Setup

Let’s create our frontend with React, install [axios](https://axios-http.com/docs/intro) and [react router packages](https://v5.reactrouter.com/web/guides/quick-start), and connect them to Strapi. To spin up our React app, we will first exit or cd out of the backend folder before creating our React app, or we can open up a new terminal and run the commands below inside the Marketing-Site folder:

```shell
    npx create-react-app frontend
    cd frontend
    npm i axios react-router-dom --save
    npm start
```

Below is what our folder structure will look like. I will also explicitly state and explain the pages so you won’t get lost.

![Frontend folder structure](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639876078528_structure.png)


I’ve written the **code** below according to the folder structure. You can copy and paste accordingly. You can also clone the Github repo [here](https://github.com/Vic-Orlands/Strapi-React) to access the CSS styling.

    // frontend/src/hooks/useFetch.js
    
```js
    import { useState, useEffect } from 'react';
    import axios from 'axios';
    export default function useFetch(url) {
        const [ estate, setEstate ] = useState(null);
        const [ error, setError ] = useState(null);
        const [ loading, setLoading ] = useState(true);
    
        useEffect(
            () => {
                const fetchData = async () => {
                    setLoading(true);
                    try {
                        const res = await axios.get(url);
                        setEstate(res.data.data);
                        setLoading(false);
                    } catch (error) {
                        setError(error);
                        setLoading(false);
                    }
                };
                fetchData();
            },
            [ url ]
        );
    
        return { estate, error, loading };
    }
```
    


    // frontend/src/pages/about/About.js
    
```js
    import React from 'react';
    import { useParams, Link } from 'react-router-dom';
    import classes from './about.module.css';
    import useFetch from '../../hooks/useFetch';
    
    function AboutPage() {
        const { id } = useParams();
        const { loading, error, estate } = useFetch(`http://localhost:1337/api/estates/${id}?populate=*`);
        if (loading) return <p> Loading... </p>;
        if (error) return <p> Error :( </p>;
    
        return (
            <article className={classes.aboutPage}>
                <h2>More Description</h2>
                <hr />
                <section className={classes.aboutBoard}>
                    <h2>{estate.attributes.name}</h2>
                    <div className={classes.aboutDescription}>
                        <div className={classes.aboutImgContainer}>
                            {estate.attributes.image.data ? (
                                estate.attributes.image.data.map((pic) => (
                                    <img
                                        src={`http://localhost:1337${pic.attributes.url}`}
                                        alt="img"
                                        key={pic.attributes.id}
                                    />
                                ))
                            ) : (
                                <img
                                    src={`http://localhost:1337${estate.attributes.image.data.attributes.url}`}
                                    alt="img"
                                />
                            )}
                        </div>
                        <div>
                            <h3>{estate.attributes.price}</h3>
                            <p>{estate.attributes.description}</p>
                            <Link
                                to={'/'}
                                style={{
                                    textDecoration: 'none',
                                    background: 'black',
                                    color: 'white',
                                    border: '1px solid black',
                                    padding: '5px 10px'
                                }}
                            >
                                {'< Back to Home'}
                            </Link>
                        </div>
                    </div>
                </section>
            </article>
        );
    }
    export default AboutPage;
```


    // frontend/src/pages/estates/Estates.js

```js    
    import React from 'react';
    import { Link } from 'react-router-dom';
    import useFetch from '../../hooks/useFetch';
    import classes from './estates.module.css';
    
    export default function Estatepage() {
        const { estate, error, loading } = useFetch('http://localhost:1337/api/estates?populate=*');
        if (loading) return <p> Loading... </p>;
        if (error) return <p> Error :( </p>;
    
        return (
            <div className={classes['estates']}>
                <section>
                    <h2>Available Houses</h2>
                    <hr className={classes['horizontal-rule']} />
                    {estate.map((house) => (
                        <article className={classes['article']} key={house.id}>
                            <h2>{house.attributes.name}</h2>
                            <section className={classes['article-description']}>
                                <img
                                    src={`http://localhost:1337${house.attributes.image.data[0].attributes.url}`}
                                    alt="img"
                                />
                                <div>
                                    <p>{house.attributes.price}</p>
                                    <p>{house.attributes.description}</p>
                                    <Link to={`${house.id}`}>See More...</Link>
                                </div>
                            </section>
                        </article>
                    ))}
                </section>
            </div>
        );
    }
```


    // frontend/src/pages/home/Home.js
    
```js
    import React from 'react';
    import { Link } from 'react-router-dom';
    import useFetch from '../../hooks/useFetch';
    import classes from './home.module.css';
    
    export default function Homepage() {
      const { estate, error, loading } = useFetch('http://localhost:1337/api/estates?populate=*');
      if (loading) return <p> Loading... </p>;
      if (error) return <p> Error :( </p>;
    
      return (
        <div className={classes['home']}>
          <section>
            <h2>Welcome to our Estate</h2>
            <hr className={classes['horizontal-rule']} />
            <p>We help you find your new home</p>
    
          <form className={classes["home-form"]}>
            <h5>Interested in joining our Newsletter</h5>
            <h6>Sign up with your email below</h6>
    
            <label htmlFor="email">
              Email Address:
              <input type="email" />
            </label>
            <button>Signup</button>
          </form>
            {estate.splice(0, 2).map((house) => (
              <article className={classes['home-article']} key={house.id}>
                <h2>{house.attributes.name}</h2>
                <section className={classes['home-article-description']}>
                  <img
                    src={`http://localhost:1337${house.attributes.image.data[0].attributes.url}`}
                    alt="img"
                  />
                  <div>
                    <p>{house.attributes.price}</p>
                    <p>{house.attributes.description}</p>
                    <Link to={`estates/${house.id}`}>See More...</Link>
                  </div>
                </section>
              </article>
            ))}
          </section>
        </div>
      );
    }
```


    // frontend/src/pages/nav/Nav.js

```js    
    import React from 'react';
    import { Link } from 'react-router-dom';
    import classes from './nav.module.css';
    
    export default function NavHeader() {
      return (
        <div className={classes.navBar}>
          <h1>My Estate</h1>
          <nav className={classes.navLink}>
            <ul>
              <Link to="/" style={{ textDecoration: 'none' }}>
                <li>Home</li>
              </Link>
              <Link to="estates" style={{ textDecoration: 'none' }}>
                <li>Estates</li>
              </Link>
            </ul>
          </nav>
        </div>
      );
    }
```

    // frontend/src/App.js
    
```js
    import React, { Suspense } from 'react';
    import { Routes, Route } from 'react-router-dom';
    import Nav from './pages/nav/Nav';
    import Home from './pages/home/Home';
    
    const About = React.lazy(() => import('./pages/about/About'));
    const Estates = React.lazy(() => import('./pages/estates/Estates'));
    
    export default function App() {
        return (
            <div>
                <Nav />
                <Routes>
                    <Route path="/" element={<Home />} />
                    <Route
                        path="estates"
                        element={
                            <Suspense fallback={<p>Loading...</p>}>
                                <Estates />
                            </Suspense>
                        }
                    />
                    <Route
                        path="estates/:id"
                        element={
                            <Suspense fallback={<p>Loading...</p>}>
                                <About />
                            </Suspense>
                        }
                    />
                </Routes>
            </div>
        );
    }
```
    

Inside App.js file above, we implemented a React 18 feature called Suspense API.  

According to [](https://reactjs.org/)[React’s official page](https://reactjs.org/), “Suspense is a new feature that lets you also use `<Suspense>` to declaratively “wait” for anything else, including data.  It’s a mechanism for data-fetching libraries to communicate to React that the **data a component is reading is not ready yet. React can then wait for it to be ready and update the UI”.


    // frontend/src/index.js
    
```js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { BrowserRouter } from 'react-router-dom';
    import App from './App';
    
    ReactDOM.render(
        <React.StrictMode>
            <BrowserRouter>
                <App />
            </BrowserRouter>
        </React.StrictMode>,
        document.getElementById('root')
    );
```


Here’s what the files above do:

1. We separated our fetch hook by writing it in the `useFetch.js` file. By doing this, we wouldn’t have to rewrite the same logic every time. Instead, we will invoke it in the components we need it. 
2. We imported it into our *Home* and *Estates* components. After our Axios fetch was successful, we mapped through the returned array to display the data. On clicking on each data, we will be redirected to the *About* page. This page shows the full description of the house with the different available pictures, prices, etc.
3. `Nav.js` contains the static nav header on our website that houses the website’s name and the Estate and Home anchor links.
4. Also, `Home.js` has a form element. We will be using this form for our newsletter signup, which is one of the main focus of this article.
5. To run the React app and see the result, open up your terminal and run any of the commands below;
```shell
    // Yarn 
    yarn start
```
    
```shell
    //Npm
    npm start
```

![Estate Homepage](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639986394593_hint.png)

![Estate Description page](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639877823425_describe.png)


Now, we can easily change and add content with Strapi by simply editing any of the current Collection Types or creating new entries.

We have completed the frontend setup and integration. Next, we’ll work on our newsletter integration.


## SendGrid Setup

There are so many email providers out there like [Mailchimp](https://mailchimp.com/), [MailerLite](https://www.mailerlite.com/), [Sendinblue](https://www.sendinblue.com/), and so on. But for this article, we will be using an email provider called [SendGrid](https://signup.sendgrid.com/).

To set up a SendGrid service, we will first create a SendGrid account. This is because we need to connect SendGrid to Strapi through the SendGrid API. [Head over to SendGrid to sign up and create your account.](https://signup.sendgrid.com/)

![SendGrid Welcome page](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639305380896_1.png)


After logging into your dashboard, click on the *S**ettings* dropdown on the left side of the dashboard and click on *Sender Authentication*. Proceed to create a new sender and make sure to verify the email address too.

![SendGrid Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639402881998_1.png)


Next, we will create our API key. On the left side of the dashboard, click on the Settings dropdown again and click on API keys. Click on *create API key*, give it a name and copy your API key.

**Note: Make sure to copy your API key and store it somewhere safe because SendGrid won’t show it to you again. You can always create a new key though if you loose or forget where you stored it.**

![SendGrid Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639403096415_2.png)


Next, we will go into our backend folder and run any of the commands below to download strapi email provider plugin.

```shell
    // using yarn
    yarn add @strapi/provider-email-sendgrid --save
    
    // using npm
    npm install @strapi/provider-email-sendgrid --save
```

After we have successfully downloaded the plugin, we’ll set it up in our backend folder. In the `config` folder, create a new file called `plugins.js` and paste the code below:


    // config/plugins.js

```js    
    module.exports = ({ env }) => ({
        email: {
            provider: 'sendgrid',
            providerOptions: {
                apiKey: env('SENDGRID_API_KEY')
            },
            settings: {
                defaultFrom: 'myemail@protonmail.com',
                defaultReplyTo: 'myemail@protonmail.com'
            }
        }
    });
```
    

Replace the settings default emails with your SendGrid verified email. Also, in your `.env` file, add your SendGrid API key.


    SENDGRID_API_KEY=SG.5hoLikrVQXudcUtgaV6n6g.aKttCp***********************************

After that, head over to the `api` folder inside `src` folder and create a new folder called `subscribe`. Inside our `subscribe` folder, we will also create two extra folders: `config` and `controllers`. In our config folder, create a new `routes.json` file and add the code below.


    // src/api/subscribe/config/routes.json
    
```json
    {
        "routes": [
            {
                "method": "POST",
                "path": "/email",
                "handler": "email.send",
                "config": {
                    "policies": []
                }
            }
        ]
    }
```
    

Then, in our controllers folder create an email.js file, and add the following code


    // src/api/subscribe/controllers/email.js
    
```js
    module.exports = {
        send: async (ctx) => {
            let options = ctx.request.body;
    
            await strapi.plugins.email.services.email.send({
                to: options.to,
                from: 'yourmail@gmail.com',
                replyTo: 'yourmail@gmail.com',
                subject: options.subject,
                text: options.html
            });
    
            ctx.send('Email sent!');
        }
    };
```
    

We will now test our configuration in Postman and see what we get. Before that, make sure you make the email route publicly available in your Strapi admin settings.

**(Settings > Users and Permissions Plugin > Roles > Public > Email)**

![Strapi Dashboard](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639979111134_14.png)


Then, in our postman, let’s test our API to see if it works.

![Postman Workspace](https://paper-attachments.dropbox.com/s_0BDFDF76523BF5CBC2EF1D4CECF13DED15326AE849E7017A5787789D78E398BD_1639979183633_13.png)


We can see we got a status of 200 meaning the request was sent successfully. Log in to your email account to see the test message.

Finally, We will now integrate our Strapi subscribe functionality into our React app.

Head over to your `frontend` folder. Under the hooks folder where we created our `useFetch.js` file, create a new file called `usePost.js`. We will be putting our POST logic here; then, we will import it into our *Home* file.


    // frontend/src/hooks/usePost.js
    
```js
    import { useState } from 'react';
    import axios from 'axios';
    const usePost = (url) => {
        const [ signup, setSignup ] = useState('');
        const [ signupError, setError ] = useState(null);
        const [ signupMsg, setSignupMsg ] = useState('');
        const [ signupLoading, setSignupLoading ] = useState(true);
    
        const handleChange = (e) => {
            setSignup(e.target.value);
        };
    
        const handleSignup = (e) => {
            e.preventDefault();
            let userData = {
                to: signup,
                from: 'chimezieinnocent39@gmail.com',
                replyTo: 'chimezieinnocent39@gmail.com',
                subject: 'Thanks for signing up',
                html:
                    "<h3>Hi!,</h3> <p>You've been subscribed to our primary newsletter. You can expect to receive an email from us every few weeks, sharing the new things that we've published and new houses to check out. Occasionally, We'll share unique newsletter-only content as well</p><p>Thanks for choosing us!</p>"
            };
            axios
                .post(url, userData)
                .then((res) => {
                    setSignup(res);
                    setSignupMsg(true);
                    setSignupLoading(false);
                })
                .catch((signupError) => {
                    setError(signupError);
                    setSignupLoading(false);
                });
        };
    
        return { signup, signupError, signupMsg, signupLoading, handleChange, handleSignup };
    };
    export default usePost;
```
    

Let us import it in our Home file below:


    // frontend/src/pages/home/Home.js
    
```js
    import React from 'react';
    import { Link } from 'react-router-dom';
    import useFetch from '../../hooks/useFetch';
    import usePost from '../../hooks/usePost';
    import classes from './home.module.css';
    
    export default function Homepage() {
        const { estate, error, loading } = useFetch('http://localhost:1337/api/estates?populate=*');
        const { signup, signupError, signupMsg, signupLoading, handleChange, handleSignup } = usePost(
            'http://localhost:1337/api/email'
        );
    
        if (loading && signupLoading) return <p> Loading... </p>;
        if (error) return <p> Error :( </p>;
    
        return (
            <div className={classes['home']}>
                <section>
                    <h2>Welcome to our Estate</h2>
                    <hr className={classes['horizontal-rule']} />
                    <p>We help you find your new home</p>
                    <form className={classes['home-form']} onSubmit={handleSignup}>
                        <h5>Interested in joining our Newsletter</h5>
                        <h6>Sign up with your email below</h6>
                        <label htmlFor="email">
                            {signupError ? <p> {signupError} </p> : null}
                            Email Address:
                            <input type="email" name="email" value={signup} onChange={handleChange} />
                            {signupMsg ? <p> Thanks for signing up!</p> : null}
                        </label>
                        <button>Signup</button>
                    </form>
                    {estate.splice(0, 2).map((house) => (
                        <article className={classes['home-article']} key={house.id}>
                            <h2>{house.attributes.name}</h2>
                            <section className={classes['home-article-description']}>
                                <img
                                    src={`http://localhost:1337${house.attributes.image.data[0].attributes.url}`}
                                    alt="img"
                                />
                                <div>
                                    <p>{house.attributes.price}</p>
                                    <p>{house.attributes.description}</p>
                                    <Link to={`estates/${house.id}`}>See More...</Link>
                                </div>
                            </section>
                        </article>
                    ))}
                </section>
            </div>
        );
    }
```
    

Go ahead and test your app.

# Conclusion

We have seen how to use Strapi with React to build a startup website. We’ve also seen how to Integrate SendGrid with Strapi and React to create a newsletter email form.

Lastly, we have seen how to implement one of React’s new features— React Suspense — and what it does. I hope you understood what we did and can now implement same in your projects.

https://github.com/Vic-Orlands/Strapi-React