← Back to list

Bootstrap your App with react-static and grommet — Tutorial Part I

Full tutorial series to build a beautiful PWA App for your Company/Product and host it on Firebase with CI/CD

Christian Kaatz in ITNEXT · 2020-12-15 17:08 · 13 claps · 7.5 min read
#development #react #grommet #react-static #tutorial
Open on Medium ↗
Wiki topics: 🌐 · Web Development ☁️ · DevOps & Cloud

Bootstrap your App with react-static and grommet — Tutorial Part I

Photo by Al Hakiim on Unsplash

Photo by Al Hakiim on Unsplash

01 React-Static-App

This is the first part of a series to kickstart your app development for your company or project with react-static and for the UI, it uses grommet. Later in this series, we will deploy the App to Firebase, add CI to have each commit directly published on our target environment, add PWA capabilities and finally add End to End testing with Cypress.

Getting started

We’ll create an initial project with react static, in order to do that, we need to install it first (assuming, you already have Node and NPM/Yarn installed.)

$ npm i -g react-static
# or
$ yarn global add react-static

After that, you can create your new app

$ react-static create
? What should we name this project? the-app
? Select a template below… basic
Creating new react-static project…
Using React Static template: basic
Installing dependencies with: Yarn…
[✓] Project “the-app” created (19.7s)
To get started:
cd “the-app”
yarn start — Start the development server
 yarn build — Build for production
 yarn serve — Test a production build locally

Which will create the following folder structure for you:

.
├── LICENSE
├── README.md
├── package.json
├── public
│ └── robots.txt
├── src
│ ├── App.js
│ ├── app.css
│ ├── components
│ │ └── Router.js
│ ├── containers
│ │ ├── Dynamic.js
│ │ └── Post.js
│ ├── index.js
│ └── pages
│ ├── 404.js
│ ├── about.js
│ ├── blog.js
│ └── index.js
├── static.config.js
└── yarn.lock
5 directories, 16 files

Now we want to make the page beautiful by adding grommet.

$ yarn add yarn add grommet styled-components grommet-icons

I already created an app layout on grommet designer which can be found in the grommet-designer folder of the repository. This JSON file can be imported on https://designer.grommet.io/ and customised on your needs and later used within your app.

Customisation

React static, offers the capability to dynamically load content during build and then create pages for e.g. blog posts but we don’t need this capability yet, therefore we will make some changes to the static.config.js and remove this part. Replace the contents of the file with the following and also remove axios from your package.json to make your app smaller.

import path from "path";
export default {
  plugins: [
    [
      require.resolve("react-static-plugin-source-filesystem"),
      {
        location: path.resolve("./src/pages"),
      },
    ],
    require.resolve("react-static-plugin-reach-router"),
    require.resolve("react-static-plugin-sitemap"),
  ],
  silent: true,
};

Components

We want to create a small Navigation component as usually used in apps with three items.

First, we’ll need more methods exported from the router leveraged through the components/Router.js

export { Link, Router, useNavigate, useLocation } from “@reach/router”;

And then, we create a file called Navigation.js in the components folder with the following contents:

import React from "react";
import { Box, Grid } from "grommet";
import { Home, Menu, BarChart } from "grommet-icons";
import { useNavigate, useLocation } from "components/Router";
const Navigation = () => {
  const navigate = useNavigate();
  const location = useLocation();
// highlight the active router/area we are in
  const getNavColor = ({ pathname }, currentPage) =>
    currentPage.indexOf(pathname) >= 0 ? "accent-1" : "light-6";
return (
    <Box align="center" justify="center" fill="horizontal">
      <Grid
        columns={["1/3", "1/3", "1/3"]}
        fill="horizontal"
        rows={["auto"]}
        areas={[
          { name: "first", start: [0, 0], end: [0, 0] },
          { name: "second", start: [1, 0], end: [1, 0] },
          { name: "third", start: [2, 0], end: [2, 0] },
        ]}
      >
        <Box
          align="center"
          justify="center"
          background={{
            color: getNavColor(location, ["/"]),
          }}
          onClick={() => navigate("/")}
          flex
          pad="medium"
          overflow="hidden"
          gridArea="first"
        >
          <Home />
        </Box>
        <Box
          align="center"
          justify="center"
          background={{
            color: getNavColor(location, ["/progress"]),
          }}
          flex="grow"
          onClick={() => navigate("/progress")}
          pad="medium"
          overflow="hidden"
          gridArea="second"
        >
          <BarChart />
        </Box>
        <Box
          align="center"
          justify="center"
          background={{
            color: getNavColor(location, ["/more", "/legal", "/about"]),
          }}
          pad="medium"
          gridArea="third"
          onClick={() => navigate("/more")}
        >
          <Menu />
        </Box>
      </Grid>
    </Box>
  );
};
export default Navigation;

It contains a Box and a Grid with 3 fixed areas containing each navigation item.

App

The App.js also needs some changes and make use of our new Navigation.

