diff --git a/desktop/src/main.js b/desktop/src/main.js
--- a/desktop/src/main.js
+++ b/desktop/src/main.js
@@ -22,7 +22,7 @@
 } from './push-notifications.js';
 
 const isDev = process.env.ENV === 'dev';
-const url = isDev ? 'http://localhost:3000/comm/' : 'https://web.comm.app';
+const url = isDev ? 'http://localhost:3000/webapp/' : 'https://web.comm.app';
 const isMac = process.platform === 'darwin';
 
 const scrollbarCSS = fs.promises.readFile(
diff --git a/docs/dev_environment.md b/docs/dev_environment.md
--- a/docs/dev_environment.md
+++ b/docs/dev_environment.md
@@ -366,16 +366,16 @@
 
 ```
 mkdir -p keyserver/facts
-vim keyserver/facts/commapp_url.json
+vim keyserver/facts/webapp_url.json
 ```
 
-Your `commapp_url.json` file should look like this:
+Your `webapp_url.json` file should look like this:
 
 ```json
 {
   "baseDomain": "http://localhost:3000",
-  "basePath": "/comm/",
-  "baseRoutePath": "/comm/",
+  "basePath": "/webapp/",
+  "baseRoutePath": "/webapp/",
   "https": false,
   "proxy": "none"
 }
@@ -506,7 +506,7 @@
 yarn dev
 ```
 
-You should now be able to load the web app in your web browser at http://localhost/comm/.
+You should now be able to load the web app in your web browser at http://localhost/webapp/.
 
 This command will start two processes. One is `webpack-dev-server`, which will serve the JS files. `webpack-dev-server` also makes sure the website automatically hot-reloads whenever any of the source files change. The other process is `webpack --watch`, which will build the `app.build.cjs` file, as well as rebuilding it whenever any of the source files change. The `app.build.cjs` file is consumed by the Node server in order to pre-render the initial HTML from the web source (“Server-Side Rendering”).
 
@@ -675,7 +675,7 @@
   3.  Finally, you should be able to navigate to Profile → Developer tools in the app and set the address of the local server. It should look something like this:
 
       ```
-      http://w.x.y.z/comm
+      http://w.x.y.z/webapp
       ```
 
       Where `w.x.y.z` is the local IP address you found earlier.
diff --git a/docs/linux_dev_environment.md b/docs/linux_dev_environment.md
--- a/docs/linux_dev_environment.md
+++ b/docs/linux_dev_environment.md
@@ -91,8 +91,9 @@
 
 <VirtualHost *:80>
   ProxyRequests on
-  ProxyPass /comm/ws ws://localhost:3000/ws
-  ProxyPass /comm/ http://localhost:3000/
+  ProxyPass /keyserver/ http://localhost:3000/keyserver/
+  ProxyPass /keyserver/ws ws://localhost:3000/keyserver/ws
+  ProxyPass /webapp/ http://localhost:3000/webapp/
   ProxyPass /commlanding/ http://localhost:3000/commlanding/
 
   RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
diff --git a/docs/nix_keyserver_deployment.md b/docs/nix_keyserver_deployment.md
--- a/docs/nix_keyserver_deployment.md
+++ b/docs/nix_keyserver_deployment.md
@@ -13,7 +13,8 @@
 COMM_DATABASE_PASSWORD=<MariaDB password>
 COMM_JSONCONFIG_secrets_user_credentials='{"username":"<user>","password":"<password>"}'
 COMM_JSONCONFIG_facts_landing_url='{"baseDomain":"http://localhost","basePath":"/commlanding/","baseRoutePath":"/commlanding/","https":false}'
-COMM_JSONCONFIG_facts_commapp_url='{"baseDomain":"http://localhost:3000","basePath":"/comm/","https":false,"baseRoutePath":"/comm/","proxy":"none"}'
+COMM_JSONCONFIG_facts_webapp_url='{"baseDomain":"http://localhost:3000","basePath":"/webapp/","https":false,"baseRoutePath":"/webapp/","proxy":"none"}'
+COMM_JSONCONFIG_facts_keyserver_url='{"baseDomain":"http://localhost:3000","basePath":"/keyserver/","baseRoutePath":"/keyserver/","https":false,"proxy":"none"}'
 COMM_JSONCONFIG_facts_webapp_cors='{"domain": "http://localhost:3000"}'
 
 # Required to connect to production Identity service
@@ -45,7 +46,7 @@
 
 ### URL configuration
 
-- `COMM_JSONCONFIG_facts_commapp_url`: Your keyserver needs to know what its externally-facing URL is in order to construct links. It also needs to know if it’s being proxied to that externally-facing URL, and what the internal route path is.
+- `COMM_JSONCONFIG_facts_keyserver_url`: Your keyserver needs to know what its externally-facing URL is in order to construct links. It also needs to know if it’s being proxied to that externally-facing URL, and what the internal route path is.
   - `baseDomain`: Externally-facing domain. Used for constructing links.
   - `basePath`: Externally-facing path. Used for constructing links.
   - `baseRoutePath`: Internally-facing path. Same as basePath if no proxy. If there’s a proxy, this is the local path (e.g. http://localhost:3000/landing would correspond with /landing/)
diff --git a/docs/nix_web_workflows.md b/docs/nix_web_workflows.md
--- a/docs/nix_web_workflows.md
+++ b/docs/nix_web_workflows.md
@@ -42,7 +42,7 @@
 yarn dev
 ```
 
-You should now be able to load the web app in your web browser at http://localhost:3000/comm/.
+You should now be able to load the web app in your web browser at http://localhost:3000/webapp/.
 
 This command will start two processes. One is `webpack-dev-server`, which will serve the JS files. `webpack-dev-server` also makes sure the website automatically hot-reloads whenever any of the source files change. The other process is `webpack --watch`, which will build the `app.build.cjs` file, as well as rebuilding it whenever any of the source files change. The `app.build.cjs` file is consumed by the Node server in order to pre-render the initial HTML from the web source (“Server-Side Rendering”).
 
diff --git a/keyserver/icons/site.webmanifest b/keyserver/icons/site.webmanifest
--- a/keyserver/icons/site.webmanifest
+++ b/keyserver/icons/site.webmanifest
@@ -3,12 +3,12 @@
   "short_name": "SquadCal",
   "icons": [
     {
-      "src": "/android-chrome-192x192.png",
+      "src": "android-chrome-192x192.png",
       "sizes": "192x192",
       "type": "image/png"
     },
     {
-      "src": "/android-chrome-512x512.png",
+      "src": "android-chrome-512x512.png",
       "sizes": "512x512",
       "type": "image/png"
     }
diff --git a/keyserver/src/creators/report-creator.js b/keyserver/src/creators/report-creator.js
--- a/keyserver/src/creators/report-creator.js
+++ b/keyserver/src/creators/report-creator.js
@@ -29,7 +29,7 @@
 import { handleAsyncPromise } from '../responders/handlers.js';
 import { createBotViewer } from '../session/bots.js';
 import type { Viewer } from '../session/viewer.js';
-import { getAndAssertCommAppURLFacts } from '../utils/urls.js';
+import { getAndAssertKeyserverURLFacts } from '../utils/urls.js';
 
 const { commbot } = bots;
 
@@ -144,7 +144,7 @@
   const { platform, codeVersion } = platformDetails;
   const platformString = codeVersion ? `${platform} v${codeVersion}` : platform;
   if (request.type === reportTypes.ERROR) {
-    const { baseDomain, basePath } = getAndAssertCommAppURLFacts();
+    const { baseDomain, basePath } = getAndAssertKeyserverURLFacts();
     return (
       `${name} got an error :(\n` +
       `using ${platformString}\n` +
diff --git a/keyserver/src/fetchers/upload-fetchers.js b/keyserver/src/fetchers/upload-fetchers.js
--- a/keyserver/src/fetchers/upload-fetchers.js
+++ b/keyserver/src/fetchers/upload-fetchers.js
@@ -18,7 +18,7 @@
 
 import { dbQuery, SQL } from '../database/database.js';
 import type { Viewer } from '../session/viewer.js';
-import { getAndAssertCommAppURLFacts } from '../utils/urls.js';
+import { getAndAssertKeyserverURLFacts } from '../utils/urls.js';
 
 type UploadInfo = {
   content: Buffer,
@@ -104,7 +104,7 @@
 }
 
 function getUploadURL(id: string, secret: string): string {
-  const { baseDomain, basePath } = getAndAssertCommAppURLFacts();
+  const { baseDomain, basePath } = getAndAssertKeyserverURLFacts();
   const uploadPath = `${basePath}upload/${id}/${secret}`;
   if (isDev) {
     const ipV4 = ip.v4.sync() || 'localhost';
diff --git a/keyserver/src/keyserver.js b/keyserver/src/keyserver.js
--- a/keyserver/src/keyserver.js
+++ b/keyserver/src/keyserver.js
@@ -7,12 +7,14 @@
 import cors from 'cors';
 import crypto from 'crypto';
 import express from 'express';
+import type { $Request, $Response } from 'express';
 import expressWs from 'express-ws';
 import os from 'os';
 import qrcode from 'qrcode';
 
 import './cron/cron.js';
 import { qrCodeLinkURL } from 'lib/facts/links.js';
+import { isDev } from 'lib/utils/dev-utils.js';
 
 import { migrate } from './database/migrations.js';
 import { jsonEndpoints } from './endpoints.js';
@@ -44,9 +46,9 @@
 import { getContentSigningKey } from './utils/olm-utils.js';
 import {
   prefetchAllURLFacts,
-  getSquadCalURLFacts,
+  getKeyserverURLFacts,
   getLandingURLFacts,
-  getCommAppURLFacts,
+  getWebAppURLFacts,
   getWebAppCorsConfig,
 } from './utils/urls.js';
 
@@ -60,9 +62,10 @@
     initENSCache(),
   ]);
 
-  const squadCalBaseRoutePath = getSquadCalURLFacts()?.baseRoutePath;
+  const keyserverBaseRoutePath = getKeyserverURLFacts()?.baseRoutePath;
   const landingBaseRoutePath = getLandingURLFacts()?.baseRoutePath;
-  const commAppBaseRoutePath = getCommAppURLFacts()?.baseRoutePath;
+  const webAppURLFacts = getWebAppURLFacts();
+  const webAppBaseRoutePath = webAppURLFacts?.baseRoutePath;
 
   const compiledFolderOptions =
     process.env.NODE_ENV === 'development'
@@ -233,19 +236,28 @@
       server.use(landingBaseRoutePath, landingRouter);
     }
 
-    if (commAppBaseRoutePath) {
+    if (webAppBaseRoutePath) {
       const commAppRouter = express.Router();
       setupAppRouter(commAppRouter);
-      server.use(commAppBaseRoutePath, commAppRouter);
+      server.use(webAppBaseRoutePath, commAppRouter);
     }
 
-    if (squadCalBaseRoutePath) {
+    if (keyserverBaseRoutePath) {
       const squadCalRouter = express.Router();
       if (keyserverCorsOptions) {
         squadCalRouter.use(cors(keyserverCorsOptions));
       }
       setupAppRouter(squadCalRouter);
-      server.use(squadCalBaseRoutePath, squadCalRouter);
+      server.use(keyserverBaseRoutePath, squadCalRouter);
+    }
+
+    if (isDev && webAppURLFacts) {
+      const oldPath = '/comm/';
+      server.all(`${oldPath}*`, (req: $Request, res: $Response) => {
+        const endpoint = req.url.slice(oldPath.length);
+        const newURL = `${webAppURLFacts.baseDomain}${webAppURLFacts.basePath}${endpoint}`;
+        res.redirect(newURL);
+      });
     }
 
     const listenAddress = (() => {
diff --git a/keyserver/src/responders/website-responders.js b/keyserver/src/responders/website-responders.js
--- a/keyserver/src/responders/website-responders.js
+++ b/keyserver/src/responders/website-responders.js
@@ -15,8 +15,9 @@
 
 import { waitForStream } from '../utils/json-stream.js';
 import {
+  getAndAssertKeyserverURLFacts,
   getAppURLFactsFromRequestURL,
-  getCommAppURLFacts,
+  getWebAppURLFacts,
 } from '../utils/urls.js';
 
 const { renderToNodeStream } = ReactDOMServer;
@@ -110,9 +111,18 @@
   }
 }
 
+function stripLastSlash(input: string): string {
+  return input.replace(/\/$/, '');
+}
+
 async function websiteResponder(req: $Request, res: $Response): Promise<void> {
   const { basePath } = getAppURLFactsFromRequestURL(req.originalUrl);
-  const baseURL = basePath.replace(/\/$/, '');
+  const baseURL = stripLastSlash(basePath);
+
+  const keyserverURLFacts = getAndAssertKeyserverURLFacts();
+  const keyserverURL = `${keyserverURLFacts.baseDomain}${stripLastSlash(
+    keyserverURLFacts.basePath,
+  )}`;
 
   const loadingPromise = getWebpackCompiledRootComponentForSSR();
 
@@ -171,6 +181,7 @@
   res.end(html`
     </div>
     <script>
+          var keyserverURL = "${keyserverURL}";
           var baseURL = "${baseURL}";
           var olmFilename = "${olmFilename}";
           var commQueryExecutorFilename = "${commQueryExecutorFilename}";
@@ -202,7 +213,7 @@
     res.end();
     return;
   } else if (detectionResult.os !== 'iOS') {
-    const urlFacts = getCommAppURLFacts();
+    const urlFacts = getWebAppURLFacts();
     const baseDomain = urlFacts?.baseDomain ?? '';
     const basePath = urlFacts?.basePath ?? '/';
     const redirectUrl = `${baseDomain}${basePath}handle/invite/${secret}`;
diff --git a/keyserver/src/utils/urls.js b/keyserver/src/utils/urls.js
--- a/keyserver/src/utils/urls.js
+++ b/keyserver/src/utils/urls.js
@@ -15,8 +15,8 @@
 const validProxies = new Set(['apache', 'none']);
 const sitesObj = Object.freeze({
   a: 'landing',
-  b: 'commapp',
-  c: 'squadcal',
+  b: 'webapp',
+  c: 'keyserver',
 });
 export type Site = $Values<typeof sitesObj>;
 const sites: $ReadOnlyArray<Site> = values(sitesObj);
@@ -46,30 +46,30 @@
   await Promise.all(sites.map(fetchURLFacts));
 }
 
-function getSquadCalURLFacts(): ?AppURLFacts {
-  return cachedURLFacts.get('squadcal');
+function getKeyserverURLFacts(): ?AppURLFacts {
+  return cachedURLFacts.get('keyserver');
 }
 
-function getCommAppURLFacts(): ?AppURLFacts {
-  return cachedURLFacts.get('commapp');
+function getWebAppURLFacts(): ?AppURLFacts {
+  return cachedURLFacts.get('webapp');
 }
 
-function getAndAssertCommAppURLFacts(): AppURLFacts {
-  const urlFacts = getCommAppURLFacts();
-  invariant(urlFacts, 'keyserver/facts/commapp_url.json missing');
+function getAndAssertKeyserverURLFacts(): AppURLFacts {
+  const urlFacts = getKeyserverURLFacts();
+  invariant(urlFacts, 'keyserver/facts/keyserver_url.json missing');
   return urlFacts;
 }
 
 // ESLint doesn't recognize that invariant always throws
 // eslint-disable-next-line consistent-return
 function getAppURLFactsFromRequestURL(url: string): AppURLFacts {
-  const commURLFacts = getCommAppURLFacts();
-  if (commURLFacts && url.startsWith(commURLFacts.baseRoutePath)) {
-    return commURLFacts;
+  const webAppURLFacts = getWebAppURLFacts();
+  if (webAppURLFacts && url.startsWith(webAppURLFacts.baseRoutePath)) {
+    return webAppURLFacts;
   }
-  const squadCalURLFacts = getSquadCalURLFacts();
-  if (squadCalURLFacts) {
-    return squadCalURLFacts;
+  const keyserverURLFacts = getKeyserverURLFacts();
+  if (keyserverURLFacts && url.startsWith(keyserverURLFacts.baseRoutePath)) {
+    return keyserverURLFacts;
   }
   invariant(false, 'request received but no URL facts are present');
 }
@@ -95,9 +95,9 @@
 
 export {
   prefetchAllURLFacts,
-  getSquadCalURLFacts,
-  getCommAppURLFacts,
-  getAndAssertCommAppURLFacts,
+  getKeyserverURLFacts,
+  getWebAppURLFacts,
+  getAndAssertKeyserverURLFacts,
   getLandingURLFacts,
   getAndAssertLandingURLFacts,
   getAppURLFactsFromRequestURL,
diff --git a/native/redux/persist.js b/native/redux/persist.js
--- a/native/redux/persist.js
+++ b/native/redux/persist.js
@@ -93,6 +93,7 @@
 import { commCoreModule } from '../native-modules.js';
 import { defaultDeviceCameraInfo } from '../types/camera.js';
 import { isTaskCancelledError } from '../utils/error-handling.js';
+import { defaultURLPrefix } from '../utils/url-utils.js';
 
 const migrations = {
   [1]: (state: AppState) => ({
@@ -836,6 +837,22 @@
       },
     };
   },
+  [55]: async state =>
+    __DEV__
+      ? {
+          ...state,
+          keyserverStore: {
+            ...state.keyserverStore,
+            keyserverInfos: {
+              ...state.keyserverStore.keyserverInfos,
+              [ashoatKeyserverID]: {
+                ...state.keyserverStore.keyserverInfos[ashoatKeyserverID],
+                urlPrefix: defaultURLPrefix,
+              },
+            },
+          },
+        }
+      : state,
 };
 
 // After migration 31, we'll no longer want to persist `messageStore.messages`
@@ -965,7 +982,7 @@
     'connection',
   ],
   debug: __DEV__,
-  version: 54,
+  version: 55,
   transforms: [
     messageStoreMessagesBlocklistTransform,
     reportStoreTransform,
diff --git a/native/utils/url-utils.js b/native/utils/url-utils.js
--- a/native/utils/url-utils.js
+++ b/native/utils/url-utils.js
@@ -32,7 +32,7 @@
 }
 
 function getDevNodeServerURLFromHostname(hostname: string): string {
-  return `http://${hostname}:3000/comm`;
+  return `http://${hostname}:3000/keyserver`;
 }
 
 function getDevLandingURLFromHostname(hostname: string): string {
diff --git a/web/push-notif/service-worker.js b/web/push-notif/service-worker.js
--- a/web/push-notif/service-worker.js
+++ b/web/push-notif/service-worker.js
@@ -72,7 +72,7 @@
         const url =
           (process.env.NODE_ENV === 'production'
             ? 'https://web.comm.app'
-            : 'http://localhost:3000/comm') + `/chat/thread/${threadID}/`;
+            : 'http://localhost:3000/webapp') + `/chat/thread/${threadID}/`;
         clients.openWindow(url);
       }
     })(),
diff --git a/web/redux/default-state.js b/web/redux/default-state.js
--- a/web/redux/default-state.js
+++ b/web/redux/default-state.js
@@ -4,12 +4,13 @@
 import { defaultCalendarFilters } from 'lib/types/filter-types.js';
 import { defaultConnectionInfo } from 'lib/types/socket-types.js';
 import { defaultGlobalThemeInfo } from 'lib/types/theme-types.js';
-import { isDev } from 'lib/utils/dev-utils.js';
 import { defaultNotifPermissionAlertInfo } from 'lib/utils/push-alerts.js';
 import { ashoatKeyserverID } from 'lib/utils/validation-utils.js';
 
 import type { AppState } from './redux-setup.js';
 
+declare var keyserverURL: string;
+
 const defaultWebState: AppState = Object.freeze({
   navInfo: {
     activeChatThreadID: null,
@@ -79,9 +80,7 @@
       [ashoatKeyserverID]: {
         cookie: null,
         updatesCurrentAsOf: 0,
-        urlPrefix: isDev
-          ? 'http://localhost:3000/comm'
-          : 'https://web.comm.app',
+        urlPrefix: keyserverURL,
         connection: { ...defaultConnectionInfo },
         lastCommunicatedPlatformDetails: null,
       },
diff --git a/web/redux/persist.js b/web/redux/persist.js
--- a/web/redux/persist.js
+++ b/web/redux/persist.js
@@ -38,6 +38,8 @@
 import { isSQLiteSupported } from '../database/utils/db-utils.js';
 import { workerRequestMessageTypes } from '../types/worker-types.js';
 
+declare var keyserverURL: string;
+
 const migrations = {
   [1]: async state => {
     const {
@@ -168,6 +170,19 @@
     ...state,
     globalThemeInfo: defaultGlobalThemeInfo,
   }),
+  [9]: async state => ({
+    ...state,
+    keyserverStore: {
+      ...state.keyserverStore,
+      keyserverInfos: {
+        ...state.keyserverStore.keyserverInfos,
+        [ashoatKeyserverID]: {
+          ...state.keyserverStore.keyserverInfos[ashoatKeyserverID],
+          urlPrefix: keyserverURL,
+        },
+      },
+    },
+  }),
 };
 
 const persistWhitelist = [
@@ -280,7 +295,7 @@
     { debug: isDev },
     migrateStorageToSQLite,
   ): any),
-  version: 8,
+  version: 9,
   transforms: [keyserverStoreTransform],
 };