diff --git a/Dockerfile-production b/Dockerfile-production
index 57dc80537a12d810753aa045b6310e32475ec4d4..d8d3986321eecee878db3ae43d28517b73ef17d4 100644
--- a/Dockerfile-production
+++ b/Dockerfile-production
@@ -22,11 +22,17 @@ ARG node_env
 ARG server_protocol
 ARG server_host
 ARG server_port
+ARG client_protocol
+ARG client_host
+ARG client_port
 
 ENV NODE_ENV=$node_env
 ENV SERVER_PROTOCOL=$server_protocol
 ENV SERVER_HOST=$server_host
 ENV SERVER_PORT=$server_port
+ENV CLIENT_PROTOCOL=$client_protocol
+ENV CLIENT_HOST=$client_host
+ENV CLIENT_PORT=$client_port
 
 RUN yarn pubsweet build
 
@@ -42,6 +48,7 @@ COPY --chown=node:node ./config ./config
 COPY --chown=node:node ./public ./public
 COPY --chown=node:node ./scripts ./scripts
 COPY --chown=node:node ./server ./server
+COPY --chown=node:node ./app/storage ./app/storage
 COPY --chown=node:node ./startServer.js .
 
 COPY --from=build /home/node/app/_build/assets ./_build
diff --git a/app/components/AdminPage.js b/app/components/AdminPage.js
index 04fffaed8378a7b9d39ce3a2f86223346fa975e8..cd3548780ecf395f8c82169cda14a8496f95a1bc 100644
--- a/app/components/AdminPage.js
+++ b/app/components/AdminPage.js
@@ -32,8 +32,7 @@ import { Spinner } from './shared'
 import currentRolesVar from '../shared/currentRolesVar'
 import RolesUpdater from './RolesUpdater'
 
