adding_new_app.md

March 6, 2026 ยท View on GitHub

Adding a new App to Force

Revised 1/16/2024

To add a new app to force we can leverage our React-based SSR router.

  • Create a new folder in the apps directory Apps/MyAppName
  • Create a new routes.tsx file
  • Add some routes:
// routes.tsx
export const routes: RouteProps[] = [
  {
    path: "/new-app",
    Component: props => {
      return (
        <div>
          <h1>Hello new app!</h1>
          <nav>
            <RouterLink to="/new-app/artworks">
              {" "}
              - Navigate to Artworks
            </RouterLink>
            <RouterLink to="/new-app/artists">
              {" "}
              - Navigate to Artists
            </RouterLink>
          </nav>
          <div>{props.children}</div>
        </div>
      )
    },
    children: [
      {
        path: "artworks",
        Component: () => <div>Artworks list...</div>,
      },
      {
        path: "artists",
        Component: () => <div>Artists list...</div>,
      },
    ],
  },
]
import { routes as myNewAppRoutes } from "Apps/MyNewApp/routes"

export function getAppRoutes() {
  return buildAppRoutes([
    {
      routes: myNewAppRoutes,
    },
  ])
}
  • Done! Now, when you visit http://localhost:5000/new-app you should see your newly created content above.
  • An example app is available to view and use as a starting point in Example App

Fetching data with Relay

Most apps rely on GraphQL data at Artsy. Thankfully, our router makes it easy to access it!

Lets fetch an artist name and render it on the page:

export const myNewAppRoutes: AppRouteConfig[] = [
  {
    path: "/new-app",
    // How long should we cache the query on the server? Default is .env GRAPHQL_CACHE_TTL
    serverCacheTTL: 1000,
    // Relay config to always force a fetch; use very deliberately! We want
    // caching, but some pages should never be cached.
    cacheConfig: {
      force: true
    }
    Component: props => {
      return (
        <div>
          <h1>Hello new {props.artist.name}</h1>
        </div>
      )
    },
    query: graphql`
      query myNewAppRoutesQuery {
        artist(id: "andy-warhol") {
          name
        }
      }
    `,
  },
]

Error handling for @principalField routes

Error handling for @principalField queries is automatic. The router (buildAppRoutes) injects a default error render function on any route that has a query but no custom render. This means simple routes need no extra configuration.

For routes that need a custom render function (e.g., canonical slug redirects), you must handle errors yourself. Two options:

Use canonicalSlugRedirect() which handles errors internally:

import { canonicalSlugRedirect } from "System/Router/Utils/canonicalSlugRedirect"

render: canonicalSlugRedirect({
  entityName: "artist",
  paramName: "slug",
  basePath: "/my-route",
}),

Or call renderRouteError() at the top of a custom render function:

import { renderRouteError } from "System/Router/Utils/renderRouteError"

render: ({ Component, props, error }) => {
  if (error) return renderRouteError(error)

  if (!(Component && props)) return undefined

  // Your custom logic here...
  return <Component {...props} />
},

See error-handling.md for the full error handling architecture.

Advanced Setup

For most apps we don't need more than the above to get a new route and SSR rendering out of the box. However, sometimes we need additional server-side functionality, be it redirects or something else that interacts directly with the request / response cycle. For that, we can leverage express.js middleware.

Extending the example above, lets add a server-side redirect if the user isn't logged in:

  • Create a co-located /Server folder -- Apps/MyNewApp/Server
  • Create a file inside of it called myNewAppRedirect.tsx
  • Add the following to our new file:
export function myNewAppRedirect({ req, res, next }) {
  if (!res.locals.sd.CURRENT_USER) {
    return res.redirect(
      `/login?redirectTo=${encodeURIComponent(req.originalUrl)}`,
    )
  }
}
  • Then update the route:
const routes = [
  {
    path: '/foo',
    Component: () => <div>hello how are you?</div>
    onServerSideRender: myNewAppRedirect
  }
]

Similar to the above, there's also a client-side hook that one can execute:

const routes = [
  {
    path: '/foo',
    Component: () => <div>hello how are you?</div>
    onClientSideRender: () => console.log('hey there!')
  }
]

Testing Gotchas

When testing the top level route of your new app with React Testing Library make sure to jest.mock("v2/Components/MetaTags") or wrap your component tree in <MockBoot>, otherwise things wont render in jest. On first glance RTL seems to break when it encounters react-head (how we handle meta tags and the like). Mocking will replace the component in the tree; using <MockBoot> will wrap things in the required React context.

Learn More

See the best practices docs or Example App for more info.