AWS Cloud Operations Blog

Using Amazon CloudWatch RUM with a React web application in five steps

In this post we will explain how you can use Amazon CloudWatch RUM to monitor a single-page web application built using React.

CloudWatch RUM is a real user monitoring (RUM) capability which helps you identify and debug client-side issues and enhance the end user’s digital experience. The data that you can visualize and analyze includes page load times, client-side errors, and user behavior. This helps application developers and DevOps engineers to reduce the mean time to resolve (MTTR) for client-side performance issues. Please refer to blog post An Observability Journey with Amazon CloudWatch RUM, Evidently, and ServiceLens for more on the benefits of RUM and other features of CloudWatch.

Solution Overview

To use RUM, you create an app monitor, then you then install the CloudWatch RUM web client in your application via npm, and paste the JavaScript or TypeScript snippet into your application’s source code.

In the first three steps of this blog post, you will create a RUM application and a basic React app.

Once your React application is integrated with RUM, you will start to see custom page argument e.g userid in your RUM page views. In step four, you will remove arguments from the URL paths collected in RUM; this aggregates similar URL paths.

Finally step five, shows you how to use React Error Boundaries to catch your errors and still send the error information to RUM.

Step 1: Create RUM App Monitor

In step two, you will create a web server on your local machine (localhost) to host the React web application but first we will create a RUM App Monitor to monitor localhost in the AWS console.

  1. Open the CloudWatch console and select RUM under the “Application monitoring” heading
  2. Click “Add app monitor” (you may need to click “Getting started” if this is your first visit)
  3. Add a name for your app monitor, I used “localhost”.
  4. Under application domain, specify “localhost”
    1. If you are not using localhost, please update the application domain to match the URL you will be using or your application will be unable to post data to RUM.
  5. Under Configure RUM data collection, leave Performance telemetry, JavaScript errors and HTTP errors selected
  6. Leave “Allow cookies” selected
  7. Leave sessions samples at 100%
  8. Under Data storage, leave the option to store application telemetry data in CloudWatch logs not selected
  9. Under Authorization, ensure “Create a new identity pool” is selected
  10. Leave the remaining settings as Default and click “Add app monitor”
  11. You will be presented with a script, change the code format to JavaScript and copy or download the script, you will need this for Step 3.
  12. If you need to retrieve the script later, you can browse into the app monitor and navigate to the Configuration tab.
Animated GIF of the app creation process

Figure 1: Application creation process

Step 2: Create a local React application

In this step we will create a simple React Singe Page Application (SPA) and install the RUM NPM module but not configure it.

From the command line on your workstation follow the steps below to create the new local React application:

1. Create Default React App in a new directory.

npx create-react-app rum-react

2. Install the npm CloudWatch RUM and React Router modules.

React Router will allow us to add url based routing to our SPA and CloudWatch RUM will allow us to easily integrate our SPA with the CloudWatch Rum service.

cd rum-react
npm install aws-rum-web react-router-dom

3. Create the local app.

In the src folder, replace all contents of the App.js file with the code below and save. This code is a basic React application using React Router, with a few routes created, no integration with CloudWatch RUM has yet been configured:

import React from "react";
import {
  BrowserRouter as Router,
  Routes,
  Route,
  Link,
  useLocation
} from "react-router-dom";

export default function App() {
  return (
    <Router>
      <div>
        <p><Link to="/">Home</Link></p>
        <p><Link to="/about">About</Link></p>
        <p><Link to="/users">Users</Link></p>
        <p><Link to="/welcome">Welcome</Link></p>
        <Routes>
          <Route path="/about" element={<About />} />
          <Route path="/users" element={<Users />} />
          <Route path="/user/*" element={<User />} />
          <Route path="/welcome" element={<Welcome />} />
          <Route exact path="/" element={<Home />} />
        </Routes>
      </div>
    </Router>
  )
}

function Home() {
  return <h2>Home</h2>;
}

function About() {
  return <h2>About</h2>;
}

function Users() {
  return <div>
    <h2>Users</h2>
    <p><Link to="/user/1">User 1</Link></p>
    <p><Link to="/user/2">User 2</Link></p>
    <p><Link to="/user/3">User 3</Link></p>
  </div>
  ;
}

function User() {
  const location = useLocation();
  const user = location.pathname.split('/').pop();
  return <h2>User: {user}</h2>;
}

