diff --git a/client/src/about/index.tsx b/client/src/about/index.tsx
index 92055d8c82f2..3c5ae1b1785a 100644
--- a/client/src/about/index.tsx
+++ b/client/src/about/index.tsx
@@ -7,7 +7,7 @@ import { WRITER_MODE } from "../env";
 import { Prose } from "../document/ingredients/prose";
 
 import "./index.scss";
-import "./custom-elements";
+import "../lit/about";
 import { useGleanClick } from "../telemetry/glean-context";
 import { ABOUT } from "../telemetry/constants";
 
diff --git a/client/src/community/index.tsx b/client/src/community/index.tsx
index e2290d123a52..37af5b68c086 100644
--- a/client/src/community/index.tsx
+++ b/client/src/community/index.tsx
@@ -11,7 +11,7 @@ export function Community(appProps: HydrationData<any, AboutDoc>) {
   const doc = useAboutDoc(appProps);
 
   useEffect(() => {
-    import("./contributor-list");
+    import("../lit/community/contributor-list");
   }, []);
 
   return (
diff --git a/client/src/curriculum/landing.tsx b/client/src/curriculum/landing.tsx
index bc3cbd5c96bd..9b60e447a671 100644
--- a/client/src/curriculum/landing.tsx
+++ b/client/src/curriculum/landing.tsx
@@ -20,7 +20,7 @@ import scrimBg from "../assets/curriculum/landing-scrim.png";
 import { useGleanClick } from "../telemetry/glean-context";
 import { CURRICULUM } from "../telemetry/constants";
 
-const ScrimInline = lazy(() => import("./scrim-inline"));
+const ScrimInline = lazy(() => import("../lit/curriculum/scrim-inline"));
 
 export function CurriculumLanding(appProps: HydrationData<any, CurriculumDoc>) {
   const doc = useCurriculumDoc(appProps as CurriculumData);
diff --git a/client/src/curriculum/module.tsx b/client/src/curriculum/module.tsx
index 07cf874ad478..3804dedfbf2b 100644
--- a/client/src/curriculum/module.tsx
+++ b/client/src/curriculum/module.tsx
@@ -14,7 +14,7 @@ export function CurriculumModule(props: HydrationData<any, CurriculumDoc>) {
   const doc = useCurriculumDoc(props as CurriculumData);
 
   useEffect(() => {
-    import("./scrim-inline");
+    import("../lit/curriculum/scrim-inline");
   }, []);
 
   return (
diff --git a/client/src/about/custom-elements.js b/client/src/lit/about.js
similarity index 100%
rename from client/src/about/custom-elements.js
rename to client/src/lit/about.js
diff --git a/client/src/community/contributor-list.js b/client/src/lit/community/contributor-list.js
similarity index 100%
rename from client/src/community/contributor-list.js
rename to client/src/lit/community/contributor-list.js
diff --git a/client/src/community/contributor-list.scss b/client/src/lit/community/contributor-list.scss
similarity index 99%
rename from client/src/community/contributor-list.scss
rename to client/src/lit/community/contributor-list.scss
index 232d5e7a6949..e2d6b20c81d6 100644
--- a/client/src/community/contributor-list.scss
+++ b/client/src/lit/community/contributor-list.scss
@@ -1,4 +1,4 @@
-@use "../ui/vars" as *;
+@use "../../ui/vars" as *;
 
 * {
   box-sizing: border-box;
diff --git a/client/src/community/types.d.ts b/client/src/lit/community/types.d.ts
similarity index 100%
rename from client/src/community/types.d.ts
rename to client/src/lit/community/types.d.ts
diff --git a/client/src/curriculum/scrim-inline.global.css b/client/src/lit/curriculum/scrim-inline.global.css
similarity index 65%
rename from client/src/curriculum/scrim-inline.global.css
rename to client/src/lit/curriculum/scrim-inline.global.css
index ac55e7fa316a..716a41ddcec5 100644
--- a/client/src/curriculum/scrim-inline.global.css
+++ b/client/src/lit/curriculum/scrim-inline.global.css
@@ -4,5 +4,5 @@
   font-display: block;
   src:
     local("BarlowCondensed-SemiBold"),
-    url("../assets/fonts/BarlowCondensed-SemiBold.woff2") format("woff2");
+    url("../../assets/fonts/BarlowCondensed-SemiBold.woff2") format("woff2");
 }
diff --git a/client/src/curriculum/scrim-inline.js b/client/src/lit/curriculum/scrim-inline.js
similarity index 96%
rename from client/src/curriculum/scrim-inline.js
rename to client/src/lit/curriculum/scrim-inline.js
index 894e7d529599..2340ba55746c 100644
--- a/client/src/curriculum/scrim-inline.js
+++ b/client/src/lit/curriculum/scrim-inline.js
@@ -4,13 +4,13 @@ import { styleMap } from "lit/directives/style-map.js";
 import { ifDefined } from "lit/directives/if-defined.js";
 import { createComponent } from "@lit/react";
 import React from "react";
-import { CURRICULUM } from "../telemetry/constants.ts";
+import { CURRICULUM } from "../../telemetry/constants.ts";
 
 import "./scrim-inline.global.css";
 import styles from "./scrim-inline.scss?css" with { type: "css" };
-import playSvg from "../assets/curriculum/scrim-play.svg?raw";
+import playSvg from "../../assets/curriculum/scrim-play.svg?raw";
 
-class ScrimInline extends LitElement {
+export class ScrimInline extends LitElement {
   static properties = {
     url: { type: String },
     img: { type: String },
diff --git a/client/src/curriculum/scrim-inline.scss b/client/src/lit/curriculum/scrim-inline.scss
similarity index 88%
rename from client/src/curriculum/scrim-inline.scss
rename to client/src/lit/curriculum/scrim-inline.scss
index 6618fb42ac2f..ef70efbe675f 100644
--- a/client/src/curriculum/scrim-inline.scss
+++ b/client/src/lit/curriculum/scrim-inline.scss
@@ -88,16 +88,16 @@ dialog {
 
 .toggle {
   &.enter {
-    mask-image: url("../assets/icons/fullscreen-enter.svg");
+    mask-image: url("../../assets/icons/fullscreen-enter.svg");
   }
 
   &.exit {
-    mask-image: url("../assets/icons/cancel.svg");
+    mask-image: url("../../assets/icons/cancel.svg");
   }
 }
 
 .external {
-  mask-image: url("../assets/icons/external.svg");
+  mask-image: url("../../assets/icons/external.svg");
   mask-size: 75%;
 }
 
@@ -109,9 +109,9 @@ dialog {
 
 .background {
   background-color: #453c78;
-  background-image: url("../assets/curriculum/scrimba-logo.svg"),
-    url("../assets/curriculum/scrim-hexagons.svg"),
-    url("../assets/curriculum/scrim-bg.png");
+  background-image: url("../../assets/curriculum/scrimba-logo.svg"),
+    url("../../assets/curriculum/scrim-hexagons.svg"),
+    url("../../assets/curriculum/scrim-bg.png");
   background-position:
     1.5em 1.5em,
     right,
diff --git a/client/src/lit/globals.d.ts b/client/src/lit/globals.d.ts
new file mode 100644
index 000000000000..51d5a73c8baf
--- /dev/null
+++ b/client/src/lit/globals.d.ts
@@ -0,0 +1,14 @@
+import { MDNImageHistory, TeamMember } from "./about";
+import { ContributorList } from "./community/contributor-list";
+import { ScrimInline } from "./curriculum/scrim-inline";
+import { PlayConsole } from "./play/console";
+
+declare global {
+  interface HTMLElementTagNameMap {
+    "mdn-image-history": MDNImageHistory;
+    "team-member": TeamMember;
+    "contributor-list": ContributorList;
+    "scrim-inline": ScrimInline;
+    "play-console": PlayConsole;
+  }
+}
diff --git a/client/src/lit/modules.d.ts b/client/src/lit/modules.d.ts
new file mode 100644
index 000000000000..c5fc9cfff13e
--- /dev/null
+++ b/client/src/lit/modules.d.ts
@@ -0,0 +1,12 @@
+// once https://github.com/microsoft/TypeScript/issues/46135 is fixed
+// we'll be able to do something like:
+// declare module '*' with {type: 'css'} {
+declare module "*?css" {
+  const sheet: CSSStyleSheet;
+  export default sheet;
+}
+
+declare module "*?raw" {
+  const src: string;
+  export default src;
+}
diff --git a/client/src/playground/console.js b/client/src/lit/play/console.js
similarity index 100%
rename from client/src/playground/console.js
rename to client/src/lit/play/console.js
diff --git a/client/src/playground/console.scss b/client/src/lit/play/console.scss
similarity index 100%
rename from client/src/playground/console.scss
rename to client/src/lit/play/console.scss
diff --git a/client/src/playground/types.d.ts b/client/src/lit/play/types.d.ts
similarity index 100%
rename from client/src/playground/types.d.ts
rename to client/src/lit/play/types.d.ts
diff --git a/client/src/lit/tsconfig.json b/client/src/lit/tsconfig.json
new file mode 100644
index 000000000000..a3708bfbf670
--- /dev/null
+++ b/client/src/lit/tsconfig.json
@@ -0,0 +1,30 @@
+{
+  "compilerOptions": {
+    "allowJs": true,
+    "checkJs": true,
+    "strict": true,
+    "noUncheckedIndexedAccess": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noImplicitReturns": true,
+    "noEmit": true,
+    "noFallthroughCasesInSwitch": true,
+    "allowImportingTsExtensions": true,
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "isolatedModules": true,
+    "jsx": "react-jsx",
+    "lib": ["dom", "dom.iterable", "es2021"],
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "target": "ES2020",
+    "plugins": [
+      {
+        "name": "ts-lit-plugin",
+        "strict": true
+      }
+    ]
+  },
+  "include": ["**/*"]
+}
diff --git a/client/src/playground/index.tsx b/client/src/playground/index.tsx
index 67d54f06ab42..c023cccf3811 100644
--- a/client/src/playground/index.tsx
+++ b/client/src/playground/index.tsx
@@ -21,10 +21,10 @@ import {
 import "./index.scss";
 import { PLAYGROUND_BASE_HOST } from "../env";
 import { FlagForm, ShareForm } from "./forms";
-import { ReactPlayConsole } from "./console";
+import { ReactPlayConsole } from "../lit/play/console";
 import { useGleanClick } from "../telemetry/glean-context";
 import { PLAYGROUND } from "../telemetry/constants";
-import type { VConsole } from "./types";
+import type { VConsole } from "../lit/play/types";
 
 const HTML_DEFAULT = "";
 const CSS_DEFAULT = "";
diff --git a/client/src/plus/collections/frequently-viewed.tsx b/client/src/plus/collections/frequently-viewed.tsx
index 63352fbd817d..6d329e351a7a 100644
--- a/client/src/plus/collections/frequently-viewed.tsx
+++ b/client/src/plus/collections/frequently-viewed.tsx
@@ -78,8 +78,13 @@ const sortByVisitsThenTimestampsDesc = (
   //'Each timestamp represents one visit. The first is the most recent visit.
   if (first.timestamps.length > second.timestamps.length) return -1;
   if (first.timestamps.length < second.timestamps.length) return 1;
-  if (first.timestamps[0] < second.timestamps[0]) return 1;
-  if (first.timestamps[0] > second.timestamps[0]) return -1;
+  if (
+    typeof first.timestamps[0] !== "undefined" &&
+    typeof second.timestamps[0] !== "undefined"
+  ) {
+    if (first.timestamps[0] < second.timestamps[0]) return 1;
+    if (first.timestamps[0] > second.timestamps[0]) return -1;
+  }
   return 0;
 };
 
@@ -98,7 +103,7 @@ function getNextFrequentlyViewedSerial(
 export function useFrequentlyViewed(
   limit: number = 0,
   offset: number = 10,
-  setEnd?: (bool) => void
+  setEnd?: (bool: boolean) => void
 ): FrequentlyViewedCollection {
   const [collection, setCollection] = useState<FrequentlyViewedCollection>({
     article_count: 0,
@@ -130,7 +135,7 @@ export function useFrequentlyViewed(
         ...c,
         article_count: freqViewed.length,
         items: paged,
-        updated_at: freqViewed[0]
+        updated_at: freqViewed[0]?.timestamps[0]
           ? new Date(freqViewed[0].timestamps[0]).toISOString()
           : new Date().toISOString(),
       };
@@ -208,12 +213,12 @@ export function useIncrementFrequentlyViewed(doc: Doc | undefined) {
 
     let frequentlyViewed = getFrequentlyViewed();
 
-    const index = frequentlyViewed.findIndex(
+    const foundEntry = frequentlyViewed.find(
       (entry) => entry.url === doc.mdn_url
     );
 
-    if (index !== -1) {
-      frequentlyViewed[index].timestamps.unshift(Date.now());
+    if (foundEntry) {
+      foundEntry.timestamps.unshift(Date.now());
     } else {
       const newEntry: FrequentlyViewedEntry = {
         serial: getNextFrequentlyViewedSerial(frequentlyViewed),
@@ -236,7 +241,7 @@ export function useIncrementFrequentlyViewed(doc: Doc | undefined) {
   }, [user?.isAuthenticated, doc]);
 }
 
-const filterFrequentlyViewed = (frequentlyViewed) => {
+const filterFrequentlyViewed = (frequentlyViewed: FrequentlyViewedEntry[]) => {
   //1. Remove timestamps older than 30 days.
   //2. Filter all values with no remaining timestamps
   return frequentlyViewed
diff --git a/client/src/react-app.d.ts b/client/src/react-app.d.ts
index 23b53c872707..20002b3e2305 100644
--- a/client/src/react-app.d.ts
+++ b/client/src/react-app.d.ts
@@ -78,39 +78,3 @@ declare module "*.svg" {
   const src: string;
   export default src;
 }
-
-declare module "*?raw" {
-  const src: string;
-  export default src;
-}
-
-// once https://github.com/microsoft/TypeScript/issues/46135 is fixed
-// we'll be able to do something like:
-// declare module '*' with {type: 'css'} {
-declare module "*?css" {
-  const sheet: CSSStyleSheet;
-  export default sheet;
-}
-
-// once https://github.com/microsoft/TypeScript/issues/46135 is fixed
-// we'll be able to do something like:
-// declare module '*' with {type: 'css'} {
-declare module "*?css" {
-  const sheet: CSSStyleSheet;
-  export default sheet;
-}
-
-declare module "*.module.css" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
-
-declare module "*.module.scss" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
-
-declare module "*.module.sass" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
diff --git a/client/src/settings/mdn-worker.tsx b/client/src/settings/mdn-worker.tsx
index c2f58230aa4c..602b845f424b 100644
--- a/client/src/settings/mdn-worker.tsx
+++ b/client/src/settings/mdn-worker.tsx
@@ -35,7 +35,7 @@ export class MDNWorker {
     this.timeout = setTimeout(() => this.autoUpdate(), 60 * 60 * 1000);
   }
 
-  messageHandler(event) {
+  messageHandler(event: MessageEvent) {
     switch (event.data.type) {
       case "pong":
         console.log("pong");
diff --git a/client/src/telemetry/glean-context.tsx b/client/src/telemetry/glean-context.tsx
index 599d77f09260..caa8acb3b30e 100644
--- a/client/src/telemetry/glean-context.tsx
+++ b/client/src/telemetry/glean-context.tsx
@@ -78,8 +78,8 @@ function glean(): GleanAnalytics {
   if (typeof window === "undefined" || !GLEAN_ENABLED) {
     //SSR return noop.
     return {
-      page: (page: PageProps) => () => {},
-      click: (element: ElementClickedProps) => {},
+      page: () => () => {},
+      click: () => {},
     };
   }
   const userIsOptedOut = document.cookie
@@ -114,8 +114,13 @@ function glean(): GleanAnalytics {
       if (page.isBaseline) {
         pageMetric.isBaseline.set(page.isBaseline);
       }
-      for (const param in page.utm) {
-        pageMetric.utm[param].set(page.utm[param]);
+      for (const param of Object.keys(page.utm) as Array<
+        keyof typeof page.utm
+      >) {
+        const value = page.utm[param];
+        if (value) {
+          pageMetric.utm[param]?.set(value);
+        }
       }
       pageMetric.httpStatus.set(page.httpStatus);
       if (page.geo) {
diff --git a/client/src/user-context.tsx b/client/src/user-context.tsx
index d59b371995c2..109b6abe8857 100644
--- a/client/src/user-context.tsx
+++ b/client/src/user-context.tsx
@@ -121,8 +121,8 @@ function getSessionStorageData() {
     }
   } catch (error: any) {
     console.warn("sessionStorage.getItem didn't work", error.toString());
-    return undefined;
   }
+  return undefined;
 }
 
 export function cleanupUserData() {
@@ -184,7 +184,7 @@ function setNoPlacementFlag(noAds: boolean) {
 export function UserDataProvider(props: { children: React.ReactNode }) {
   const { data, error, isLoading, mutate } = useSWR<User | null, Error | null>(
     DISABLE_AUTH ? null : "/api/v1/whoami",
-    async (url) => {
+    async (url: string) => {
       const response = await fetch(url);
       if (!response.ok) {
         removeSessionStorageData();
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 262b9c47b077..d82d4baedb94 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -19,13 +19,7 @@
     "skipLibCheck": true,
     "sourceMap": true,
     "strict": true,
-    "target": "ES2020",
-    "plugins": [
-      {
-        "name": "ts-lit-plugin",
-        "strict": true
-      }
-    ]
+    "target": "ES2020"
   },
   "include": ["src"]
 }
diff --git a/server/react-app.d.ts b/server/react-app.d.ts
deleted file mode 100644
index 8206136fee8c..000000000000
--- a/server/react-app.d.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-declare namespace NodeJS {
-  interface ProcessEnv {
-    readonly NODE_ENV: "development" | "production" | "test";
-  }
-}
-
-declare module "*.avif" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.bmp" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.gif" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.jpg" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.jpeg" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.mp3" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.mp4" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.ogg" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.png" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.webm" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.webp" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.woff2" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.svg" {
-  import * as React from "react";
-
-  export const ReactComponent: React.FunctionComponent<
-    React.SVGProps<SVGSVGElement> & { title?: string }
-  >;
-
-  const src: string;
-  export default src;
-}
-
-declare module "*.module.css" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
-
-declare module "*.module.scss" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
-
-declare module "*.module.sass" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
diff --git a/ssr/modules.d.ts b/ssr/modules.d.ts
new file mode 100644
index 000000000000..9f24738429e9
--- /dev/null
+++ b/ssr/modules.d.ts
@@ -0,0 +1,9 @@
+declare module "*?inline" {
+  const source: string;
+  export default source;
+}
+
+declare module "*?public" {
+  const src: string;
+  export default src;
+}
diff --git a/ssr/react-app.d.ts b/ssr/react-app.d.ts
deleted file mode 100644
index 4822f4144835..000000000000
--- a/ssr/react-app.d.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-declare namespace NodeJS {
-  interface ProcessEnv {
-    readonly NODE_ENV: "development" | "production" | "test";
-  }
-}
-
-declare module "*.avif" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.bmp" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.gif" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.jpg" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.jpeg" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.mp3" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.mp4" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.ogg" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.png" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.webm" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.webp" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.woff2" {
-  const src: string;
-  export default src;
-}
-
-declare module "*.svg" {
-  import * as React from "react";
-
-  export const ReactComponent: React.FunctionComponent<
-    React.SVGProps<SVGSVGElement> & { title?: string }
-  >;
-
-  const src: string;
-  export default src;
-}
-
-declare module "*?raw" {
-  const src: string;
-  export default src;
-}
-
-// once https://github.com/microsoft/TypeScript/issues/46135 is fixed
-// we'll be able to do something like:
-// declare module '*' with {type: 'css'} {
-declare module "*?css" {
-  const sheet: CSSStyleSheet;
-  export default sheet;
-}
-
-// once https://github.com/microsoft/TypeScript/issues/46135 is fixed
-// we'll be able to do something like:
-// declare module '*' with {type: 'css'} {
-declare module "*?css" {
-  const sheet: CSSStyleSheet;
-  export default sheet;
-}
-
-declare module "*.module.css" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
-
-declare module "*.module.scss" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
-
-declare module "*.module.sass" {
-  const classes: { readonly [key: string]: string };
-  export default classes;
-}
-
-declare module "*?inline" {
-  const source: string;
-  export default source;
-}
-
-declare module "*?public" {
-  const src: string;
-  export default src;
-}
diff --git a/ssr/tsconfig.json b/ssr/tsconfig.json
index bf2c0bd5252e..e86b2592693b 100644
--- a/ssr/tsconfig.json
+++ b/ssr/tsconfig.json
@@ -1,5 +1,5 @@
 {
   "extends": "../client/tsconfig.json",
-  "include": ["."],
+  "include": [".", "../client/**/*.d.ts"],
   "exclude": ["dist"]
 }