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.tsxfile - 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>,
},
],
},
]
- Then add your routes to the main route list:
import { routes as myNewAppRoutes } from "Apps/MyNewApp/routes"
export function getAppRoutes() {
return buildAppRoutes([
{
routes: myNewAppRoutes,
},
])
}
- Done! Now, when you visit
http://localhost:5000/new-appyou 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
/Serverfolder --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.