function Welcome() {
  // deliberate error
  return <h2>Welcome {this.subject.toUpperCase()}</h2>;
}

4.    Start and test the development server.

  • Type “npm start” which will open a new browser tab and navigate to http://localhost:3000
  • Browse the page and click on the links, note that the “Welcome” link is broken deliberately, you will need to reload the page after clicking Welcome
Browser window showing the running sample application

Figure 2. Sample application

Step 3: Instrument Application with CloudWatch RUM web client

Now you will configure the RUM module to automatically instrument PageViews in our sample application.

  1. Edit the src/App.js file
  2. Take the sample code you generated earlier in Step 1 and add it after the last “import”.
  3. Initialise the awsRum variable before the try block (let awsRum = null), and remove the const before awsRum inside the try block.  This will allow us to access awsRum globally in the application and we’ll use this in steps four and five.
  4. When you are done save the changes and it should look similar to this:
Code snippet showing the contents of the App.js file

Figure 3: Snippet of App.js file

­­­­­­­­­Note, none of the items in the config object are secret – they are publicly accessible by design.

  1. Now you can see RUM in action: browse to your application, http://localhost:3000, using ‘Web Developer Tools’ open the Network Tab.  You will see the additional POST requests to the RUM dataplane (dataplane.rum.eu-west-1.amazonaws.com), including at least one ‘page_view_event’.
Network section of Developer Tools view of the Home page showing POST events to the RUM dataplane.

Figure 4. Network Section of Developer tools

  1. If you would like to browse around your application and click on the various sections, including Users, you will be able to see the pageviews in the AWS CloudWatch RUM Console.   Notice that when you browse Users, what is reflected in RUM contains the userid for every user page that you click on.

Figure 5: RUM console

Step 4: Omit user ID from page views

If your application contains arguments in the URL’s path, you may want to record custom page IDs so that the arguments can be removed and the pages will be aggregated in CloudWatch. For example, if you have two URLs https://localhost/user/1 and https://localhost/user/2, you can remove the user ID from the path so that the page ID is /user for both URLs.

To do this, you will update the sample application as follows:

  1. Disable automatic logging of all page views to RUM by adding disableAutoPageView:true to the config section in src/App.js. Note, you will need to add a comma on the previous line.

Figure 6: Code snippet

  1. To handle the recording of page views, we add 2 new functions:
    1. RecordPageView(): Records page views normally
    2. RecordPageViewWithoutUserId(): Removes the “user id” from the recorded page views.

Add the following before the default function export in src/App.js and save:

function RecordPageView() {
  let location = useLocation();
  React.useEffect(() => {
    console.log('logging pageview to cwr: ' + location.pathname);
    awsRum.recordPageView(location.pathname);
  }, [location]);
}

function RecordPageViewWithoutUserId() {
  let location = useLocation();
  let baseLocation = location.pathname.split('/');
  baseLocation.pop();
  const baseLocationPath = baseLocation.join('/')
  React.useEffect(() => {
    console.log(baseLocationPath);
    awsRum.recordPageView(baseLocationPath);
  }, [baseLocationPath]);
}

Figure 7: Two new functions in the App.js file

We will now instrument our React components to invoke the new functions. Sections – Home, About, Users and Welcome should use RecordPageView() but the User section should use RecordPageViewWithoutUserId() to strip the user id from the recorded URLs. Once updated and you have saved your code, your App.js should look like this:

Figure 8: Updated React components in the App.js file

  1. If you now browse your site again (you may need to refresh the page if you already had it open) and open the user pages and examine the Network Tab with Web Developer Tools, you will see in the POST request to the RUM dataplane that the path logged is /user not /user/1 or /user/2.  Before and After screenshots are shown below.
Network section of Developer Tools showing detail sent to RUM dataplane before changes were made

Figure 9: Network section in Developer Tools before changes

Network section of Developer Tools showing detail sent to RUM dataplane after changes were made

Figure 10: Network section in Developer Tools after changes

  1. Now that you have seen the data being posted, take a look in RUM and you will see page views for the Users page without the corresponding ids.
RUM console showing the userid is no longer being recorded.

Figure 11: RUM console

  1. As a recap, in this step, we have turned off RUMs automatic page recording (disableAutoPageView:true) and manually implemented our own page recording using the awsRum.recordPageView method so we can choose exactly how are pages are recorded in RUM.

