diff --git a/create-app.ts b/create-app.ts
index 8d6ce9c5089062f2c451927fe283344743a29bc6..8cb6f287dce6a125b12ced307870e02f0070c658 100644
--- a/create-app.ts
+++ b/create-app.ts
@@ -43,6 +43,7 @@ export async function createApp({
   postInstallAction,
   dataSource,
   tools,
+  observability,
 }: InstallAppArgs): Promise<void> {
   const root = path.resolve(appPath);
 
@@ -90,6 +91,7 @@ export async function createApp({
     postInstallAction,
     dataSource,
     tools,
+    observability,
   };
 
   if (frontend) {
@@ -143,5 +145,15 @@ export async function createApp({
       `file://${root}/README.md`,
     )} and learn how to get started.`,
   );
+
+  if (args.observability === "opentelemetry") {
+    console.log(
+      `\n${yellow("Observability")}: Visit the ${terminalLink(
+        "documentation",
+        "https://traceloop.com/docs/openllmetry/integrations",
+      )} to set up the environment variables and start seeing execution traces.`,
+    );
+  }
+
   console.log();
 }
diff --git a/helpers/types.ts b/helpers/types.ts
index 76be9af317babc463c508aec831e3df415df930f..0d359423278473b91af2a19170d246b8cb79e5fd 100644
--- a/helpers/types.ts
+++ b/helpers/types.ts
@@ -16,6 +16,7 @@ export type TemplateDataSource = {
   config: TemplateDataSourceConfig;
 };
 export type TemplateDataSourceType = "none" | "file" | "folder" | "web";
+export type TemplateObservability = "none" | "opentelemetry";
 // Config for both file and folder
 export type FileSourceConfig = {
   path?: string;
@@ -49,4 +50,5 @@ export interface InstallTemplateArgs {
   externalPort?: number;
   postInstallAction?: TemplatePostInstallAction;
   tools?: Tool[];
+  observability?: TemplateObservability;
 }
diff --git a/helpers/typescript.ts b/helpers/typescript.ts
index 6c1dff1779838e07fd434c02d1ae85a594f4a373..92a04be12d9d00e2f210222ef956181857e53950 100644
--- a/helpers/typescript.ts
+++ b/helpers/typescript.ts
@@ -63,6 +63,7 @@ export const installTSTemplate = async ({
   vectorDb,
   postInstallAction,
   backend,
+  observability,
 }: InstallTemplateArgs & { backend: boolean }) => {
   console.log(bold(`Using ${packageManager}.`));
 
@@ -81,19 +82,47 @@ export const installTSTemplate = async ({
   });
 
   /**
-   * If next.js is not used as a backend, update next.config.js to use static site generation.
+   * If next.js is used, update its configuration if necessary
    */
-  if (framework === "nextjs" && !backend) {
-    // update next.config.json for static site generation
-    const nextConfigJsonFile = path.join(root, "next.config.json");
-    const nextConfigJson: any = JSON.parse(
-      await fs.readFile(nextConfigJsonFile, "utf8"),
+  if (framework === "nextjs") {
+    if (!backend) {
+      // update next.config.json for static site generation
+      const nextConfigJsonFile = path.join(root, "next.config.json");
+      const nextConfigJson: any = JSON.parse(
+        await fs.readFile(nextConfigJsonFile, "utf8"),
+      );
+      nextConfigJson.output = "export";
+      nextConfigJson.images = { unoptimized: true };
+      await fs.writeFile(
+        nextConfigJsonFile,
+        JSON.stringify(nextConfigJson, null, 2) + os.EOL,
+      );
+    }
+
+    const webpackConfigOtelFile = path.join(root, "webpack.config.o11y.mjs");
+    if (observability === "opentelemetry") {
+      const webpackConfigDefaultFile = path.join(root, "webpack.config.mjs");
+      await fs.rm(webpackConfigDefaultFile);
+      await fs.rename(webpackConfigOtelFile, webpackConfigDefaultFile);
+    } else {
+      await fs.rm(webpackConfigOtelFile);
+    }
+  }
+
+  if (observability && observability !== "none") {
+    const chosenObservabilityPath = path.join(
+      templatesDir,
+      "components",
+      "observability",
+      "typescript",
+      observability,
     );
-    nextConfigJson.output = "export";
-    nextConfigJson.images = { unoptimized: true };
-    await fs.writeFile(
-      nextConfigJsonFile,
-      JSON.stringify(nextConfigJson, null, 2) + os.EOL,
+    const relativeObservabilityPath = framework === "nextjs" ? "app" : "src";
+
+    await copy(
+      "**",
+      path.join(root, relativeObservabilityPath, "observability"),
+      { cwd: chosenObservabilityPath },
     );
   }
 
@@ -202,6 +231,18 @@ export const installTSTemplate = async ({
     };
   }
 
+  if (observability === "opentelemetry") {
+    packageJson.dependencies = {
+      ...packageJson.dependencies,
+      "@traceloop/node-server-sdk": "^0.5.19",
+    };
+
+    packageJson.devDependencies = {
+      ...packageJson.devDependencies,
+      "node-loader": "^2.0.0",
+    };
+  }
+
   if (!eslint) {
     // Remove packages starting with "eslint" from devDependencies
     packageJson.devDependencies = Object.fromEntries(
diff --git a/questions.ts b/questions.ts
index af5f17d69fd3b12cdaca5f83d7bbcd57b0fb6f35..275c2d0a78bcfe78bc284840396aadbb604faad8 100644
--- a/questions.ts
+++ b/questions.ts
@@ -429,6 +429,28 @@ export const askQuestions = async (
     }
   }
 
+  if (program.framework === "express" || program.framework === "nextjs") {
+    if (!program.observability) {
+      if (ciInfo.isCI) {
+        program.observability = getPrefOrDefault("observability");
+      }
+    } else {
+      const { observability } = await prompts({
+        type: "select",
+        name: "observability",
+        message: "Would you like to set up observability?",
+        choices: [
+          { title: "No", value: "none" },
+          { title: "OpenTelemetry", value: "opentelemetry" },
+        ],
+        initial: 0,
+      });
+
+      program.observability = observability;
+      preferences.observability = observability;
+    }
+  }
+
   if (!program.model) {
     if (ciInfo.isCI) {
       program.model = getPrefOrDefault("model");
diff --git a/templates/components/observability/typescript/opentelemetry/index.ts b/templates/components/observability/typescript/opentelemetry/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7e54b5feeef15141470b6add7898e3a9a08705f9
--- /dev/null
+++ b/templates/components/observability/typescript/opentelemetry/index.ts
@@ -0,0 +1,12 @@
+import * as traceloop from "@traceloop/node-server-sdk";
+import * as LlamaIndex from "llamaindex";
+
+export const initObservability = () => {
+  traceloop.initialize({
+    appName: "llama-app",
+    disableBatch: true,
+    instrumentModules: {
+      llamaIndex: LlamaIndex,
+    },
+  });
+};
diff --git a/templates/types/simple/express/index.ts b/templates/types/simple/express/index.ts
index 721c4ec9dd1922a36756c1b78142cab739cb1e85..150dbf598c909aa1e70200d458becfb2bbf34f28 100644
--- a/templates/types/simple/express/index.ts
+++ b/templates/types/simple/express/index.ts
@@ -2,6 +2,7 @@
 import cors from "cors";
 import "dotenv/config";
 import express, { Express, Request, Response } from "express";
+import { initObservability } from "./src/observability";
 import chatRouter from "./src/routes/chat.route";
 
 const app: Express = express();
@@ -11,6 +12,8 @@ const env = process.env["NODE_ENV"];
 const isDevelopment = !env || env === "development";
 const prodCorsOrigin = process.env["PROD_CORS_ORIGIN"];
 
+initObservability();
+
 app.use(express.json());
 
 if (isDevelopment) {
diff --git a/templates/types/simple/express/src/observability/init.ts b/templates/types/simple/express/src/observability/init.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e4ce2b18dfe32b4bed8b812772911030cdbdc91
--- /dev/null
+++ b/templates/types/simple/express/src/observability/init.ts
@@ -0,0 +1 @@
+export const initObservability = () => {};
diff --git a/templates/types/streaming/express/index.ts b/templates/types/streaming/express/index.ts
index 721c4ec9dd1922a36756c1b78142cab739cb1e85..150dbf598c909aa1e70200d458becfb2bbf34f28 100644
--- a/templates/types/streaming/express/index.ts
+++ b/templates/types/streaming/express/index.ts
@@ -2,6 +2,7 @@
 import cors from "cors";
 import "dotenv/config";
 import express, { Express, Request, Response } from "express";
+import { initObservability } from "./src/observability";
 import chatRouter from "./src/routes/chat.route";
 
 const app: Express = express();
@@ -11,6 +12,8 @@ const env = process.env["NODE_ENV"];
 const isDevelopment = !env || env === "development";
 const prodCorsOrigin = process.env["PROD_CORS_ORIGIN"];
 
+initObservability();
+
 app.use(express.json());
 
 if (isDevelopment) {
diff --git a/templates/types/streaming/express/src/observability/index.ts b/templates/types/streaming/express/src/observability/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e4ce2b18dfe32b4bed8b812772911030cdbdc91
--- /dev/null
+++ b/templates/types/streaming/express/src/observability/index.ts
@@ -0,0 +1 @@
+export const initObservability = () => {};
diff --git a/templates/types/streaming/nextjs/app/api/chat/route.ts b/templates/types/streaming/nextjs/app/api/chat/route.ts
index ef35bf76e427e27ea4f05460624c91017b2112b1..32b9bb163d19e158e186e2648347da54c02d5fe5 100644
--- a/templates/types/streaming/nextjs/app/api/chat/route.ts
+++ b/templates/types/streaming/nextjs/app/api/chat/route.ts
@@ -1,9 +1,12 @@
+import { initObservability } from "@/app/observability";
 import { StreamingTextResponse } from "ai";
 import { ChatMessage, MessageContent, OpenAI } from "llamaindex";
 import { NextRequest, NextResponse } from "next/server";
 import { createChatEngine } from "./engine";
 import { LlamaIndexStream } from "./llamaindex-stream";
 
+initObservability();
+
 export const runtime = "nodejs";
 export const dynamic = "force-dynamic";
 
diff --git a/templates/types/streaming/nextjs/app/observability/index.ts b/templates/types/streaming/nextjs/app/observability/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e4ce2b18dfe32b4bed8b812772911030cdbdc91
--- /dev/null
+++ b/templates/types/streaming/nextjs/app/observability/index.ts
@@ -0,0 +1 @@
+export const initObservability = () => {};
diff --git a/templates/types/streaming/nextjs/package.json b/templates/types/streaming/nextjs/package.json
index b0af0eebd465649a073de9f67a5435d1c8bedec2..a5872f79421a243e24d12dfe48c5324fc91aa03e 100644
--- a/templates/types/streaming/nextjs/package.json
+++ b/templates/types/streaming/nextjs/package.json
@@ -24,7 +24,7 @@
     "remark-code-import": "^1.2.0",
     "remark-gfm": "^3.0.1",
     "remark-math": "^5.1.1",
-    "supports-color": "^9.4.0",
+    "supports-color": "^8.1.1",
     "tailwind-merge": "^2.1.0"
   },
   "devDependencies": {
diff --git a/templates/types/streaming/nextjs/webpack.config.o11y.mjs b/templates/types/streaming/nextjs/webpack.config.o11y.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..b28a4591ae2870a6dd19a44fd2257e1d81977378
--- /dev/null
+++ b/templates/types/streaming/nextjs/webpack.config.o11y.mjs
@@ -0,0 +1,16 @@
+export default function webpack(config, isServer) {
+  // See https://webpack.js.org/configuration/resolve/#resolvealias
+  config.resolve.alias = {
+    ...config.resolve.alias,
+    sharp$: false,
+    "onnxruntime-node$": false,
+  };
+  config.module.rules.push({
+    test: /\.node$/,
+    loader: "node-loader",
+  });
+  if (isServer) {
+    config.ignoreWarnings = [{ module: /opentelemetry/ }];
+  }
+  return config;
+}