---
title: "Frontend"
description: "How we write frontend code at Sentry."
url: https://develop.sentry.dev/frontend/
---

# Frontend

This guide specifically focuses on the frontend part of the [Sentry](https://github.com/getsentry/sentry).

## [Directory structure](https://develop.sentry.dev/frontend.md#directory-structure)

The frontend codebase is currently located under `static/app`.

As of March 2025 `static/gsApp` and `static/gsAdmin` have been open-sourced and merged into [Sentry](https://github.com/getsentry/sentry). If you are looking for earlier git history of those folders you need to have access to [Getsentry](https://github.com/getsentry/getsentry).

## [Folder & File structure](https://develop.sentry.dev/frontend.md#folder--file-structure)

### [File Naming](https://develop.sentry.dev/frontend.md#file-naming)

* Name a file meaningfully, based on how the module's functions, or classes are used or the application section they are used in.
* Unless necessary, don't use prefixes or suffixes (ie. `dataScrubbingEditModal`, `dataScrubbingAddModal`) instead favor names like `dataScrubbing/editModal`.
* Tip: Name the file to match the component or function that is being exported. This makes it easier for people to know what is inside the file, and re-use it.

### [Using `index.tsx?$`](https://develop.sentry.dev/frontend.md#using-indextsx)

Having an `index` file in a folder provides a way to implicitly import the main file without specifying it.

The use of an index file should comply with the following rules:

* If the folder is created to group components that are used together, and there is an entrypoint component, that uses the components within the grouping (examples, avatar, idBadge). The entrypoint component should be the index file.
* *Don't* use an `index.tsx?$` file if the folder contains components used in other parts of the app regardless of the entrypoint file. (ie, actionCreators, panels)
* *Don't* use an index file just to re-export. Prefer importing individual components instead.

## [React](https://develop.sentry.dev/frontend.md#react)

### [Defining React components](https://develop.sentry.dev/frontend.md#defining-react-components)

New components should be created as functional components, using function declarations instead of arrow functions. Props should be declared above the component.

```typescript
interface Props {
  author: object;
  content: string;
  onEdit: (value: string) => void;
}

// use destructuring assignment for props
export default function Note({ author, content, onEdit }: Props) {
  const handleChange = (value) => {
    const user = ConfigStore.get("user");

    if (user.isSuperuser) {
      onEdit(value);
    }
  };

  return <div onChange={handleChange}>{content}</div>;
}
```

### [Components vs views](https://develop.sentry.dev/frontend.md#components-vs-views)

Both the `app/components/` and `app/views/` folders contain React components.

* Put files in `app/views/` when the component can not be reused in other parts of the codebase.
  * Typically only layout/construction of full pages should be in this folder. ie: files that are imported by [routes.tsx](https://github.com/getsentry/sentry/blob/master/static/app/routes.tsx).
* Put files into `app/components/` for anything else that *could* be reused.
  * If the component accepts any prop beyond `RouteComponentProps` then it very likely belongs inside `app/components/`

### [Type declarations](https://develop.sentry.dev/frontend.md#type-declarations)

We do not follow any strict "prefer type over interface" rules for type declarations, so using either is at your discretion. That being said, there are [functional differences](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces) between the two that you should be aware of so you can use them appropriately.

A simple guiding principle is to prefer interfaces when you require declaration merging. The reason for that is that type intersections are poor at detecting conflicts, produce unexpected or unwanted results and when used in combination with large types [perform worse than interfaces](https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections).

Example:

```typescript
// This is valid and the outcome is a "never" type
type A = { color: "blue" } & { color: "red" };

interface B {
  color: "blue";
}
// Interface 'C' incorrectly extends interface 'B'.
//   Types of property 'color' are incompatible.
interface C extends B {
  color: "red";
}
```

### [Event handlers](https://develop.sentry.dev/frontend.md#event-handlers)

We use different prefixes to better distinguish event handlers from event callback props.

Use the `handle` prefix for event handlers, e.g:

```javascript
<Button onClick={this.handleDelete} />;
```

For event callback props passed to the component use the `on` prefix, e.g:

```javascript
<Button onClick={this.props.onDelete}>
```

## [State management](https://develop.sentry.dev/frontend.md#state-management)

For React Hooks tips check out [this page](https://develop.sentry.dev/frontend/using-hooks.md).

Prefer the built-in `useState` and `useReducer` react hooks for state whenever possible.

We currently also have [Reflux](https://github.com/reflux/refluxjs) and [MobX](https://github.com/mobxjs/mobx) included in package.json but their use for new cases is discouraged.

Reflux implements the unidirectional data flow pattern outlined by [Flux](https://facebookarchive.github.io/flux). Stores are registered under `app/stores` and are used to store various pieces of data used by the application. Actions need to be registered under `app/actions`. We use action creator functions (under `app/actionCreators`) to dispatch actions. Reflux stores listen to actions and update themselves accordingly.

## [Testing](https://develop.sentry.dev/frontend.md#testing)

For React Testing Library (RTL) tips check out [this page](https://develop.sentry.dev/frontend/using-rtl.md).

Note: Your filename needs to be `.spec.tsx` for jest to run run it!

We have useful fixtures defined in [tests/js/fixtures/](https://github.com/getsentry/sentry/tree/master/tests/js/fixtures) Use these! If you are defining mock data in a repetitive way, it’s probably worth adding this these files. `routerContext` is a particularly useful one for providing the context object that most view are written to rely on.

`Client.addMockResponse()` is the best way to mock API requests. it’s [our code](https://github.com/getsentry/sentry/blob/master/static/app/__mocks__/api.tsx) so if it’s confusing you, just put `console.log()` statements into its logic!

Marking your test method `async` and using the `await tick();` utility can let the event loop flush run events and fix this:

```javascript
wrapper.find("ExpandButton").simulate("click");
await tick();
expect(wrapper.find("CommitRow")).toHaveLength(2);
```

## [Babel Syntax Plugins](https://develop.sentry.dev/frontend.md#babel-syntax-plugins)

We have decided to only use ECMAScript proposals that are in stage 3 (or later) (See [TC39 Proposals](https://github.com/tc39/proposals)). The only exception to this are decorators.

## [New Syntax](https://develop.sentry.dev/frontend.md#new-syntax)

### [Optional Chaining](https://develop.sentry.dev/frontend.md#optional-chaining)

[Optional chaining](https://github.com/tc39/proposal-optional-chaining) helps us access \[nested] objects without having to check for existence before each property/method access. If we try to access a property of an `undefined` or `null` object, it will stop and return `undefined`.

#### [Syntax](https://develop.sentry.dev/frontend.md#syntax)

The Optional Chaining operator is spelled `?.`. It may appear in three positions:

```bash
obj?.prop       // optional static property access
obj?.[expr]     // optional dynamic property access
func?.(...args) // optional function or method call
```

> From <https://github.com/tc39/proposal-optional-chaining>

### [Nullish Coalescing](https://develop.sentry.dev/frontend.md#nullish-coalescing)

This is a way to set a "default" value. e.g. previously you would do something like

```javascript
let x = volume || 0.5;
```

Which is a problem since `0` is a valid value for `volume`, but because it evaluates to `false` -y, we do not short circuit the expression and the value of `x` is `0.5`

If instead we used [nullish coalescing](https://github.com/tc39/proposal-nullish-coalescing)

```javascript
let x = volume ?? 0.5;
```

It will only default to `0.5` if `volume` is `null` or `undefined`.

#### [Syntax](https://develop.sentry.dev/frontend.md#syntax-1)

Base case. If the expression at the left-hand side of the ?? operator evaluates to undefined or null, its right-hand side is returned.

```javascript
const response = {
  settings: {
    nullValue: null,
    height: 400,
    animationDuration: 0,
    headerText: "",
    showSplashScreen: false,
  },
};

const undefinedValue =
  response.settings.undefinedValue ?? "some other default"; // result: 'some other default'
const nullValue = response.settings.nullValue ?? "some other default"; // result: 'some other default'
const headerText = response.settings.headerText ?? "Hello, world!"; // result: ''
const animationDuration = response.settings.animationDuration ?? 300; // result: 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // result: false
```

> From <https://github.com/tc39/proposal-nullish-coalescing>

## [Lodash](https://develop.sentry.dev/frontend.md#lodash)

Be sure to not import `lodash` utilities using the default `lodash` package. There is an `eslint` rule to make sure this does not happen. Instead, import the utility directly, e.g. `import isEqual from 'lodash/isEqual';`.

Previously we used a combination of [lodash-webpack-plugin](https://www.npmjs.com/package/lodash-webpack-plugin) and [babel-plugin-lodash](https://github.com/lodash/babel-plugin-lodash) but it is easy to overlook these plugins and configuration when trying to use a new lodash utility (e.g. [this PR](https://github.com/getsentry/sentry/pull/13834)). With `webpack` tree shaking and `eslint` enforcement, we should be able to maintain reasonable bundle sizes.

See [this PR](https://github.com/getsentry/sentry/pull/15521) for more information.

We prefer using optional chaining and nullish coalescing over `get` from `loadash/get`.

## Pages in this section

- [Frontend Design Tenets](https://develop.sentry.dev/frontend/design-tenets.md)
- [Content & Voice](https://develop.sentry.dev/frontend/sentry-voice.md)
- [Using Styled Components](https://develop.sentry.dev/frontend/using-styled-components.md)
- [Component Library](https://develop.sentry.dev/frontend/component-library.md)
- [Dependency Upgrade Policies](https://develop.sentry.dev/frontend/upgrade-policies.md)
- [Using Hooks](https://develop.sentry.dev/frontend/using-hooks.md)
- [Testing](https://develop.sentry.dev/frontend/testing.md)
- [Using React Testing Library](https://develop.sentry.dev/frontend/using-rtl.md)
- [Strict TypeScript Settings Guide](https://develop.sentry.dev/frontend/strict-typescript-settings-guide.md)
- [Network Requests](https://develop.sentry.dev/frontend/network-requests.md)
- [URL State](https://develop.sentry.dev/frontend/url-state.md)
- [Pull Request Previews](https://develop.sentry.dev/frontend/pull-request-previews.md)
- [Working on Getting Started Docs](https://develop.sentry.dev/frontend/working-on-getting-started-docs.md)
