diff --git a/packages/app-root/.eslintrc.json b/packages/app-root/.eslintrc.json
new file mode 100644
index 0000000000..5522f86278
--- /dev/null
+++ b/packages/app-root/.eslintrc.json
@@ -0,0 +1,20 @@
+{
+  "extends": [
+    "next/core-web-vitals",
+    "plugin:jsx-a11y/recommended",
+    "plugin:@next/next/recommended"
+  ],
+  "rules": {
+    "consistent-return": "error"
+  },
+  "overrides": [
+      {
+        "files": [
+          "src/**/*.stories.js"
+        ],
+        "rules": {
+          "import/no-anonymous-default-export": "off"
+        }
+      }
+    ]
+}
diff --git a/packages/app-root/next.config.mjs b/packages/app-root/next.config.mjs
index a2a6e99052..026a6646f5 100644
--- a/packages/app-root/next.config.mjs
+++ b/packages/app-root/next.config.mjs
@@ -4,6 +4,10 @@ const bundleAnalyzer = withBundleAnalyzer({
 	enabled: process.env.ANALYZE === 'true',
 })
 
-const nextConfig = {}
+const nextConfig = {
+  experimental: {
+    optimizePackageImports: ['@zooniverse/react-components', 'grommet', 'grommet-icons'],
+  }
+}
 
 export default bundleAnalyzer(nextConfig)
diff --git a/packages/app-root/package.json b/packages/app-root/package.json
index 7f7bc2adea..370d1c37db 100644
--- a/packages/app-root/package.json
+++ b/packages/app-root/package.json
@@ -6,9 +6,9 @@
   "version": "0.1.0",
   "private": true,
   "scripts": {
-    "dev": "next dev",
+    "dev": "APP_ENV=${APP_ENV:-development} PANOPTES_ENV=${PANOPTES_ENV:-staging} node server/server.js",
     "build": "next build",
-    "start": "next start",
+    "start": "NODE_ENV=${NODE_ENV:-production} PANOPTES_ENV=${PANOPTES_ENV:-production} node server/server.js",
     "lint": "next lint"
   },
   "type": "module",
@@ -17,17 +17,24 @@
     "@zooniverse/grommet-theme": "~3.1.1",
     "@zooniverse/panoptes-js": "~0.4.1",
     "@zooniverse/react-components": "~1.6.1",
+    "express": "~4.18.2",
     "grommet": "~2.33.2",
     "grommet-icons": "~4.11.0",
+    "newrelic": "~11.2.0",
     "next": "~13.5.5",
+    "panoptes-client": "~5.5.6",
     "react": "~18.2.0",
     "react-dom": "~18.2.0",
-    "styled-components": "~5.3.10"
+    "styled-components": "~5.3.10",
+    "swr": "~2.2.4"
   },
   "engines": {
     "node": ">=20.5"
   },
   "devDependencies": {
-    "@next/bundle-analyzer": "~13.5.4"
+    "@next/bundle-analyzer": "~13.5.5",
+    "eslint-config-next": "~13.5.5",
+    "eslint-plugin-jsx-a11y": "~6.7.0",
+    "selfsigned": "~2.1.1"
   }
 }