App Preview

App Preview

import React from "react";
import { Root, Routes } from "react-static";
import { Grommet, Box, Main } from "grommet";
import { grommet } from "grommet/themes";
import { Router } from "components/Router";
import Navigation from "components/Navigation";
function App() {
  return (
    <Root>
      <Grommet full theme={grommet}>
        <Box
          fill
          overflow="hidden"
          align="center"
          flex="grow"
          background={{ color: "light-1" }}
        >
          <Box align="center" justify="center" fill pad="medium">
            <React.Suspense fallback={<em>Loading...</em>}>
              <Main
                fill
                overflow="auto"
                pad="medium"
                direction="column"
                justify="start"
                align="center"
                flex="grow"
              >
                <Router>
                  <Routes path="*" />
                </Router>
              </Main>
            </React.Suspense>
          </Box>
          <Navigation />
        </Box>
      </Grommet>
    </Root>
  );
}
export default App;

Also note, that we’ll make use of React’s suspense to cache each route to speed it up in the future.

We are using the default theme of grommet, but this can be easily replaced, by creating your own theme and overwriting/customising the app to your needs.

Pages

Home

Now, as we have the skeleton ready for the app, we need to update the index.js to use grommet and and show our exported screen from grommet designer — everything within the main container there and should be changed to look like the following:

import React from "react";
import { Box, Heading, Paragraph } from "grommet";
export default () => (
  <Box align="center" justify="center" fill pad="medium">
    <Heading>Home</Heading>
    <Paragraph fill>
      Jean shorts kombucha vice schlitz, ugh raw denim godard YOLO locavore
      gluten-free mumblecore enamel pin DIY retro. Health goth shaman put a bird
      on it flexitarian cloud bread bitters keffiyeh vinyl offal vegan celiac
      distillery. Kitsch beard neutra try-hard, gochujang roof party jianbing.
      Activated charcoal green juice coloring book slow-carb chambray unicorn
      gentrify selvage.
    </Paragraph>
    <Paragraph fill>
      Selvage pitchfork bushwick seitan chia. Hot chicken try-hard godard next
      level tacos. Try-hard actually green juice pop-up, salvia pug chillwave
      gastropub tofu flexitarian pok pok normcore sartorial squid. Actually
      gochujang flexitarian austin kitsch humblebrag single-origin coffee.
      Portland fanny pack organic, occupy semiotics brooklyn green juice pork
      belly seitan 90's meggings hot chicken.
    </Paragraph>
  </Box>
);

Progress

Next we’ll add a secondary page — I called it progress.js:

import React from "react";
import { Heading, Box, Chart, Paragraph } from "grommet";
export default function Progress() {
  return (
    <Box align="center" justify="center" fill pad="medium">
      <Heading>Progress</Heading>
      <Chart
        type="bar"
        values={[
          { value: [0, 10] },
          { value: [1, 20] },
          { value: [2, 25] },
          { value: [3, 40] },
          { value: [4, 35] },
        ]}
      />
      <Paragraph fill>
        Tumblr lo-fi prism viral. Freegan single-origin coffee chillwave disrupt
        umami lo-fi. Scenester church-key helvetica blog yuccie sriracha
        kickstarter live-edge put a bird on it organic portland, tote bag umami
        schlitz. Quinoa beard pabst microdosing cray DIY.
      </Paragraph>
    </Box>
  );
}

More

The third top-level page will be the More screen — more.js

import React from "react";
import { Heading, Box, Nav, Button } from "grommet";
import { useNavigate } from "components/Router";
export default function More() {
  const navigate = useNavigate();
  return (
    <Box align="center" justify="center" fill pad="medium">
      <Heading>More</Heading>
      <Box
        justify="center"
        direction="row"
        fill="horizontal"
        gap="xsmall"
        pad="xsmall"
      >
        <Nav
          align="center"
          flex={true}
          direction="column"
          fill="horizontal"
          gap="medium"
        >
          <Button
            secondary
            size="medium"
            onClick={() => navigate("/about")}
            label="About"
          />
          <Button
            secondary
            size="medium"
            onClick={() => navigate("/legal")}
            label="Legal"
          />
        </Nav>
      </Box>
    </Box>
  );
}

The More screen contains links to 2 more pages: About and Legal where we can put all other maybe required content.

The About screen will be rendered by the about.js file and should look as following:

import React from "react";
import { Box, Heading, Paragraph } from "grommet";
import { Mail } from "grommet-icons";
export default () => (
  <Box align="center" justify="center" fill pad="medium">
    <Heading>About</Heading>
    <Heading level="3">ACME Corp</Heading>
    <Box align="center" justify="start" direction="row" gap="small">
      <Mail />
      <Paragraph>info@acme.corp</Paragraph>
    </Box>
  </Box>
);

The Legal screen will be rendered by the legal.js file and should look as following:

