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
Bootstrap your App with react-static and grommet — Tutorial Part I

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
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
메타데이터
- 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