Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Single-Page-Application Pages KB Article #2428

Merged
merged 16 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed _assets/images/pages/env-vars.png
Binary file not shown.
Binary file added _assets/images/pages/env_var.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions _kbarticlespages/single-page-application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
layout: post
title: "Hosting Single Page Applications on Pages"
date: November 2, 2023
excerpt:
---

## Intro
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved

cloud.gov Pages hosts static sites, but this doesn’t mean that developers are limited to only using [“static site generators”](https://jamstack.org/generators/): anything that can be compiled down into HTML can be run on Pages. You can use single-page application frameworks like Vue.js, Svelte, Angular, or React etc. and host it seamlessly on the Pages platform. This is achieved via the `npm run pages` command which allows developers to add a custom build script, and we’ll automatically publish everything in the `_site` folder. Details on utilizing a custom build script will be covered later in the article.
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved

In this example we will be taking a look at a simple single-page application which uses the React library and React Router v6. You can view a repository with a full example here <https://github.com/Ephraim-G/react_spa4> and see the results in Pages [here](https://federalist-01aa8660-8aca-452d-a270-5e58ffa18645.sites.pages.cloud.gov/site/ephraim-g/react_spa4/). There are also more detailed instructions on how to run this locally in the repositories’ README.md. This article serves a high level overview and will highlight three key features:
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved

* Application routing
* Environment variables
* 404 Pages

## Key features of a single-page application on Pages
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved

#### Application Routing
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved

Static site generators like Jekyll and Hugo handle routing by creating an HTML file at each path that can be requested by the user. For single-page applications, we will only render one HTML file: index.html, which loads the Javascript necessary for running our application and router.
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved

All of the routing is located in the `index.js` file. Here we import all our components, css and functions. We create the `<BrowserRouter>` and use the `<RouterProvider>` to provide that router to our application. The parent route registers a `Layout` component which essentially wraps all the other page components thus providing a uniform structure/design for all the components.
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved

```
const router = createBrowserRouter(
createRoutesFromElements(
// set route as the path plus layout
<Route path={path} element={<Layout />}>
<Route index element={<Home />}/>
<Route path={path + "Stuff"} element={<Stuff />}/>
<Route path={path + "contact"} element={<Contact />}/>
<Route path={path + "/*"} element={<Page404 />}/>
</Route>
)
);

createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);

```



Additionally within the `Layout` component is where we house all the `NavLinks` and `navbar` which are located at the top of the page. The `Outlet` component tells the `react router` where to output the child route page components within the layout.

```
class Layout extends Component {

render() {
return (
<div>
<h1>Simple SPA</h1>
<ul className="header">
{/* All nav links need to go to {path} */}
<li><NavLink to={path}>Home</NavLink></li>
<li><NavLink to={path + "stuff"}>Stuff</NavLink></li>
<li><NavLink to={path + "contact"}>Contact</NavLink></li>
</ul>
<div className="content">
<Outlet />
</div>
</div>
)
}
}

```

It’s important to remember that none of these routes are creating new HTML files at a given path, they are only rendering in the browser (unless you prerender with [react-snap](https://github.com/stereobooster/react-snap)). We’ll see how to handle situations when the URL doesn’t match the HTML file in a later section.

## Environment variables

Pages provides [certain environment variables](https://cloud.gov/pages/documentation/env-vars-on-pages-builds/) in each build container and you can also add customer variables in your “Site Settings”. In the case of `create-react-app`, the library we’re using in this example, [we need two variables](https://create-react-app.dev/docs/advanced-configuration/):
- `BUILD_PATH`: this determines the output directory where all the built assets end up after running `react-scripts build`. In our case, Pages requires all assets to be in the `_site` folder. We can set that in the UI like so:
<img src="{{ site.baseurl }}/assets/images/pages/env_var.png"/>

`PUBLIC_URL`: this variable determines how assets are referenced in the final build. We can’t set this one statically in the UI because our root URL will change for preview builds. Instead, we’ll add a small bash script to our custom build command to set the default environment variable `BASEURL` as the value for `PUBLIC_URL` like this:

```
echo "PUBLIC_URL=$BASEURL" >> .env
```

You can see how this is included in the build command in the [repository](https://github.com/Ephraim-G/react_spa4/blob/main/build.sh#L3C32-L3C32).
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved
Ephraim-G marked this conversation as resolved.
Show resolved Hide resolved





## 404 Pages

In our application code, we created a 404 page as another component to be imported into the `index.js` file to be handled by `react-router`. The Route for the 404 page was set as a wildcard catch-all so that when any path entered is not `/`, `/contact` or `/stuff` the Page404 component will be rendered.

```
<Route path={path + "/*"} element={<Page404 />}/>
```

If users navigate to a sub-path of your site directly, we need special handling for this type of 404 because the application will not have loaded your index.js file (or associated router).

It is important to note that:

1. Specifically for any single-page applications hosted on Pages you will need to reach out to an [Pages operator to manually set your custom 404](https://cloud.gov/pages/documentation/customization/#custom-domain-404-page) behavior to point to index.html.

2. This will only work on your production URL, and only when you have a custom domain. For preview links, you’ll need to navigate to the base URL first before moving on to other paths.
4 changes: 2 additions & 2 deletions _pages/pages/documentation/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ Pages Users within an Organization will be able to see all of the Sites in the O

- Navigate to the organizations tab
- Click edit in the lower right hand corner of the organization pane
<img src="{{ site.baseurl }}/assets/pages/images/edit_organizations.png"
<img src="{{ site.baseurl }}/assets/images/pages/edit_organizations.png"
alt="'Your Organizations' pane with an 'Edit' link highlighted"/>

- Click the plus sign under “Members”
<img src="{{ site.baseurl }}/assets/pages/images/add_user.png"
<img src="{{ site.baseurl }}/assets/images/pages/add_user.png"
alt="Organization edit pane with the plus sign button next to 'Add user' highlighted"/>

- Fill out the required fields of Email and Role
Expand Down
Loading