Step 5: Instrument Error Handling to Record Errors

Now if you browse to the Welcome page, you will notice that we have an error on this page that is not being caught by the application or React, resulting in a blank page and an error in the browser console for users, but it is sending the error information to RUM.

RUM console showing error.

Figure 12. RUM console showing error

React Error Boundaries can be used to catch this error but, by default, errors would not be emitted to RUM, so you must also instruct the Error Boundary to emit the captured error to RUM.  You will also have the Error Boundary render a helpful message for the user.

  1. In src/App.js, add an ErrorBoundary class after the two new PageView functions you added earlier:
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    console.log('recordingError: ' + error)
    awsRum.recordError(error);
  }
  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return (
        <div>
          <h1>Something went wrong.</h1>
          <button onClick={() => {window.location.href = '/'}}>Clear Error</button>
        </div>
      );
    }
    return this.props.children; 
  }
}

Note the awsRum.recordError(error) in componentDidCatch – this statement sends captured errors to RUM.

  1. So that the new code is used we will add <ErrorBoundary>..</ErrorBoundary> around our <Routes>…</Routes> and save the changes to App.js, your updated code in should look like this:
export default function App() {
  return (
    <Router>
      <div>
        <p><Link to="/">Home</Link></p>
        <p><Link to="/about">About</Link></p>
        <p><Link to="/users">Users</Link></p>
        <p><Link to="/welcome">Welcome</Link></p>
        <ErrorBoundary>
          <Routes>
            <Route path="/about" element={<About />} />
            <Route path="/users" element={<Users />} />
            <Route path="/user/*" element={<User />} />
            <Route path="/welcome" element={<Welcome />} />
            <Route exact path="/" element={<Home />} />
          </Routes>
        </ErrorBoundary>
      </div>
    </Router>
  )
}
Code snippet highlighting the addition of ErrorBoundary

Figure 13: Code snippet highlighting the addition of ErrorBoundary

  1. Now browse to the localhost site again and open the Welcome page, a graceful error message will now be displayed.
  2. Using your browsers Web Developer Tools to look at the POST to CloudWatch RUM (the url will be similar to dataplane.rum.eu-west-1.amazonaws.com), you will see that a com.amazon.rum.js_error_event has been logged with an error of “this is undefined” which is the expected error message in this example.  Note, if you are not using the Firefox browser, you may see a different error.
Network section of Developer Tools showing the error page and details sent to RUM.

Figure 14: Network section of Developer Tools showing error page

  1. If you now look in RUM, you will see these errors recorded, which would not occur if the error boundary had not been configured to emit the error to RUM correctly:
RUM console showing error.

Figure 15. RUM console showing error

To recap – In this step, you have used React Error Boundaries to catch errors and used awsRum.recordError to send records to RUM.

Clean-up

  1. Stop your local site (Control+C) and delete files if no longer required.
  2. Remove the RUM app monitor:
  • Open RUM from CloudWatch console and select List View
  • Select the app monitor you created earlier
  • Click Actions and click Delete
  • Confirm Deletion.

Conclusion

In this post, we created a new CloudWatch RUM App monitor and a basic React site.  You added the CloudWatch RUM NPM module to the React site to record page views, you used disableAutoPageView:true  and the awsRum.recordPageView method to configure page recording to remove the userid from the recorded URL.  Finally, you added React error handling with CloudWatch RUM integration using Error Boundaries.  For more information, refer to the CloudWatch RUM documentation and CloudWatch RUM Web Client github repo here.   All sample code is for demonstration purposes only.

Visit the One Observability Workshop for more examples and a deeper dive into synthetic testing, log correlation, custom metrics, advanced dashboarding, anomaly detection models, and more.

About the authors:

Ania Develter

Ania Develter is a Senior Specialist Solutions Architect in the AWS Cloud Operations team.  Ania works with customers from all industries and helps them with their observability, compliance and centralised operations management challenges.  She loves talking about Observability, CloudOps and DevOps.Ania Develter.

Steven Askwith

Steven Askwith is a Senior Solutions Architect in the UK NPO Team. Steven helps Non Profit Organisations to build on AWS. He also helps customers with their DevOps challenges. His areas of interest include, DevOps, developer tools, CI/CD and CDK.