import React from "react";
import { Box, Heading, Paragraph } from "grommet";
export default () => (
  <Box fill="vertical" overflow="hidden" align="center" flex="grow">
    <Box align="center" justify="center" fill overflow="auto">
      <Box
        align="center"
        justify="center"
        pad="large"
        wrap={false}
        overflow="auto"
        flex="grow"
      >
        <Heading>Legal</Heading>
        <Paragraph fill>
          Portland disrupt tumeric thundercats fingerstache meditation. Meggings
          austin fashion axe, subway tile hot chicken gentrify enamel pin
          poutine vice forage. Adaptogen chillwave air plant la croix. Cred
          literally craft beer ugh hexagon brunch godard williamsburg kinfolk
          waistcoat. Schlitz food truck shoreditch selvage affogato, venmo cray
          gastropub subway tile roof party craft beer sustainable try-hard you
          probably haven't heard of them. Chambray health goth gochujang ugh,
          bitters seitan blog hoodie XOXO keytar.
        </Paragraph>
        <Paragraph fill>
          Portland disrupt tumeric thundercats fingerstache meditation. Meggings
          austin fashion axe, subway tile hot chicken gentrify enamel pin
          poutine vice forage. Adaptogen chillwave air plant la croix. Cred
          literally craft beer ugh hexagon brunch godard williamsburg kinfolk
          waistcoat. Schlitz food truck shoreditch selvage affogato, venmo cray
          gastropub subway tile roof party craft beer sustainable try-hard you
          probably haven't heard of them. Chambray health goth gochujang ugh,
          bitters seitan blog hoodie XOXO keytar.
        </Paragraph>
        <Paragraph fill>
          Portland disrupt tumeric thundercats fingerstache meditation. Meggings
          austin fashion axe, subway tile hot chicken gentrify enamel pin
          poutine vice forage. Adaptogen chillwave air plant la croix. Cred
          literally craft beer ugh hexagon brunch godard williamsburg kinfolk
          waistcoat. Schlitz food truck shoreditch selvage affogato, venmo cray
          gastropub subway tile roof party craft beer sustainable try-hard you
          probably haven't heard of them. Chambray health goth gochujang ugh,
          bitters seitan blog hoodie XOXO keytar.
        </Paragraph>
        <Paragraph fill>
          Portland disrupt tumeric thundercats fingerstache meditation. Meggings
          austin fashion axe, subway tile hot chicken gentrify enamel pin
          poutine vice forage. Adaptogen chillwave air plant la croix. Cred
          literally craft beer ugh hexagon brunch godard williamsburg kinfolk
          waistcoat. Schlitz food truck shoreditch selvage affogato, venmo cray
          gastropub subway tile roof party craft beer sustainable try-hard you
          probably haven't heard of them. Chambray health goth gochujang ugh,
          bitters seitan blog hoodie XOXO keytar.
        </Paragraph>
        <Paragraph fill>
          Portland disrupt tumeric thundercats fingerstache meditation. Meggings
          austin fashion axe, subway tile hot chicken gentrify enamel pin
          poutine vice forage. Adaptogen chillwave air plant la croix. Cred
          literally craft beer ugh hexagon brunch godard williamsburg kinfolk
          waistcoat. Schlitz food truck shoreditch selvage affogato, venmo cray
          gastropub subway tile roof party craft beer sustainable try-hard you
          probably haven't heard of them. Chambray health goth gochujang ugh,
          bitters seitan blog hoodie XOXO keytar.
        </Paragraph>
      </Box>
    </Box>
  </Box>
);

All Texts are based on the Hipster Lorem generator.

404 Page

Finally, we added a 404 page if someone hits a non-existent page.

import React from "react";
import { Box, Heading } from "grommet";
export default () => (
  <Box align="center" justify="center" fill pad="medium">
    <Heading>404 - Oh no's! We couldn't find that page :(</Heading>
  </Box>
);

Wrap-Up

At this stage, we have a working application which can be tested and further customised to your own needs.

Next, we’ll deploy the App to Firebase and test staged rollouts.

# start the dev server
$ yarn start
# build your bundle which can be deployed
$ yarn build

Code

The Code can be found at GitHub https://github.com/chrkaatz/static-app-tutorial where each tutorial step will have it’s respective commit and tag associated with it. This one is tagged with 01-React-Static-App.

Tutorial

  1. **Part I — Bootstrap your App with react-static and grommet**
  2. Part II — React-static app deployment and CI
  3. Part III — React-static PWA
  4. Part IV — React-static app testing with Cypress

메타데이터
post_id
fac9c137cc31
slug
bootstrap-your-app-with-react-static-and-grommet-fac9c137cc31
url
https://medium.com/itnext/bootstrap-your-app-with-react-static-and-grommet-fac9c137cc31
canonical_url
https://medium.com/itnext/bootstrap-your-app-with-react-static-and-grommet-fac9c137cc31
author_url
https://medium.com/@chrkaatz
status
ok
fetched_at
2026-06-17 18:03:35