lab
This procedure is part of a lab that teaches you how to troubleshoot your web app with New Relic browser.
Each procedure in the lab builds upon the last, so make sure you've completed the last procedure, Instrument your application with our browser agent, before starting this one.
Until now, your application has been working fine. Users were able to place their orders and were satisfied with your service. But now that you have some insights in your application, you notice that it's showing some JavaScript errors.
In this procedure, you use New Relic browser to find out what's causing these errors and debug your application timely.
Important
In order to see your data in New Relic, you need to enable browser monitoring for this procedure.
If you haven't already, instrument your app with our browser agent.
Debug frontend errors
The bad news is that you've confirmed there are some errors in your application. The good news is that you recently instrumented your application with our browser agent! Go to New Relic and sign into your account, if you haven't already.
From the New Relic homepage, navigate to Browser and choose your Relicstaurants application.
Here, you see all the data related to your browser application including Page views with JavaScript errors, Core web vitals, User time on the site, Initial page load and route changes, and others.
Tip
Not seeing your data? Make sure you enabled browser monitoring and your load generator is running.
Notice Page views with javascript errors.
Here, you see spikes showing that your application has some Javascript errors.
Click on Page views with javascript errors.
This takes you to JS errors page where you see all the JS errors along with Total error instances.
Click on the Cart cannot be empty error for details.
Here, you see errorMessage, INSTANCES, INTERACTIONS AFFECTED and other details related to your error.
Next, navigate to Error Instances tab.
Here, you see more details related to your particular error including Event Log, Stack trace, and other.
Scroll down on the Error Instances page to view the Stack trace.
Here, you see the stack trace of your error.
Looking at the error details above, you now know the particular error affecting your services. However, the stack trace shown here is minified and it's hard to understand what's causing this error. To understand that, we need to upload the source map to un-minify the error.
Upload source map to un-minify JS error
Minified JavaScript mostly results in hard-to-understand, useless stack traces on browser's errors page. Uploading source maps converts these errors to understandable stack traces. It also provides a useful reference to code lines and makes it easier to debug. You can upload your soucre map to New Relic via UI, API or npm module.
Here, we use New Relic UI to upload source map and un-minify the JS error.
From JS errors page, navigate to Stack trace of the error and expand it.
Here, you see an option to upload source map.
Click on find file.
This opens a file explorer window for you to upload source map from your local storage. Find and upload your source map from build/static/js directory of your project.
Tip
Source map files have a file extension of .js.map
. Relicstaurants is set to generate source maps and you find it under build/static/js directory.
If you're having trouble generating source maps for your project, follow our documentation to learn how to generate one.
Once your source map is uploaded successfully, you see your error un-minified.
Here, you see the particular file and the line of code that's generating this error. Notice that at line 119, the Cart cannot be empty! is associated with onClick event in components/layouts/app/app-container/header/app-container-header.js file and is also triggering an alert for the user. Let's take a closer look at this file!
Open the application in the IDE of your choice and navigate to src/components/layouts/app/app-container/header/app-container-header.js file. Take a closer look at the shown code.
1import { Button, Drawer, Table } from 'antd';2import Text from 'antd/lib/typography/Text';3import { orderList } from 'atoms/order-list.atom';4import { useState } from 'react';5import { Link } from 'react-router-dom';6import { useRecoilState } from 'recoil';7import { Logo, StyledHeader } from './app-header-styled';8import Navi from './navi-items';9import { useNavigate } from 'react-router';10
11const Header = () => {12 const [isSidebarVisible, setIsSidebarVisible] = useState(false);13 const [orderListState, setOrderList] = useRecoilState(orderList);14 const navigate = useNavigate();15
16 const onClose = () => {17 setIsSidebarVisible(false);18 };19 const handleSidebarOpen = () => {20 setIsSidebarVisible(true);21 };22
23 const itemQuantity = (list) => {24 let totalItemQuantity = 0;25 list.forEach((item) => (totalItemQuantity += item.count));26
27 return totalItemQuantity;28 };29
30 const handleDeleteItem = (clickedRow) => {31 const reducedData = orderListState.filter((item) =>32 item.name === clickedRow.name ? false : true33 );34 setOrderList(reducedData);35 };36
37 const columns = [38 {39 title: 'Name',40 dataIndex: 'name',41 key: 'name',42 },43 {44 title: 'Count',45 dataIndex: 'count',46 key: 'count',47 },48 {49 title: 'Price',50 dataIndex: 'price',51 key: 'price',52 },53 {54 title: 'Delete',55 render: (clickedRow) => (56 <Button onClick={() => handleDeleteItem(clickedRow)}>-</Button>57 ),58 },59 ];60
61 return (62 <StyledHeader>63 <Link to="/">64 <Logo>65 <div>Relicstaurants</div>66 <p>by New Relic</p>67 </Logo>68 </Link>69 <Navi70 sidebarVisible={handleSidebarOpen}71 orderListLength={itemQuantity(orderListState)}72 />73 <Drawer74 size="large"75 title="Cart"76 placement="right"77 onClose={onClose}78 visible={isSidebarVisible}79 >80 <Table81 dataSource={orderListState}82 columns={columns}83 pagination={false}84 summary={(pageData) => {85 let totalPrice = 0;86
87 pageData.forEach(88 ({ price, count }) => (totalPrice += price * count)89 );90
91 return (92 <>93 <Table.Summary.Row>94 <Table.Summary.Cell colSpan={2}>Total</Table.Summary.Cell>95 <Table.Summary.Cell>96 <Text type="danger">{totalPrice.toFixed(2)}</Text>97 </Table.Summary.Cell>98 </Table.Summary.Row>99 <Table.Summary.Row>100 <Table.Summary.Cell colSpan={3}>101 <Button102 disabled={totalPrice > 0 ? false : true}103 primary104 onClick={() => {105 setOrderList([]);106 setIsSidebarVisible(false);107 }}108 >109 Clear Cart110 </Button>111 </Table.Summary.Cell>112 <Table.Summary.Cell>113 <Button 114 id="pay" 115 primary 116 onClick={() => { 117 if (!(totalPrice > 0)) { 118 var err = new Error('Cart cannot be empty!'); 119 newrelic.noticeError(err); 120 alert(err) 121 navigate('/') 122 setIsSidebarVisible(false); 123 } else { 124 navigate(`/payment`, { state: totalPrice }); 125 setIsSidebarVisible(false); 126 } 127 }} 128 > 129 PAY 130 </Button> 131 </Table.Summary.Cell>132 </Table.Summary.Row>133 </>134 );135 }}136 />137 </Drawer>138 </StyledHeader>139 );140};141
142export default Header;
Here, notice that the error Cart cannot be empty! only occurs whenever the user accidently tries to checkout with an empty cart. The function is coded to alert the end user that they can't proceed to checkout with an empty cart. You now know that this error will not affect your services. However, there are better ways to handle this edge case and avoid the error.
Press Ctrl+C
in the terminal that's running your application to stop serving it. Update the src/components/layouts/app/app-container/header/app-container-header.js as follows.
1import { Button, Drawer, Table } from 'antd';2import Text from 'antd/lib/typography/Text';3import { orderList } from 'atoms/order-list.atom';4import { Message } from 'components/common';5import { useState } from 'react';6import { Link } from 'react-router-dom';7import { useRecoilState } from 'recoil';8import { Logo, StyledHeader } from './app-header-styled';9import Navi from './navi-items';10import { useNavigate } from 'react-router';11
12const Header = () => {13 const [isSidebarVisible, setIsSidebarVisible] = useState(false);14 const [orderListState, setOrderList] = useRecoilState(orderList);15 const navigate = useNavigate();16
17 const onClose = () => {18 setIsSidebarVisible(false);19 };20 const handleSidebarOpen = () => {21 setIsSidebarVisible(true);22 };23
24 const itemQuantity = (list) => {25 let totalItemQuantity = 0;26 list.forEach((item) => (totalItemQuantity += item.count));27
28 return totalItemQuantity;29 };30
31 const handleDeleteItem = (clickedRow) => {32 const reducedData = orderListState.filter((item) =>33 item.name === clickedRow.name ? false : true34 );35 setOrderList(reducedData);36 };37
38 const columns = [39 {40 title: 'Name',41 dataIndex: 'name',42 key: 'name',43 },44 {45 title: 'Count',46 dataIndex: 'count',47 key: 'count',48 },49 {50 title: 'Price',51 dataIndex: 'price',52 key: 'price',53 },54 {55 title: 'Delete',56 render: (clickedRow) => (57 <Button onClick={() => handleDeleteItem(clickedRow)}>-</Button>58 ),59 },60 ];61
62 return (63 <StyledHeader>64 <Link to="/">65 <Logo>66 <div>Relicstaurants</div>67 <p>by New Relic</p>68 </Logo>69 </Link>70 <Navi71 sidebarVisible={handleSidebarOpen}72 orderListLength={itemQuantity(orderListState)}73 />74 <Drawer75 size="large"76 title="Cart"77 placement="right"78 onClose={onClose}79 visible={isSidebarVisible}80 >81 {orderListState.length > 0 ? (82 <Table83 dataSource={orderListState}84 columns={columns}85 pagination={false}86 summary={(pageData) => {87 let totalPrice = 0;88
89 pageData.forEach(90 ({ price, count }) => (totalPrice += price * count)91 );92
93 return (94 <>95 <Table.Summary.Row>96 <Table.Summary.Cell colSpan={2}>Total</Table.Summary.Cell>97 <Table.Summary.Cell>98 <Text type="danger">{totalPrice.toFixed(2)}</Text>99 </Table.Summary.Cell>100 </Table.Summary.Row>101 <Table.Summary.Row>102 <Table.Summary.Cell colSpan={3}>103 <Button104 disabled={totalPrice > 0 ? false : true}105 primary106 onClick={() => {107 setOrderList([]);108 setIsSidebarVisible(false);109 }}110 >111 Clear Cart112 </Button>113 </Table.Summary.Cell>114 <Table.Summary.Cell>115 <Button116 id="pay"117 disabled={totalPrice > 0 ? false : true}118 primary119 onClick={() => {120 navigate(`/payment`, { state: totalPrice });121 setIsSidebarVisible(false);122 }}123 >124 PAY125 </Button>126 </Table.Summary.Cell>127 </Table.Summary.Row>128 </>129 );130 }}131 />132 ) : (133 <Message>Nothing in cart</Message>134 )}135 </Drawer>136 </StyledHeader>137 );138};139
140export default Header;
Here, you modified the file show a message Nothing in cart instead of an error when the cart is empty. The PAY button remains disabled until the end users has items in their cart.
Restart your application
Now that you've fixed your application, it's time to restart your local server.
$npm run build$npm run newstart
Restart your load generator, as well.
$python3 simulator.py
Important
Make sure you're running these commands in the correct terminal windows. If you no longer have those windows, follow the steps in the setup procedure.
Once the load generator starts sending data to New Relic, notice that the application is no longer reporting JavaScript errors.
Summary
To recap, you observed an error in your application and used New Relic browser to:
- Review the error percentage
- Analyze the JS errors in your application
- Understand the error instance
- Debug the JS error by uploading source map
lab
This procedure is part of a lab that teaches you how to troubleshoot your web app with New Relic browser. Next, try to debug frontend slowness in your application.