-const getParams = routerPath => {
-  const path = '/journal/versions/:version'
+const getParams = ({ routerPath, path }) => {
   return matchPath(routerPath, path).params
 }
 
@@ -55,14 +54,14 @@ const Root = styled.div`
 `
 
 // TODO: Redirect if token expires
-const PrivateRoute = ({ component: Component, ...rest }) => (
+const PrivateRoute = ({ component: Component, redirectLink, ...rest }) => (
   <Route
     {...rest}
     render={props =>
       localStorage.getItem('token') ? (
         <Component {...props} />
       ) : (
-        <Redirect to="/login?next=/journal/dashboard" />
+        <Redirect to={redirectLink} />
       )
     }
   />
@@ -70,6 +69,7 @@ const PrivateRoute = ({ component: Component, ...rest }) => (
 
 PrivateRoute.propTypes = {
   component: PropTypes.func.isRequired,
+  redirectLink: PropTypes.string.isRequired,
 }
 
 const updateStuff = data => {
@@ -113,16 +113,20 @@ const AdminPage = () => {
 
   previousDataRef.current = data
 
+  const urlFrag = journal.metadata.toplevel_urlfragment
   const { pathname } = history.location
   const showLinks = pathname.match(/^\/(submit|manuscript)/g)
   let links = []
-  const formBuilderLink = `/journal/admin/form-builder`
-  const homeLink = '/journal/dashboard'
-  const profileLink = '/journal/profile'
+  const formBuilderLink = `${urlFrag}/admin/form-builder`
+  const homeLink = `${urlFrag}/dashboard`
+  const profileLink = `${urlFrag}/profile`
+  const loginLink = `/login?next=${homeLink}`
+  const path = `${urlFrag}/versions/:version`
+  const redirectLink = `/login?next=${homeLink}`
 
   if (showLinks) {
-    const params = getParams(pathname)
-    const baseLink = `/journal/versions/${params.version}`
+    const params = getParams(pathname, path)
+    const baseLink = `${urlFrag}/versions/${params.version}`
     const submitLink = `${baseLink}/submit`
     const manuscriptLink = `${baseLink}/manuscript`
 
@@ -139,11 +143,10 @@ const AdminPage = () => {
   }
 
   if (currentUser && currentUser.admin) {
-    // links.push({ link: '/journal/admin/teams', name: 'Teams', icon: 'grid' })
     links.push({ link: formBuilderLink, name: 'Forms', icon: 'check-square' })
-    links.push({ link: '/journal/admin/users', name: 'Users', icon: 'users' })
+    links.push({ link: `${urlFrag}/admin/users`, name: 'Users', icon: 'users' })
     links.push({
-      link: '/journal/admin/manuscripts',
+      link: `${urlFrag}/admin/manuscripts`,
       name: 'Manuscripts',
       icon: 'file-text',
     })
@@ -157,55 +160,79 @@ const AdminPage = () => {
     <Root converting={conversion.converting}>
       <Menu
         brand={journal.metadata.name}
-        brandLink="/journal/dashboard"
-        loginLink="/login?next=/journal/dashboard"
+        brandLink={homeLink}
+        className=""
+        loginLink={loginLink}
         navLinkComponents={links}
         notice={notice}
+        profileLink={profileLink}
         user={currentUser}
       />
       <Switch>
-        <PrivateRoute component={Dashboard} exact path="/journal/dashboard" />
+        <PrivateRoute
+          component={Dashboard}
+          exact
+          path={homeLink}
+          redirectLink={redirectLink}
+        />
 
         <PrivateRoute
           component={NewSubmissionPage}
           exact
-          path="/journal/newSubmission"
+          path={`${urlFrag}/newSubmission`}
+          redirectLink={redirectLink}
         />
         <PrivateRoute
           component={SubmitPage}
           exact
-          path="/journal/versions/:version/submit"
+          path={`${urlFrag}/versions/:version/submit`}
+          redirectLink={redirectLink}
         />
         <PrivateRoute
           component={FormBuilderPage}
           exact
-          path="/journal/admin/form-builder"
+          path={`${urlFrag}/admin/form-builder`}
+          redirectLink={redirectLink}
         />
         <PrivateRoute
           component={ManuscriptPage}
           exact
-          path="/journal/versions/:version/manuscript"
+          path={`${urlFrag}/versions/:version/manuscript`}
+          redirectLink={redirectLink}
         />
         <PrivateRoute
           component={ReviewersPage}
           exact
-          path="/journal/versions/:version/reviewers"
+          path={`${urlFrag}/versions/:version/reviewers`}
+          redirectLink={redirectLink}
         />
         <PrivateRoute
           component={ReviewPage}
           exact
-          path="/journal/versions/:version/review"
+          path={`${urlFrag}/versions/:version/review`}
+          redirectLink={redirectLink}
         />
         <PrivateRoute
           component={DecisionPage}
           exact
-          path="/journal/versions/:version/decision"
+          path={`${urlFrag}/versions/:version/decision`}
+          redirectLink={redirectLink}
+        />
+        <PrivateRoute
+          component={Profile}
+          exact
+          path={`${urlFrag}/profile`}
+          redirectLink={redirectLink}
+        />
+        <PrivateRoute
+          component={UsersManager}
+          path={`${urlFrag}/admin/users`}
+          redirectLink={redirectLink}
         />
-        <PrivateRoute component={Profile} exact path="/journal/profile" />
-        <PrivateRoute component={UsersManager} path="/journal/admin/users" />
         <PrivateRoute
           component={Manuscripts}
-          path="/journal/admin/manuscripts"
+          path={`${urlFrag}/admin/manuscripts`}
+          redirectLink={redirectLink}
         />
       </Switch>
       <RolesUpdater />
diff --git a/app/components/Menu.js b/app/components/Menu.js
index 8c39a1ede00ec449d3d2d46ddd631ad32a96fcc6..163b3be5b6867b15dc699659b42592374c408aea 100644
--- a/app/components/Menu.js
+++ b/app/components/Menu.js
@@ -1,31 +1,24 @@
 import React from 'react'
 import styled, { css } from 'styled-components'
-// import PropTypes from 'prop-types'
+import PropTypes from 'prop-types'
 import { th, grid, lighten } from '@pubsweet/ui-toolkit'
 import { Link, useLocation } from 'react-router-dom'
 import { Icon } from '@pubsweet/ui'
-import { UserAvatar } from '../components/component-avatar/src'
+import { UserAvatar } from './component-avatar/src'
 
 const Root = styled.nav`
-  grid-area: menu;
-  padding: ${grid(2)};
-  // display: flex;
-  // align-items: center;
-  // justify-content: space-between;
+  background: linear-gradient(
+    134deg,
+    ${th('colorPrimary')},
+    ${lighten('colorPrimary', 0.3)}
+  );
   border-right: 1px solid ${th('colorFurniture')};
-  // background: ${th('colorPrimary')};
-  // background: linear-gradient(45deg, #191654, #43C6AC);
-  background: linear-gradient(134deg, ${th('colorPrimary')}, ${lighten(
-  'colorPrimary',
-  0.3,
-)});
+  grid-area: menu;
   max-height: 100vh;
+  padding: ${grid(2)};
 `
 
-const Section = styled.div`
-  // display: flex;
-  // align-items: center;
-`
+const Section = styled.div``
 
 // const Logo = styled.span`
 //   // margin: ${grid(2)} 1rem ${grid(2)} 1rem;
@@ -48,33 +41,21 @@ const NavItem = ({ className, link, name, icon }) => (
   </Link>
 )
 
+NavItem.propTypes = {
+  className: PropTypes.string.isRequired,
+  link: PropTypes.string.isRequired,
+  name: PropTypes.string.isRequired,
+  icon: PropTypes.string.isRequired,
+}
+
 const Item = styled(NavItem)`
+  align-items: center;
   border-radius: 10px;
-  padding-left: ${grid(1)};
+  color: ${th('colorTextReverse')};
+  display: flex;
   height: ${grid(5)};
   line-height: ${grid(3)};
 
-  display: flex;
-  align-items: center;
-  color: ${th('colorTextReverse')};
-
-  &:hover {
-    color: ${th('colorText')};
-    stroke: ${th('colorText')};
-    background-color: ${lighten('colorPrimary', 0.5)};
-    svg {
-      stroke: ${th('colorText')};
-
-    }
-  }
-
-  svg {
-    &:hover {
-    }
-    width: 1em;
-    stroke: ${th('colorTextReverse')};
-  }
-
   ${props =>
     props.active &&
     css`
@@ -88,26 +69,36 @@ const Item = styled(NavItem)`
         stroke: ${th('colorText')};
       }
     `}
-  // align-items: center;
-  // display: inline-flex;
-  // margin: calc(${th('gridUnit')} * 3) 1rem calc(${th('gridUnit')} * 3) 0;
+
+  padding-left: ${grid(1)};
+
+  svg {
+    stroke: ${th('colorTextReverse')};
+    width: 1em;
+  }
+
+  &:hover {
+    background-color: ${lighten('colorPrimary', 0.5)};
+    color: ${th('colorText')};
+    stroke: ${th('colorText')};
+
+    svg {
+      stroke: ${th('colorText')};
+    }
+  }
 `
 
 const UserItem = styled(Link)`
-  // height: ${grid(5)};
-  // line-height: ${grid(2)};
   color: ${th('colorTextReverse')};
   display: flex;
   padding-bottom: ${grid(2)};
-  // margin-bottom: ${grid(2)};
-  // border-bottom: 1px solid ${th('colorFurniture')};
 `
 
 const UserInfo = styled.div`
-  margin-left: ${grid(1)};
   display: flex;
-  justify-content: center;
   flex-direction: column;
+  justify-content: center;
+  margin-left: ${grid(1)};
 `
 
 const Menu = ({
@@ -116,6 +107,7 @@ const Menu = ({
   navLinkComponents,
   user,
   notice,
+  profileLink,
 }) => {
   const location = useLocation()
   return (
@@ -123,7 +115,11 @@ const Menu = ({
       <Section>
         {/* TODO: Place this notice (used for offline notification) better */}
         {notice}
-        <UserComponent loginLink={loginLink} user={user} />
+        <UserComponent
+          loginLink={loginLink}
+          profileLink={profileLink}
+          user={user}
+        />
         {navLinkComponents &&
           navLinkComponents.map((navInfo, idx) => (
             <Item
@@ -137,10 +133,10 @@ const Menu = ({
   )
 }
 
-const UserComponent = ({ user, loginLink }) => (
+const UserComponent = ({ user, loginLink, profileLink }) => (
   <Section>
     {user && (
-      <UserItem title="Go to your profile" to="/journal/profile">
+      <UserItem title="Go to your profile" to={profileLink}>
         <UserAvatar isClickable={false} size={48} user={user} />
         <UserInfo>
           <p>{user.defaultIdentity.name || user.username}</p>
@@ -154,13 +150,27 @@ const UserComponent = ({ user, loginLink }) => (
   </Section>
 )
 
-// Menu.propTypes = {
-//   brandLink: PropTypes.string,
-//   brand: PropTypes.node,
-//   loginLink: PropTypes.string,
-//   onLogoutClick: PropTypes.func,
-//   user: PropTypes.object,
-//   navLinkComponents: PropTypes.arrayOf(PropTypes.element),
-// }
+Menu.propTypes = {
+  className: PropTypes.string.isRequired,
+  loginLink: PropTypes.string.isRequired,
+  navLinkComponents: PropTypes.arrayOf(PropTypes.object).isRequired,
+  user: PropTypes.oneOfType([PropTypes.object]),
+  notice: PropTypes.node.isRequired,
+  profileLink: PropTypes.string.isRequired,
+}
+
+Menu.defaultProps = {
+  user: undefined,
+}
+
+UserComponent.propTypes = {
+  user: PropTypes.oneOfType([PropTypes.object]),
+  loginLink: PropTypes.string.isRequired,
+  profileLink: PropTypes.string.isRequired,
+}
+
+UserComponent.defaultProps = {
+  user: undefined,
+}
 
 export default Menu
diff --git a/app/components/component-dashboard/src/components/Dashboard.js b/app/components/component-dashboard/src/components/Dashboard.js
index 11a02f0a3cc40f895840092a1376fc3d3809b185..94c4b691b8f8b0247ab52bc71f799e85a7309f23 100644
--- a/app/components/component-dashboard/src/components/Dashboard.js
+++ b/app/components/component-dashboard/src/components/Dashboard.js
@@ -1,10 +1,10 @@
-/* eslint-disable react/prop-types */
-
 import React from 'react'
 import { useQuery, useMutation } from '@apollo/client'
 import { Button } from '@pubsweet/ui'
 // import Authorize from 'pubsweet-client/src/helpers/Authorize'
 
+import config from 'config'
+import ReactRouterPropTypes from 'react-router-prop-types'
 import queries from '../graphql/queries'
 import mutations from '../graphql/mutations'
 import { Container, Placeholder } from '../style'
@@ -72,11 +72,16 @@ const Dashboard = ({ history, ...props }) => {
     )
     .map(latestVersion)
 
+  const urlFrag = config.journal.metadata.toplevel_urlfragment
+
   return (
     <Container>
       <HeadingWithAction>
         <Heading>Dashboard</Heading>
-        <Button onClick={() => history.push('/journal/newSubmission')} primary>
+        <Button
+          onClick={() => history.push(`${urlFrag}/newSubmission`)}
+          primary
+        >
           + New submission
         </Button>
       </HeadingWithAction>
@@ -145,4 +150,8 @@ const Dashboard = ({ history, ...props }) => {
   )
 }
 
+Dashboard.propTypes = {
+  history: ReactRouterPropTypes.history.isRequired,
+}
+
 export default Dashboard
diff --git a/app/components/component-dashboard/src/components/sections/EditorItem.js b/app/components/component-dashboard/src/components/sections/EditorItem.js
index 827eb9075af4cab1f89093e88cda71ebe2812b10..8dfbb1e03f3eb7d23c3d220c6b576cb26f7760eb 100644
--- a/app/components/component-dashboard/src/components/sections/EditorItem.js
+++ b/app/components/component-dashboard/src/components/sections/EditorItem.js
@@ -1,10 +1,9 @@
-/* eslint-disable react/prop-types */
 /* eslint-disable no-underscore-dangle */
-/* eslint-disable no-param-reassign */
-
 import React from 'react'
 import styled from 'styled-components'
 import { Action, ActionGroup } from '@pubsweet/ui'
+import config from 'config'
+import PropTypes from 'prop-types'
 import { Item, StatusBadge } from '../../style'
 import Meta from '../metadata/Meta'
 import MetadataSubmittedDate from '../metadata/MetadataSubmittedDate'
@@ -27,14 +26,16 @@ const getUserFromTeam = (version, role) => {
   return teams.length ? teams[0].members : []
 }
 
+const urlFrag = config.journal.metadata.toplevel_urlfragment
+
 const EditorItemLinks = ({ version }) => (
   <ActionGroup>
-    <Action to={`/journal/versions/${version.parentId || version.id}/submit`}>
+    <Action to={`${urlFrag}/versions/${version.parentId || version.id}/submit`}>
       Summary Info
     </Action>
     <Action
       data-testid="control-panel"
-      to={`/journal/versions/${version.parentId || version.id}/decision`}
+      to={`${urlFrag}/versions/${version.parentId || version.id}/decision`}
     >
       {version.decision && version.decision.status === 'submitted'
         ? `Decision: ${version.decision.recommendation}`
@@ -43,7 +44,12 @@ const EditorItemLinks = ({ version }) => (
   </ActionGroup>
 )
 
+EditorItemLinks.propTypes = {
+  version: PropTypes.element.isRequired,
+}
+
 const getDeclarationsObject = (version, value) => {
+  // eslint-disable-next-line no-param-reassign
   if (!version.meta) version.meta = {}
   const declarations = version.meta.declarations || {}
 
@@ -95,4 +101,8 @@ const EditorItem = ({ version }) => (
   // </Authorize>
 )
 
+EditorItem.propTypes = {
+  version: PropTypes.element.isRequired,
+}
+
 export default EditorItem
diff --git a/app/components/component-dashboard/src/components/sections/OwnerItem.js b/app/components/component-dashboard/src/components/sections/OwnerItem.js
index 18f36cc3dc06a0744e702efe99ff762b2176b33b..c0ba0332d1b99db9bc1d975ff6ae9e015c954d18 100644
--- a/app/components/component-dashboard/src/components/sections/OwnerItem.js
+++ b/app/components/component-dashboard/src/components/sections/OwnerItem.js
@@ -1,36 +1,42 @@
-/* eslint-disable react/prop-types */
-
 import React from 'react'
 import { Link } from 'react-router-dom'
+import config from 'config'
+import PropTypes from 'prop-types'
 import { Item, StatusBadge } from '../../style'
 import VersionTitle from './VersionTitle'
 import { Icon, ClickableSectionRow } from '../../../../shared'
 import theme from '../../../../../theme'
 
-const OwnerItem = ({ version, journals, deleteManuscript }) => (
-  // Links are based on the original/parent manuscript version
-  <Link
-    key={`version-${version.id}`}
-    to={`/journal/versions/${version.parentId || version.id}/submit`}
-  >
-    <ClickableSectionRow>
-      <Item>
-        <div>
-          {' '}
-          <StatusBadge
-            minimal
-            published={version.published}
-            status={version.status}
-          />
-          <VersionTitle version={version} />
-        </div>
-        <Icon color={theme.colorSecondary} noPadding size={2.5}>
-          chevron_right
-        </Icon>
-        {/* {actions} */}
-      </Item>
-    </ClickableSectionRow>
-  </Link>
-)
+const urlFrag = config.journal.metadata.toplevel_urlfragment
+
+const OwnerItem = ({ version }) => {
+  return (
+    <Link
+      key={`version-${version.id}`}
+      to={`${urlFrag}/versions/${version.parentId || version.id}/submit`}
+    >
+      <ClickableSectionRow>
+        <Item>
+          <div>
+            {' '}
+            <StatusBadge
+              minimal
+              published={version.published}
+              status={version.status}
+            />
+            <VersionTitle version={version} />
+          </div>
+          <Icon color={theme.colorSecondary} noPadding size={2.5}>
+            chevron_right
+          </Icon>
+        </Item>
+      </ClickableSectionRow>
+    </Link>
+  )
+}
+
+OwnerItem.propTypes = {
+  version: PropTypes.oneOfType([PropTypes.object]).isRequired,
+}
 
 export default OwnerItem
diff --git a/app/components/component-dashboard/src/components/sections/ReviewerItem.js b/app/components/component-dashboard/src/components/sections/ReviewerItem.js
index 9b8fd16934d355513f268051fb049ccc409012df..28b34d9ffa13aacc5b8f7c94494e558e195d6f0a 100644
--- a/app/components/component-dashboard/src/components/sections/ReviewerItem.js
+++ b/app/components/component-dashboard/src/components/sections/ReviewerItem.js
@@ -1,9 +1,8 @@
-/* eslint-disable react/prop-types */
-/* eslint-disable no-shadow */
-
 import React from 'react'
 import { Action, ActionGroup } from '@pubsweet/ui'
 // import Authorize from 'pubsweet-client/src/helpers/Authorize'
+import PropTypes from 'prop-types'
+import config from 'config'
 import { Item } from '../../style'
 
 import VersionTitle from './VersionTitle'
@@ -12,9 +11,9 @@ import VersionTitle from './VersionTitle'
 // TODO: only return actions if not accepted or rejected
 // TODO: review id in link
 
-const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => {
+const ReviewerItem = ({ version, currentUser, reviewerRespond }) => {
   const team =
-    (version.teams || []).find(team => team.role === 'reviewer') || {}
+    (version.teams || []).find(team_ => team_.role === 'reviewer') || {}
 
   const currentMember =
     team.members &&
@@ -22,13 +21,15 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => {
 
   const status = currentMember && currentMember.status
 
+  const urlFrag = config.journal.metadata.toplevel_urlfragment
+
   return (
     <Item>
       <VersionTitle version={version} />
 
       {(status === 'accepted' || status === 'completed') && (
         <ActionGroup>
-          <Action to={`/journal/versions/${version.id}/review`}>
+          <Action to={`${urlFrag}/versions/${version.id}/review`}>
             {status === 'completed' ? 'Completed' : 'Do Review'}
           </Action>
         </ActionGroup>
@@ -70,4 +71,10 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => {
   )
 }
 
+ReviewerItem.propTypes = {
+  version: PropTypes.string.isRequired,
+  currentUser: PropTypes.oneOfType([PropTypes.object]).isRequired,
+  reviewerRespond: PropTypes.func.isRequired,
+}
+
 export default ReviewerItem
diff --git a/app/components/component-login/src/Login.jsx b/app/components/component-login/src/Login.jsx
index 75fb49aab29fa83a660a0864a41abca76dfbc85e..9ca02b42bf61afb306f699b6b590c5033e8d7a50 100644
--- a/app/components/component-login/src/Login.jsx
+++ b/app/components/component-login/src/Login.jsx
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
 import { Redirect } from 'react-router-dom'
 import config from 'config'
 import { th, grid, lighten } from '@pubsweet/ui-toolkit'
-import { H1, Button } from '@pubsweet/ui'
+import { Button } from '@pubsweet/ui'
 import styled from 'styled-components'
 
 const getNextUrl = () => {
@@ -57,6 +57,12 @@ const Content = styled.div`
     margin-bottom: ${grid(2)};
   }
   margin-bottom: 1rem;
+  img {
+    max-width: 475px;
+    max-height: 307px;
+    width: auto;
+    height: auto;
+  }
 `
 
 const Centered = styled.div`
@@ -114,10 +120,9 @@ const Login = ({ logo = null, ...props }) => {
           {journalName === 'Aperture' && (
             <img alt="Aperture" src="/public/logo-aperture.png" />
           )}
-          <H1>Login to {journalName}</H1>
-          {journalName} uses ORCID <StyledORCIDIcon /> to identify authors and
-          staff. Login with your ORCID account below or{' '}
-          <a href="https://orcid.org/signin">register at the ORCID website.</a>
+          {journalName === 'Kotahi' && (
+            <img alt="Kotahi" src="/public/logo-kotahi.png" />
+          )}
           <LoginButton
             onClick={() => (window.location = '/auth/orcid')}
             primary
@@ -125,7 +130,6 @@ const Login = ({ logo = null, ...props }) => {
             Login with ORCID
           </LoginButton>
         </Content>
-        <div>Powered by Kotahi</div>
       </Centered>
     </Container>
   )
diff --git a/app/components/component-manuscripts/src/Manuscript.jsx b/app/components/component-manuscripts/src/Manuscript.jsx
index 00e2856a3f11da5ee774f918a666f29b0123a342..529e267e8ca0c5fc47a855c95e25e1f1a46a0b12 100644
--- a/app/components/component-manuscripts/src/Manuscript.jsx
+++ b/app/components/component-manuscripts/src/Manuscript.jsx
@@ -1,7 +1,10 @@
+/* eslint-disable react/jsx-filename-extension */
 import React from 'react'
 import gql from 'graphql-tag'
 import { useMutation } from '@apollo/client'
 // import { Action } from '@pubsweet/ui'
+import config from 'config'
+import PropTypes from 'prop-types'
 import { UserAvatar } from '../../component-avatar/src'
 import {
   Row,
@@ -26,14 +29,17 @@ const DELETE_MANUSCRIPT = gql`
   }
 `
 
+const urlFrag = config.journal.metadata.toplevel_urlfragment
+
 // manuscriptId is always the parent manuscript's id
 const User = ({ manuscriptId, manuscript, submitter }) => {
   const [deleteManuscript] = useMutation(DELETE_MANUSCRIPT, {
-    update(cache, { data: { deleteManuscript } }) {
+    update(cache, { data: { deleteManuscriptId } }) {
       const id = cache.identify({
         __typename: 'Manuscript',
-        id: deleteManuscript,
+        id: deleteManuscriptId,
       })
+
       cache.evict({ id })
     },
   })
@@ -60,10 +66,10 @@ const User = ({ manuscriptId, manuscript, submitter }) => {
         )}
       </Cell>
       <LastCell>
-        <Action to={`/journal/versions/${manuscriptId}/decision`}>
+        <Action to={`${urlFrag}/versions/${manuscriptId}/decision`}>
           Control
         </Action>
-        <Action to={`/journal/versions/${manuscriptId}/manuscript`}>
+        <Action to={`${urlFrag}/versions/${manuscriptId}/manuscript`}>
           View
         </Action>
         <Action
@@ -76,4 +82,10 @@ const User = ({ manuscriptId, manuscript, submitter }) => {
   )
 }
 
+User.propTypes = {
+  manuscriptId: PropTypes.number.isRequired,
+  manuscript: PropTypes.element.isRequired,
+  submitter: PropTypes.element.isRequired,
+}
+
 export default User
diff --git a/app/components/component-review/src/components/ReviewPage.js b/app/components/component-review/src/components/ReviewPage.js
index 9640ac9061164d67798b570ad815487aa3cbed16..8c23b05ef5cb8cfdf26c91a5c7dc92d17630b248 100644
--- a/app/components/component-review/src/components/ReviewPage.js
+++ b/app/components/component-review/src/components/ReviewPage.js
@@ -3,7 +3,9 @@ import { useMutation, useQuery } from '@apollo/client'
 import gql from 'graphql-tag'
 import { Formik } from 'formik'
 // import { cloneDeep } from 'lodash'
-import ReviewLayout from '../components/review/ReviewLayout'
+import config from 'config'
+import ReactRouterPropTypes from 'react-router-prop-types'
+import ReviewLayout from './review/ReviewLayout'
 import { Spinner } from '../../../shared'
 import useCurrentUser from '../../../../hooks/useCurrentUser'
 
@@ -152,7 +154,9 @@ const updateReviewMutationQuery = gql`
   }
 `
 
-export default ({ match, ...props }) => {
+const urlFrag = config.journal.metadata.toplevel__urlfragment
+
+const ReviewPage = ({ match, ...props }) => {
   const currentUser = useCurrentUser()
   const [updateReviewMutation] = useMutation(updateReviewMutationQuery)
   const [completeReview] = useMutation(completeReviewMutation)
@@ -186,13 +190,11 @@ export default ({ match, ...props }) => {
   const { manuscript } = data
   const channelId = manuscript.channels.find(c => c.type === 'editorial').id
 
-  // eslint-disable-next-line
-  const status = (
+  const { status } =
     (
       (manuscript.teams.find(team => team.role === 'reviewer') || {}).status ||
       []
-    ).find(status => status.user === currentUser.id) || {}
-  ).status
+    ).find(statusTemp => statusTemp.user === currentUser.id) || {}
 
   const updateReview = (review, file) => {
     const reviewData = {
@@ -215,13 +217,13 @@ export default ({ match, ...props }) => {
         id: existingReview.current.id || undefined,
         input: reviewData,
       },
-      update: (cache, { data: { updateReview } }) => {
+      update: (cache, { data: { updateReviewTemp } }) => {
         cache.modify({
           id: cache.identify(manuscript),
           fields: {
             reviews(existingReviewRefs = [], { readField }) {
               const newReviewRef = cache.writeFragment({
-                data: updateReview,
+                data: updateReviewTemp,
                 fragment: gql`
                   fragment NewReview on Review {
                     id
@@ -252,7 +254,7 @@ export default ({ match, ...props }) => {
       },
     })
 
-    history.push('/journal/dashboard')
+    history.push(`${urlFrag}/dashboard`)
   }
 
   return (
@@ -296,3 +298,10 @@ export default ({ match, ...props }) => {
     </Formik>
   )
 }
+
+ReviewPage.propTypes = {
+  match: ReactRouterPropTypes.match.isRequired,
+  history: ReactRouterPropTypes.history.isRequired,
+}
+
+export default ReviewPage
diff --git a/app/components/component-review/src/components/decision/DecisionReviews.js b/app/components/component-review/src/components/decision/DecisionReviews.js
index 7994f0be7e33062dfad397d62cb03e1ac400dd52..c4a741dc07f36dd81c8a3155bc009c2ee4a22ae7 100644
--- a/app/components/component-review/src/components/decision/DecisionReviews.js
+++ b/app/components/component-review/src/components/decision/DecisionReviews.js
@@ -1,8 +1,11 @@
 import React from 'react'
 import { Action } from '@pubsweet/ui'
+import PropTypes from 'prop-types'
+import config from 'config'
 import DecisionReview from './DecisionReview'
 import { SectionHeader, SectionRow, Title } from '../style'
 import { SectionContent } from '../../../../shared'
+
 // TODO: read reviewer ordinal and name from project reviewer
 // const { status } =
 //     getUserFromTeam(manuscript, 'reviewer').filter(
@@ -11,14 +14,18 @@ import { SectionContent } from '../../../../shared'
 //   return status
 
 const getCompletedReviews = (manuscript, currentUser) => {
-  const team = manuscript.teams.find(team => team.role === 'reviewer') || {}
+  const team = manuscript.teams.find(team_ => team_.role === 'reviewer') || {}
+
   if (!team.members) {
     return null
   }
+
   const currentMember = team.members.find(m => m.user?.id === currentUser?.id)
   return currentMember && currentMember.status
 }
 
+const urlFrag = config.journal.metadata.toplevel_urlfragment
+
 const DecisionReviews = ({ manuscript }) => (
   <SectionContent>
     <SectionHeader>
@@ -47,11 +54,15 @@ const DecisionReviews = ({ manuscript }) => (
       <SectionRow>No reviews completed yet.</SectionRow>
     )}
     <SectionRow>
-      <Action to={`/journal/versions/${manuscript.id}/reviewers`}>
+      <Action to={`${urlFrag}/versions/${manuscript.id}/reviewers`}>
         Manage Reviewers
       </Action>
     </SectionRow>
   </SectionContent>
 )
 
+DecisionReviews.propTypes = {
+  manuscript: PropTypes.element.isRequired,
+}
+
 export default DecisionReviews
diff --git a/app/components/component-review/src/components/reviewers/Reviewers.js b/app/components/component-review/src/components/reviewers/Reviewers.js
index 5ff9ad3024d1df2a45274171f193e96e9f320f56..95356d1e74f180119649c4c340155d65c28c5f7e 100644
--- a/app/components/component-review/src/components/reviewers/Reviewers.js
+++ b/app/components/component-review/src/components/reviewers/Reviewers.js
@@ -2,6 +2,8 @@ import React from 'react'
 import styled from 'styled-components'
 import { Action, Button } from '@pubsweet/ui'
 import { grid } from '@pubsweet/ui-toolkit'
+import PropTypes from 'prop-types'
+import config from 'config'
 import ReviewerForm from './ReviewerForm'
 import {
   Container,
@@ -19,12 +21,14 @@ import { UserAvatar } from '../../../../component-avatar/src'
 
 const ReviewersList = styled.div`
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(${grid(15)}, 1fr));
   grid-gap: ${grid(2)};
+  grid-template-columns: repeat(auto-fill, minmax(${grid(15)}, 1fr));
 `
 
 const Reviewer = styled.div``
 
+const urlFrag = config.journal.metadata.toplevel_urlfragment
+
 const Reviewers = ({
   journal,
   isValid,
@@ -43,7 +47,7 @@ const Reviewers = ({
       <Heading>Reviewers</Heading>
       <Button
         onClick={() =>
-          history.push(`/journal/versions/${manuscript.id}/decision`)
+          history.push(`${urlFrag}/versions/${manuscript.id}/decision`)
         }
         primary
       >
@@ -73,7 +77,7 @@ const Reviewers = ({
         {reviewers && reviewers.length ? (
           <ReviewersList>
             {reviewers.map(reviewer => (
-              <Reviewer>
+              <Reviewer key={reviewer.id}>
                 <StatusBadge minimal status={reviewer.status} />
                 <UserAvatar key={reviewer.id} user={reviewer.user} />
                 {reviewer.user.defaultIdentity.name}
@@ -102,4 +106,18 @@ const Reviewers = ({
   </Container>
 )
 
+Reviewers.propTypes = {
+  journal: PropTypes.node.isRequired,
+  isValid: PropTypes.node.isRequired,
+  loadOptions: PropTypes.node.isRequired,
+  version: PropTypes.node.isRequired,
+  reviewers: PropTypes.node.isRequired,
+  reviewerUsers: PropTypes.node.isRequired,
+  manuscript: PropTypes.node.isRequired,
+  handleSubmit: PropTypes.node.isRequired,
+  removeReviewer: PropTypes.node.isRequired,
+  teams: PropTypes.node.isRequired,
+  history: PropTypes.node.isRequired,
+}
+
 export default Reviewers
diff --git a/app/components/component-submit/src/components/FormTemplate.js b/app/components/component-submit/src/components/FormTemplate.js
index a4e76b2a108058f70f05219c9c9ff4c6ba0f39f7..2ebd32a672b68d8c7bc747e65c9531cf5fc38ff4 100644
--- a/app/components/component-submit/src/components/FormTemplate.js
+++ b/app/components/component-submit/src/components/FormTemplate.js
@@ -11,6 +11,8 @@ import {
 } from '@pubsweet/ui'
 import * as validators from 'xpub-validators'
 import { AbstractEditor } from 'xpub-edit'
+import PropTypes, { array } from 'prop-types'
+import config from 'config'
 import { Section as Container, Select, FilesUpload } from '../../../shared'
 import { Heading1, Section, Legend, SubNote } from '../style'
 import AuthorsInput from './AuthorsInput'
@@ -74,6 +76,14 @@ elements.AbstractEditor = ({
   />
 )
 
+elements.AbstractEditor.propTypes = {
+  validationStatus: PropTypes.node.isRequired,
+  setTouched: PropTypes.node.isRequired,
+  onChange: PropTypes.func.isRequired,
+  value: PropTypes.node.isRequired,
+  values: PropTypes.node.isRequired,
+}
+
 elements.AuthorsInput = AuthorsInput
 elements.Select = Select
 elements.LinksInput = LinksInput
@@ -81,17 +91,17 @@ elements.LinksInput = LinksInput
 const rejectProps = (obj, keys) =>
   Object.keys(obj)
     .filter(k => !keys.includes(k))
-    .map(k => Object.assign({}, { [k]: obj[k] }))
+    .map(k => ({ [k]: obj[k] }))
     .reduce(
       (res, o) =>
-        Object.values(o).includes('false')
-          ? Object.assign({}, res)
-          : Object.assign(res, o),
+        Object.values(o).includes('false') ? { ...res } : Object.assign(res, o),
       {},
     )
 
+const urlFrag = config.journal.metadata.toplevel_urlfragment
+
 const link = (journal, manuscript) =>
-  String.raw`<a href=/journal/versions/${manuscript.id}/manuscript>view here</a>`
+  String.raw`<a href=${urlFrag}/versions/${manuscript.id}/manuscript>view here</a>`
 
 const createMarkup = encodedHtml => ({
   __html: unescape(encodedHtml),
@@ -109,16 +119,18 @@ const composeValidate = (vld = [], valueField = {}) => value => {
         validatorFn === 'required'
           ? validators[validatorFn](value)
           : validators[validatorFn](valueField[validatorFn])(value)
+
       if (error) {
         errors.push(error)
       }
+
       return validatorFn
     })
   return errors.length > 0 ? errors[0] : undefined
 }
 
-const groupElements = elements => {
-  const grouped = groupBy(elements, n => n.group || 'default')
+const groupElements = els => {
+  const grouped = groupBy(els, n => n.group || 'default')
 
   Object.keys(grouped).forEach(element => {
     grouped[element].sort(
@@ -135,7 +147,7 @@ const groupElements = elements => {
     startArr = startArr
       .slice(0, first)
       .concat([grouped[element]])
-      .concat(startArr.slice(first)) // eslint-disable-line no-use-before-define
+      .concat(startArr.slice(first))
   })
   return startArr
 }
@@ -149,6 +161,7 @@ const renderArray = (elementsComponentArray, onChange) => ({
     const element = elementsComponentArray.find(elv =>
       Object.values(elValues).includes(elv.type),
     )
+
     return (
       <Section
         cssOverrides={JSON.parse(element.sectioncss || '{}')}
@@ -180,6 +193,7 @@ const renderArray = (elementsComponentArray, onChange) => ({
               notesType: element.type,
               content: value,
             }
+
             replace(index, data, `${name}.[${index}]`, true)
             const notes = cloneDeep(values)
             set(notes, `${name}.[${index}]`, data)
@@ -195,18 +209,19 @@ const renderArray = (elementsComponentArray, onChange) => ({
     )
   })
 
-const ElementComponentArray = ({
-  elementsComponentArray,
-  onChange,
-  uploadFile,
-}) => (
+const ElementComponentArray = ({ elementsComponentArray, onChange }) => (
   <FieldArray
     name={elementsComponentArray[0].group}
     render={renderArray(elementsComponentArray, onChange)}
   />
 )
 
-export default ({
+ElementComponentArray.propTypes = {
+  elementsComponentArray: PropTypes.oneOfType([array]).isRequired,
+  onChange: PropTypes.func.isRequired,
+}
+
+const FormTemplate = ({
   form,
   handleSubmit,
   journal,
@@ -299,6 +314,7 @@ export default ({
                     onChange={value => {
                       // TODO: Perhaps split components remove conditions here
                       let val
+
                       if (value.target) {
                         val = value.target.value
                       } else if (value.value) {
@@ -306,6 +322,7 @@ export default ({
                       } else {
                         val = value
                       }
+
                       setFieldValue(element.name, val, true)
                       onChange(val, element.name)
                     }}
@@ -338,7 +355,7 @@ export default ({
             <ElementComponentArray
               elementsComponentArray={element}
               // eslint-disable-next-line
-            key={i}
+              key={i}
               onChange={onChange}
               setFieldValue={setFieldValue}
               setTouched={setTouched}
@@ -384,3 +401,23 @@ export default ({
     </Container>
   )
 }
+
+FormTemplate.propTypes = {
+  form: PropTypes.element.isRequired,
+  handleSubmit: PropTypes.element.isRequired,
+  journal: PropTypes.element.isRequired,
+  toggleConfirming: PropTypes.element.isRequired,
+  confirming: PropTypes.element.isRequired,
+  manuscript: PropTypes.element.isRequired,
+  setTouched: PropTypes.element.isRequired,
+  values: PropTypes.element.isRequired,
+  setFieldValue: PropTypes.element.isRequired,
+  createSupplementaryFile: PropTypes.element.isRequired,
+  onChange: PropTypes.element.isRequired,
+  onSubmit: PropTypes.element.isRequired,
+  submitSubmission: PropTypes.element.isRequired,
+  errors: PropTypes.element.isRequired,
+  validateForm: PropTypes.element.isRequired,
+}
+
+export default FormTemplate
diff --git a/app/components/component-submit/src/components/SubmitPage.js b/app/components/component-submit/src/components/SubmitPage.js
index 9aefa914562851bf8f188c2530c7095468bfc455..99a7b382acf34063a6362b55150f2ac58191846d 100644
--- a/app/components/component-submit/src/components/SubmitPage.js
+++ b/app/components/component-submit/src/components/SubmitPage.js
@@ -1,6 +1,8 @@
 import React, { useState } from 'react'
 import { debounce, cloneDeep, set } from 'lodash'
 import { gql, useQuery, useMutation } from '@apollo/client'
+import config from 'config'
+import ReactRouterPropTypes from 'react-router-prop-types'
 import Submit from './Submit'
 import { Spinner } from '../../../shared'
 import gatherManuscriptVersions from '../../../../shared/manuscript_versions'
@@ -170,11 +172,13 @@ const createNewVersionMutation = gql`
   }
 `
 
+const urlFrag = config.journal.metadata.toplevel_urlfragment
+
 const SubmitPage = ({ match, history, ...props }) => {
   const [confirming, setConfirming] = useState(false)
 
   const toggleConfirming = () => {
-    setConfirming(confirming => !confirming)
+    setConfirming(confirm => !confirm)
   }
 
   const { data, loading, error } = useQuery(query, {
@@ -192,11 +196,11 @@ const SubmitPage = ({ match, history, ...props }) => {
   const manuscript = data?.manuscript
   const form = data?.getFile
 
-  const updateManuscript = (versionId, manuscript) =>
+  const updateManuscript = (versionId, manuscriptInput) =>
     update({
       variables: {
         id: versionId,
-        input: JSON.stringify(manuscript),
+        input: JSON.stringify(manuscriptInput),
       },
     })
 
@@ -211,18 +215,18 @@ const SubmitPage = ({ match, history, ...props }) => {
     return debouncers[path](versionId, input)
   }
 
-  const onSubmit = async (versionId, manuscript) => {
-    const updateManuscript = {
+  const onSubmit = async versionId => {
+    const updateManuscriptInput = {
       status: 'submitted',
     }
 
     await submit({
       variables: {
         id: versionId,
-        input: JSON.stringify(updateManuscript),
+        input: JSON.stringify(updateManuscriptInput),
       },
     })
-    history.push('/journal/dashboard')
+    history.push(`${urlFrag}/dashboard`)
   }
 
   const versions = gatherManuscriptVersions(manuscript)
@@ -242,4 +246,9 @@ const SubmitPage = ({ match, history, ...props }) => {
   )
 }
 
+SubmitPage.propTypes = {
+  history: ReactRouterPropTypes.history.isRequired,
+  match: ReactRouterPropTypes.match.isRequired,
+}
+
 export default SubmitPage
diff --git a/app/components/component-submit/src/upload.js b/app/components/component-submit/src/upload.js
index 7e6f76a3e484f1b1c6de6322dc3548643dc091aa..8e4eb1d50af7f61e6d56d2c8bab1f0701b3e5883 100644
--- a/app/components/component-submit/src/upload.js
+++ b/app/components/component-submit/src/upload.js
@@ -3,6 +3,8 @@ import request from 'pubsweet-client/src/helpers/api'
 import gql from 'graphql-tag'
 import currentRolesVar from '../../../shared/currentRolesVar'
 
+const urlFrag = config.journal.metadata.toplevel_urlfragment
+
 const generateTitle = name =>
   name
     .replace(/[_-]+/g, ' ') // convert hyphens/underscores to space
@@ -184,7 +186,7 @@ const createManuscriptPromise = (
 
 const redirectPromise = (setConversionState, journals, history, data) => {
   setConversionState(() => ({ converting: false, completed: true }))
-  const route = `/journal/versions/${data.createManuscript.id}/submit`
+  const route = `${urlFrag}/versions/${data.createManuscript.id}/submit`
   // redirect after a short delay
   window.setTimeout(() => {
     history.push(route)
diff --git a/app/routes.js b/app/routes.js
index f427249776032b4e3f582529b04a75874a947118..e9a6019e4e806cc24332e41477c776da6da6f368 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -1,6 +1,7 @@
 import React from 'react'
 import { Route, Switch } from 'react-router-dom'
 
+import config from 'config'
 import Login from './components/component-login/src'
 
 import AdminPage from './components/AdminPage'
@@ -13,7 +14,7 @@ import {
 export default (
   <Switch>
     {/* AdminPage has nested routes within */}
-    <Route path="/journal">
+    <Route path={config.journal.metadata.toplevel_urlfragment}>
       <AdminPage />
     </Route>
     <Route component={Login} path="/login" />
diff --git a/config/default.js b/config/default.js
index f6f93f7ed11e9e7d0d9bc2bc71381b355c94bcf5..478b96d0c4be5e9bf9eeb24b2b2836ffe5b2d56c 100644
--- a/config/default.js
+++ b/config/default.js
@@ -136,7 +136,7 @@ module.exports = {
   },
   'pubsweet-client': {
     API_ENDPOINT: '/api',
-    'login-redirect': '/journal/dashboard',
+    'login-redirect': `${journal.metadata.toplevel_urlfragment}/dashboard`,
     theme: process.env.PUBSWEET_THEME,
     baseUrl: deferConfig(cfg => {
       const { protocol, host, port } = cfg['pubsweet-client']
diff --git a/config/journal/metadata.js b/config/journal/metadata.js
index 47074571b820d9123bb8fdec3442a8e024c0a669..27f8f71e190c7291482462f08f08b45523558de7 100644
--- a/config/journal/metadata.js
+++ b/config/journal/metadata.js
@@ -1,4 +1,5 @@
 module.exports = {
   issn: '0000-0001',
-  name: 'Aperture',
+  name: 'Kotahi',
+  toplevel_urlfragment: '/kotahi',
 }
diff --git a/docker-compose.production.yml b/docker-compose.production.yml
index c384fe6f56b66126f1731887e20f564f357f4bda..93fd3222948c874d31b3667daab9264719fbfa2d 100644
--- a/docker-compose.production.yml
+++ b/docker-compose.production.yml
@@ -11,6 +11,9 @@ services:
         - server_protocol=${SERVER_PROTOCOL}
         - server_host=${SERVER_HOST}
         - server_port=${SERVER_PORT}
+        - client_protocol=${CLIENT_PROTOCOL}
+        - client_host=${CLIENT_HOST}
+        - client_port=${CLIENT_PORT}
     ports:
       - ${SERVER_PORT:-3000}:${SERVER_PORT:-3000}
     environment:
diff --git a/docker-compose.yml b/docker-compose.yml
index 661d365e11a8ce522d6e832cc457cf033693db94..a9e21fb04ac462009a8ac256e22b817d7620495d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -21,7 +21,7 @@ services:
       - CLIENT_PORT=${CLIENT_PORT:-4000}
       - SERVER_PROTOCOL=http
       - SERVER_HOST=server
-      - SERVER_PORT=3000
+      - SERVER_PORT=${SERVER_PORT:-3000}
     volumes:
       - ./app:/home/node/app/app
       - ./config:/home/node/app/config
diff --git a/package.json b/package.json
index 3514b8f4038c9374f405255f7e12d96c402bddd1..bd2b0358d8a512a7c2ea5c823d9718aa5a0daccc 100644
--- a/package.json
+++ b/package.json
@@ -86,6 +86,7 @@
     "express": "^4.17.1",
     "faker": "4.1.0",
     "font-awesome": "4.7.0",
+    "formik": "^2.2.6",
     "fs-extra": "4.0.3",
     "got": "11.7.0",
     "graphql": "14.7.0",
@@ -120,6 +121,7 @@
     "react": "16.13.1",
     "react-dom": "16.13.1",
     "react-dropzone": "10.2.2",
+    "react-hot-loader": "^4.13.0",
     "react-html-parser": "2.0.2",
     "react-image": "4.0.3",
     "react-js-pagination": "^3.0.3",
diff --git a/public/logo-kotahi.png b/public/logo-kotahi.png
new file mode 100644
index 0000000000000000000000000000000000000000..369d79aa7c859f414475c8d08bb9bd95f53316c3
Binary files /dev/null and b/public/logo-kotahi.png differ
diff --git a/webpack/plugins.js b/webpack/plugins.js
index 9a2c29675264d7660c14ee29a71d195f44e4c003..c0c78395ce80326b96001043bb93e8b3ef25124a 100644
--- a/webpack/plugins.js
+++ b/webpack/plugins.js
@@ -5,8 +5,8 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
 const CopyWebpackPlugin = require('copy-webpack-plugin')
 const CompressionPlugin = require('compression-webpack-plugin')
 const HtmlWebpackPlugin = require('html-webpack-plugin')
-const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
-  .BundleAnalyzerPlugin
+// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
+//   .BundleAnalyzerPlugin
 
 module.exports = (opts = {}) => {
   const plugins = []
@@ -38,9 +38,15 @@ module.exports = (opts = {}) => {
   }
 
   plugins.push(
-    new webpack.DefinePlugin({
-      'process.env.NODE_ENV': `"${opts.env}"`,
-    }),
+    new webpack.EnvironmentPlugin([
+      'NODE_ENV',
+      'SERVER_PROTOCOL',
+      'SERVER_HOST',
+      'SERVER_PORT',
+      'CLIENT_PROTOCOL',
+      'CLIENT_HOST',
+      'CLIENT_PORT',
+    ]),
   )
 
   // put dynamically required modules into the build
diff --git a/yarn.lock b/yarn.lock
index 0c0d80940dcd75e08b174e394f96c204a91fc73f..1c288bfd52dc4dfb81809f1fd6cfed4de133281c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10946,6 +10946,19 @@ formik@^1.4.2, formik@^2.0.0:
     tiny-warning "^1.0.2"
     tslib "^1.10.0"
 
+formik@^2.2.6:
+  version "2.2.6"
+  resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d"
+  integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA==
+  dependencies:
+    deepmerge "^2.1.1"
+    hoist-non-react-statics "^3.3.0"
+    lodash "^4.17.14"
+    lodash-es "^4.17.14"
+    react-fast-compare "^2.0.1"
+    tiny-warning "^1.0.2"
+    tslib "^1.10.0"
+
 forwarded@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"