diff --git a/packages/app-root/server/server.js b/packages/app-root/server/server.js
new file mode 100644
index 0000000000..e69efba623
--- /dev/null
+++ b/packages/app-root/server/server.js
@@ -0,0 +1,53 @@
+if (process.env.NEWRELIC_LICENSE_KEY) {
+  await import('newrelic')
+}
+
+import express from 'express'
+import next from 'next'
+
+const port = parseInt(process.env.PORT, 10) || 3000
+const dev = process.env.NODE_ENV !== 'production'
+
+const APP_ENV = process.env.APP_ENV || 'development'
+
+const hostnames = {
+  development: 'local.zooniverse.org',
+  branch: 'fe-project-branch.preview.zooniverse.org',
+  staging: 'frontend.preview.zooniverse.org',
+  production : 'www.zooniverse.org'
+}
+const hostname = hostnames[APP_ENV]
+
+const app = next({ dev, hostname, port })
+const handle = app.getRequestHandler()
+
+app.prepare().then(async () => {
+  const server = express()
+
+  server.get('*', (req, res) => {
+    return handle(req, res)
+  })
+
+  let selfsigned
+  try {
+    selfsigned = await import('selfsigned')
+  } catch (error) {
+    console.error(error)
+  }
+  if (APP_ENV === 'development' && selfsigned) {
+    const https = await import('https')
+
+    const attrs = [{ name: 'commonName', value: hostname }];
+    const { cert, private: key } = selfsigned.generate(attrs, { days: 365 })
+    return https.createServer({ cert, key }, server)
+      .listen(port, err => {
+        if (err) throw err
+        console.log(`> Ready on https://${hostname}:${port}`)
+      })
+  } else {
+    return server.listen(port, err => {
+      if (err) throw err
+      console.log(`> Ready on http://${hostname}:${port}`)
+    })
+  }
+})
diff --git a/packages/app-root/src/app/about/page.js b/packages/app-root/src/app/about/page.js
index 197074ea7e..ad09698f91 100644
--- a/packages/app-root/src/app/about/page.js
+++ b/packages/app-root/src/app/about/page.js
@@ -1,5 +1,8 @@
 export default function AboutPage() {
   return (
+    <header aria-label='About the Zooniverse'>
+      <p>This is the section header.</p>
+    </header>
     <div>
       <p>This is lib-content-pages</p>
     </div>
diff --git a/packages/app-root/src/app/projects/page.js b/packages/app-root/src/app/projects/page.js
index f4575f3406..f7fc4be5b3 100644
--- a/packages/app-root/src/app/projects/page.js
+++ b/packages/app-root/src/app/projects/page.js
@@ -1,5 +1,8 @@
 export default function ProjectPage() {
   return (
+    <header aria-label='Project header'>
+      <p>This is the project header.</p>
+    </header>
     <div>
       <p>This is lib-project</p>
     </div>
diff --git a/packages/app-root/src/components/PageContextProviders.js b/packages/app-root/src/components/PageContextProviders.js
new file mode 100644
index 0000000000..a0dfffddc7
--- /dev/null
+++ b/packages/app-root/src/components/PageContextProviders.js
@@ -0,0 +1,42 @@
+'use client'
+
+import zooTheme from '@zooniverse/grommet-theme'
+import { Grommet } from 'grommet'
+import { createGlobalStyle } from 'styled-components'
+
+import { PanoptesAuthContext } from '../contexts'
+import { useAdminMode, usePanoptesUser } from '../hooks'
+
+const GlobalStyle = createGlobalStyle`
+  body {
+    margin: 0;
+  }
+`
+
+/**
+  Context for every page:
+  - global page styles.
+  - Zooniverse Grommet theme.
+  - Panoptes auth (user account and admin mode.)
+*/
+export default function PageContextProviders({ children }) {
+  const { data: user, error, isLoading } = usePanoptesUser()
+  const { adminMode, toggleAdmin } = useAdminMode(user)
+  const authContext = { adminMode, error, isLoading, toggleAdmin, user }
+
+  return (
+    <PanoptesAuthContext.Provider value={authContext}>
+      <GlobalStyle />
+      <Grommet
+        background={{
+          dark: 'dark-1',
+          light: 'light-1'
+        }}
+        theme={zooTheme}
+      >
+        {children}
+      </Grommet>
+    </PanoptesAuthContext.Provider>
+  )
+
+}
\ No newline at end of file
diff --git a/packages/app-root/src/components/PageFooter.js b/packages/app-root/src/components/PageFooter.js
new file mode 100644
index 0000000000..934dd72a4c
--- /dev/null
+++ b/packages/app-root/src/components/PageFooter.js
@@ -0,0 +1,15 @@
+'use client'
+import { AdminCheckbox, ZooFooter } from '@zooniverse/react-components'
+import { useContext } from 'react'
+
+import { PanoptesAuthContext } from '../contexts'
+
+export default function PageFooter() {
+  const { adminMode, toggleAdmin, user } = useContext(PanoptesAuthContext)
+
+  return (
+    <ZooFooter
+      adminContainer={user?.admin ? <AdminCheckbox onChange={toggleAdmin} checked={adminMode} /> : null}
+    />
+  )
+}
\ No newline at end of file
diff --git a/packages/app-root/src/components/PageHeader.js b/packages/app-root/src/components/PageHeader.js
new file mode 100644
index 0000000000..82e1119a13
--- /dev/null
+++ b/packages/app-root/src/components/PageHeader.js
@@ -0,0 +1,27 @@
+'use client'
+import { ZooHeader } from '@zooniverse/react-components'
+import { useContext } from 'react'
+
+import {
+  useUnreadMessages,
+  useUnreadNotifications
+} from '../hooks'
+
+import { PanoptesAuthContext } from '../contexts'
+
+export default function PageHeader() {
+  const { adminMode, user } = useContext(PanoptesAuthContext)
+  const { data: unreadMessages }= useUnreadMessages(user)
+  const { data: unreadNotifications }= useUnreadNotifications(user)
+
+  return (
+    <header aria-label='Zooniverse site header'>
+      <ZooHeader
+        isAdmin={adminMode}
+        unreadMessages={unreadMessages}
+        unreadNotifications={unreadNotifications}
+        user={user}
+      />
+    </header>
+  )
+}
\ No newline at end of file
diff --git a/packages/app-root/src/components/RootLayout.js b/packages/app-root/src/components/RootLayout.js
index 6d7d3e1471..8a30856aa6 100644
--- a/packages/app-root/src/components/RootLayout.js
+++ b/packages/app-root/src/components/RootLayout.js
@@ -1,37 +1,15 @@
-'use client'
-/**
- * Note that all child components are now client components.
- * If we want children of RootLayout to be server components
- * a ZooHeaderContainer and ZooFooterContainer could be created instead.
- */
-
-import { createGlobalStyle } from 'styled-components'
-import { Grommet } from 'grommet'
-import zooTheme from '@zooniverse/grommet-theme'
-import ZooHeader from '@zooniverse/react-components/ZooHeader'
-import ZooFooter from '@zooniverse/react-components/ZooFooter'
-
-const GlobalStyle = createGlobalStyle`
-  body {
-    margin: 0;
-  }
-`
+import PageContextProviders from './PageContextProviders.js'
+import PageHeader from './PageHeader.js'
+import PageFooter from './PageFooter.js'
 
 export default function RootLayout({ children }) {
   return (
     <body>
-      <GlobalStyle />
-      <Grommet
-        background={{
-          dark: 'dark-1',
-          light: 'light-1'
-        }}
-        theme={zooTheme}
-      >
-        <ZooHeader />
+      <PageContextProviders>
+        <PageHeader />
         {children}
-        <ZooFooter />
-      </Grommet>
+        <PageFooter />
+      </PageContextProviders>
     </body>
   )
 }
diff --git a/packages/app-root/src/contexts/PanoptesAuthContext.js b/packages/app-root/src/contexts/PanoptesAuthContext.js
new file mode 100644
index 0000000000..c39b6e57f6
--- /dev/null
+++ b/packages/app-root/src/contexts/PanoptesAuthContext.js
@@ -0,0 +1,5 @@
+import { createContext } from 'react'
+
+const PanoptesAuthContext = createContext({})
+
+export default PanoptesAuthContext
diff --git a/packages/app-root/src/contexts/index.js b/packages/app-root/src/contexts/index.js
new file mode 100644
index 0000000000..5c21818a37
--- /dev/null
+++ b/packages/app-root/src/contexts/index.js
@@ -0,0 +1 @@
+export { default as PanoptesAuthContext } from './PanoptesAuthContext.js'
diff --git a/packages/app-root/src/helpers/fetchPanoptesUser.js b/packages/app-root/src/helpers/fetchPanoptesUser.js
new file mode 100644
index 0000000000..caa89f3676
--- /dev/null
+++ b/packages/app-root/src/helpers/fetchPanoptesUser.js
@@ -0,0 +1,38 @@
+import auth from 'panoptes-client/lib/auth'
+import { auth as authHelpers } from '@zooniverse/panoptes-js'
+
+/**
+  Get a Panoptes user from a Panoptes JSON Web Token (JWT), if we have one, or from
+  the Panoptes API otherwise.
+*/
+export default async function fetchPanoptesUser({ user: storedUser }) {
+  try {
+    const jwt = await auth.checkBearerToken()
+    /*
+      `crypto.subtle` is needed to decrypt the Panoptes JWT.
+      It will only exist for https:// URLs.
+    */
+    const isSecure = crypto?.subtle
+    if (jwt && isSecure) {
+      /*
+        avatar_src isn't encoded in the Panoptes JWT, so we need to add it.
+        https://github.com/zooniverse/panoptes/issues/4217
+      */
+      const { user, error } = await authHelpers.decodeJWT(jwt)
+      if (user) {
+        const { admin, display_name, id, login } = user
+        return {
+          avatar_src: storedUser.avatar_src,
+          ...user
+        }
+      }
+      if (error) {
+        throw error
+      }
+    }
+  } catch (error) {
+    console.log(error)
+  }
+  const { admin, avatar_src, display_name, id, login } = await auth.checkCurrent()
+  return { admin, avatar_src, display_name, id, login }
+}
diff --git a/packages/app-root/src/helpers/index.js b/packages/app-root/src/helpers/index.js
new file mode 100644
index 0000000000..025ab766d9
--- /dev/null
+++ b/packages/app-root/src/helpers/index.js
@@ -0,0 +1 @@
+export { default as fetchPanoptesUser } from './fetchPanoptesUser.js'
diff --git a/packages/app-root/src/hooks/index.js b/packages/app-root/src/hooks/index.js
new file mode 100644
index 0000000000..e6419b719c
--- /dev/null
+++ b/packages/app-root/src/hooks/index.js
@@ -0,0 +1,4 @@
+export { default as useAdminMode } from './useAdminMode.js'
+export { default as usePanoptesUser } from './usePanoptesUser.js'
+export { default as useUnreadMessages } from './useUnreadMessages.js'
+export { default as useUnreadNotifications } from './useUnreadNotifications.js'
diff --git a/packages/app-root/src/hooks/useAdminMode.js b/packages/app-root/src/hooks/useAdminMode.js
new file mode 100644
index 0000000000..627cc485d3
--- /dev/null
+++ b/packages/app-root/src/hooks/useAdminMode.js
@@ -0,0 +1,44 @@
+import { useEffect, useState } from 'react'
+
+const isBrowser = typeof window !== 'undefined'
+const localStorage = isBrowser ? window.localStorage : null
+const storedAdminFlag = !!localStorage?.getItem('adminFlag')
+const adminBorderImage = 'repeating-linear-gradient(45deg,#000,#000 25px,#ff0 25px,#ff0 50px) 5'
+
+export default function useAdminMode(user) {
+  const [adminState, setAdminState] = useState(storedAdminFlag)
+  const adminMode = user?.admin && adminState
+
+  useEffect(function onUserChange() {
+    const isAdmin = user?.admin
+    if (isAdmin) {
+      const adminFlag = !!localStorage?.getItem('adminFlag')
+      setAdminState(adminFlag)
+    } else {
+      localStorage?.removeItem('adminFlag')
+    }
+  }, [user?.admin])
+
+  useEffect(function onAdminChange() {
+    if (adminMode) {
+      document.body.style.border = '5px solid'
+      document.body.style.borderImage = adminBorderImage
+    }
+    return () => {
+      document.body.style.border = ''
+      document.body.style.borderImage = ''
+    }
+  }, [adminMode])
+
+  function toggleAdmin() {
+    let newAdminState = !adminState
+    setAdminState(newAdminState)
+    if (newAdminState) {
+      localStorage?.setItem('adminFlag', true)
+    } else {
+      localStorage?.removeItem('adminFlag')
+    }
+  }
+  
+  return { adminMode, toggleAdmin }
+}
\ No newline at end of file
diff --git a/packages/app-root/src/hooks/usePanoptesUser.js b/packages/app-root/src/hooks/usePanoptesUser.js
new file mode 100644
index 0000000000..4203507a95
--- /dev/null
+++ b/packages/app-root/src/hooks/usePanoptesUser.js
@@ -0,0 +1,66 @@
+import auth from 'panoptes-client/lib/auth'
+import { useEffect } from 'react'
+import useSWR from 'swr'
+
+import { fetchPanoptesUser } from '../helpers'
+
+const isBrowser = typeof window !== 'undefined'
+
+const SWROptions = {
+  revalidateIfStale: true,
+  revalidateOnMount: true,
+  revalidateOnFocus: true,
+  revalidateOnReconnect: true,
+  refreshInterval: 0
+}
+
+if (isBrowser) {
+  auth.checkCurrent()
+}
+
+const localStorage = isBrowser ? window.localStorage : null
+const storedUserJSON = localStorage?.getItem('panoptes-user')
+let storedUser = storedUserJSON && JSON.parse(storedUserJSON)
+/*
+  Null users crash the ZooHeader component.
+  Set them to undefined for now.
+*/
+if (storedUser === null) {
+  storedUser = undefined
+}
+
+export default function usePanoptesUser() {
+  const key = {
+    user: storedUser,
+    endpoint: '/me'
+  }
+
+  /*
+   `useSWR` here will always return the same stale user object.
+    See https://github.com/zooniverse/panoptes-javascript-client/issues/207
+  */
+  const { data, error, isLoading } = useSWR(key, fetchPanoptesUser, SWROptions)
+  if (data) {
+    storedUser = data
+  }
+
+  useEffect(function subscribeToAuthChanges() {
+    auth.listen('change', auth.checkCurrent)
+
+    return function () {
+      auth.stopListening('change', auth.checkCurrent)
+    }
+  }, [])
+
+  useEffect(function persistUserInStorage() {
+    if (data) {
+      localStorage?.setItem('panoptes-user', JSON.stringify(data))
+    }
+
+    return () => {
+      localStorage?.removeItem('panoptes-user')
+    }
+  }, [data])
+
+  return { data: storedUser, error, isLoading }
+}
diff --git a/packages/app-root/src/hooks/useUnreadMessages.js b/packages/app-root/src/hooks/useUnreadMessages.js
new file mode 100644
index 0000000000..b92af111f3
--- /dev/null
+++ b/packages/app-root/src/hooks/useUnreadMessages.js
@@ -0,0 +1,55 @@
+import { talkAPI } from '@zooniverse/panoptes-js'
+import auth from 'panoptes-client/lib/auth'
+import useSWR from 'swr'
+
+const SWROptions = {
+  revalidateIfStale: true,
+  revalidateOnMount: true,
+  revalidateOnFocus: true,
+  revalidateOnReconnect: true,
+  refreshInterval: 0
+}
+
+async function fetchUnreadMessageCount({ endpoint = '/conversations' }) {
+  const token = await auth.checkBearerToken()
+  const authorization = `Bearer ${token}`
+  if (!authorization) return undefined
+
+  let unreadConversationsIds = []
+
+  async function getConversations (page = 1) {
+    const query = {
+      unread: true,
+      page: page
+    }
+
+    const response = await talkAPI.get(endpoint, query, { authorization })
+    const { meta, conversations } = response?.body || {}
+
+    if (conversations && conversations.length) {
+      unreadConversationsIds = unreadConversationsIds.concat(
+        conversations.map(conversation => conversation.id)
+      )
+    }
+
+    if (meta?.next_page) {
+      return getConversations(meta.next_page)
+    }
+
+    return unreadConversationsIds
+  }
+
+  await getConversations(1)
+  return unreadConversationsIds.length
+}
+
+export default function useUnreadMessages(user) {
+  let key = null
+  if (user) {
+    key = {
+      user,
+      endpoint: '/conversations'
+    }
+  }
+  return useSWR(key, fetchUnreadMessageCount, SWROptions)
+}
diff --git a/packages/app-root/src/hooks/useUnreadNotifications.js b/packages/app-root/src/hooks/useUnreadNotifications.js
new file mode 100644
index 0000000000..ab098060da
--- /dev/null
+++ b/packages/app-root/src/hooks/useUnreadNotifications.js
@@ -0,0 +1,36 @@
+import { talkAPI } from '@zooniverse/panoptes-js'
+import auth from 'panoptes-client/lib/auth'
+import useSWR from 'swr'
+
+const SWROptions = {
+  revalidateIfStale: true,
+  revalidateOnMount: true,
+  revalidateOnFocus: true,
+  revalidateOnReconnect: true,
+  refreshInterval: 0
+}
+
+async function fetchUnreadNotificationsCount({ endpoint = '/notifications' }) {
+  const token = await auth.checkBearerToken()
+  const authorization = `Bearer ${token}`
+  if (!authorization) return undefined
+
+  const query = {
+    delivered: false,
+    page_size: 1
+  }
+
+  const response = await talkAPI.get(endpoint, query, { authorization })
+  return response?.body?.meta?.notifications?.count
+}
+
+export default function useUnreadNotifications(user) {
+  let key = null
+  if (user) {
+    key = {
+      user,
+      endpoint: '/notifications'
+    }
+  }
+  return useSWR(key, fetchUnreadNotificationsCount, SWROptions)
+}
diff --git a/yarn.lock b/yarn.lock
index a6918894d0..6edc4d73fd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1983,7 +1983,7 @@
   resolved "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz"
   integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
 
-"@grpc/grpc-js@^1.9.4":
+"@grpc/grpc-js@^1.8.10", "@grpc/grpc-js@^1.9.4":
   version "1.9.7"
   resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.7.tgz#7d0e29bc162287bee2523901c9bc9320d8402397"
   integrity sha512-yMaA/cIsRhGzW3ymCNpdlPcInXcovztlgu/rirThj2b87u3RzWUszliOqZ/pldy7yhmJPS8uwog+kZSTa4A0PQ==
@@ -2260,12 +2260,12 @@
     pump "^3.0.0"
     tar-fs "^2.1.1"
 
-"@newrelic/aws-sdk@^7.0.2":
+"@newrelic/aws-sdk@^7.0.0", "@newrelic/aws-sdk@^7.0.2":
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/@newrelic/aws-sdk/-/aws-sdk-7.0.2.tgz#e93f1796c89be8323a75f3d7ec45b1bdd5a29292"
   integrity sha512-nT19hzId0MbjR3v1ks5YetvNfrwIEgMfeai+T2pQkuWkjCsYm3z+OybLOYMCN66gueqOOqGTq60qhM4dFu5s5w==
 
-"@newrelic/koa@^8.0.1":
+"@newrelic/koa@^8.0.0", "@newrelic/koa@^8.0.1":
   version "8.0.1"
   resolved "https://registry.yarnpkg.com/@newrelic/koa/-/koa-8.0.1.tgz#26c1c6a69b15ad4b64a148b6be537ec2ca734206"
   integrity sha512-GyeZGKPllpUu6gWXRwVP/FlvE9+tU2lOprRiTdoXNM8jdVGL02IfHnvAzrIANoZoUdf3+Vev8NNeCup2Eojcvg==
@@ -2307,12 +2307,17 @@
     uuid "^9.0.0"
     ws "^7.5.9"
 
+"@newrelic/superagent@^7.0.0":
+  version "7.0.0"
+  resolved "https://registry.npmjs.org/@newrelic/superagent/-/superagent-7.0.0.tgz"
+  integrity sha512-fNB4NC+pJYYrFZRLcXaTb4Z7XFEfHi7fVQ3O9Qh10m/9CBM2W+Qc/6yyK9M1liRfgUGo5NOILRdjA23SS7720A==
+
 "@newrelic/superagent@^7.0.1":
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/@newrelic/superagent/-/superagent-7.0.1.tgz#8d5bb92579cf0b291e1298f480c4939a3d70ec09"
   integrity sha512-QZlW0VxHSVOXcMAtlkg+Mth0Nz3vFku8rfzTEmoI/pXcckHXGEYuiVUhhboCTD3xTKVgnZRUp9BWF6SOggGUSw==
 
-"@next/bundle-analyzer@~13.5.4":
+"@next/bundle-analyzer@~13.5.5":
   version "13.5.5"
   resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-13.5.5.tgz#301edbfe05ff910ce3c9ba691ea2a6257e0032cb"
   integrity sha512-v69BJm8ONM/e6l39Ao0ar8TwZyFnhI5s6id8LGayNq/3JaqkbzW97bIcBkTI0H9RiX3zZNIiaIyMgdKcbJqvsw==
@@ -9254,7 +9259,7 @@ execa@^5.0.0, execa@^5.1.1:
     signal-exit "^3.0.3"
     strip-final-newline "^2.0.0"
 
-express@^4.17.1, express@^4.17.3:
+express@^4.17.1, express@^4.17.3, express@~4.18.2:
   version "4.18.2"
   resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz"
   integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
@@ -12972,6 +12977,33 @@ newrelic@^11.0.0, newrelic@~11.4.0:
     "@newrelic/native-metrics" "^10.0.0"
     "@prisma/prisma-fmt-wasm" "^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085"
 
+newrelic@~11.2.0:
+  version "11.2.0"
+  resolved "https://registry.yarnpkg.com/newrelic/-/newrelic-11.2.0.tgz#eded32c7b7d97cae36e45396e8926a201e441793"
+  integrity sha512-gkt6c5nphsKTRBmKd0H12xELwnhdV9Xph5CL8IXT7nj0C1gL/xxfuTrwj6g+JqDvVz983iNNfdfXBEhIUJC4nQ==
+  dependencies:
+    "@grpc/grpc-js" "^1.8.10"
+    "@grpc/proto-loader" "^0.7.5"
+    "@newrelic/aws-sdk" "^7.0.0"
+    "@newrelic/koa" "^8.0.0"
+    "@newrelic/security-agent" "0.3.0"
+    "@newrelic/superagent" "^7.0.0"
+    "@tyriar/fibonacci-heap" "^2.0.7"
+    concat-stream "^2.0.0"
+    https-proxy-agent "^7.0.1"
+    import-in-the-middle "^1.4.2"
+    json-bigint "^1.0.0"
+    json-stringify-safe "^5.0.0"
+    module-details-from-path "^1.0.3"
+    readable-stream "^3.6.1"
+    require-in-the-middle "^7.2.0"
+    semver "^7.5.2"
+    winston-transport "^4.5.0"
+  optionalDependencies:
+    "@contrast/fn-inspect" "^3.3.0"
+    "@newrelic/native-metrics" "^10.0.0"
+    "@prisma/prisma-fmt-wasm" "^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085"
+
 next-absolute-url@~1.2.2:
   version "1.2.2"
   resolved "https://registry.npmjs.org/next-absolute-url/-/next-absolute-url-1.2.2.tgz"
@@ -13814,7 +13846,7 @@ pako@~1.0.5:
   resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz"
   integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
 
-panoptes-client@~5.5.1:
+panoptes-client@~5.5.1, panoptes-client@~5.5.6:
   version "5.5.6"
   resolved "https://registry.npmjs.org/panoptes-client/-/panoptes-client-5.5.6.tgz"
   integrity sha512-TvcKIS7ggrfuh8dA+9ORgHw53lWCoRjyIZWtSjOGOlIIBB2QF+3dPEgyDUltQ6Kpo49TV7PRAYNczJI3GGn07w==
@@ -15590,6 +15622,13 @@ selfsigned@^2.1.1, selfsigned@~2.4.1:
     "@types/node-forge" "^1.3.0"
     node-forge "^1"
 
+selfsigned@~2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.2.tgz#9f9a4b0d472a5f29f892eb52358056c61a7387e3"
+  integrity sha512-xc6ZKMc9owNuU3uEPuW45RnSPylOlRK5Brj8oWf/2+BQV2gD1c+/eJaHFCcTG8w8kRkEfb5mzn/yIpie6gJ1tA==
+  dependencies:
+    node-forge "^1"
+
 "semver@2 || 3 || 4 || 5", semver@^5.6.0:
   version "5.7.2"
   resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz"
@@ -16401,7 +16440,7 @@ swc-loader@^0.2.3:
   resolved "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.3.tgz"
   integrity sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==
 
-swr@~2.2.0:
+swr@~2.2.0, swr@~2.2.4:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07"
   integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==