LogoStarterkitpro
Features

Crisp

Integrating Crisp live chat for customer support.

Integrate Crisp live chat into your StarterKitPro application to provide real-time customer support.

Optional Feature

Crisp is not included by default to keep the core lean. Install it if you want to add Crisp as a support option. If not configured, the default behavior might fall back to email support where applicable.

1. Installation

Install the Crisp SDK package:

Terminal
npm install crisp-sdk-web

2. Configuration (lib/app-config.ts)

Add the crisp configuration to your lib/app-config.ts file. You'll need your Crisp Website ID.

lib/app-config.ts (Additions)
export const appConfig = {
  // ... other configs
 
  crisp: {
    // Required: Get your Crisp Website ID from Crisp dashboard
    // Settings > Website Settings > Setup Instructions
    id: process.env.CRISP_WEBSITE_ID || "", // Environment variable recommended
    // Optional: Hide Crisp by default on all routes except these.
    // Crisp is often toggled via a specific support button.
    // Leave empty or remove to show on all routes by default.
    enabledRoutes: ["/"],
  },
 
  // ... other configs
};

3. Setup Environment Variable

Add your Crisp Website ID to your environment variables file (.env.local):

.env.local
CRISP_WEBSITE_ID=YOUR_CRISP_WEBSITE_ID

Replace YOUR_CRISP_WEBSITE_ID with the actual ID from your Crisp dashboard.

4. Create Crisp Chat Component (components/shared/crisp-chat.tsx)

Create a new client component to initialize and manage the Crisp chat widget:

components/shared/crisp-chat.tsx (New File)
"use client";
 
import { useEffect, useCallback, useMemo } from "react";
import { usePathname } from "next/navigation";
import { useSession } from "next-auth/react";
import { Crisp } from "crisp-sdk-web";
import { appConfig } from "@/lib/app-config";
 
export function CrispChat(): null {
  const pathname = usePathname();
  const { data: session } = useSession();
 
  const shouldShowChat = useMemo(() => {
    if (
      !appConfig.crisp.enabledRoutes ||
      appConfig.crisp.enabledRoutes.length === 0
    ) {
      return true;
    }
 
    return appConfig.crisp.enabledRoutes.includes(pathname);
  }, [pathname]);
 
  const updateChatVisibility = useCallback(() => {
    if (!appConfig.crisp.id) return;
 
    try {
      if (shouldShowChat) {
        Crisp.chat.show();
      } else {
        Crisp.chat.hide();
      }
    } catch (error) {
      console.error("Error updating Crisp chat visibility:", error);
    }
  }, [shouldShowChat]);
 
  useEffect(() => {
    if (!appConfig.crisp.id) {
      if (process.env.NODE_ENV === "development") {
        console.warn("Crisp chat ID not configured");
      }
      return;
    }
 
    try {
      Crisp.configure(appConfig.crisp.id);
 
      updateChatVisibility();
 
      const onChatClosed = Crisp.chat.onChatClosed(() => {
        if (!shouldShowChat) {
          Crisp.chat.hide();
        }
      });
 
      return () => {
        try {
          Crisp.chat.offChatClosed(onChatClosed);
        } catch (error) {
          console.error("Error cleaning up Crisp event listener:", error);
        }
      };
    } catch (error) {
      console.error("Error initializing Crisp chat:", error);
    }
  }, [pathname, shouldShowChat, updateChatVisibility]);
 
  useEffect(() => {
    if (!session?.user || !appConfig.crisp.id) return;
 
    try {
      if (session.user.email) {
        Crisp.user.setEmail(session.user.email);
      }
 
      Crisp.user.setNickname(session.user.name || "Anonymous");
 
      if (appConfig.crisp.includeUserData && session.user) {
        Crisp.session.setData({
          // any data you want to pass
          userId: session.user.id,
        });
      }
 
      if (session.user.image) {
        Crisp.user.setAvatar(session.user.image);
      }
    } catch (error) {
      console.error("Error setting Crisp user data:", error);
    }
  }, [session]);
 
  return null;
}

This component initializes Crisp, passes user session data (email, name) if available, and handles hiding the chat widget on specific routes based on the enabledRoutes configuration.

5. Render Crisp Component (app/layout.tsx)

Import and render the CrispChat component within your main layout file, typically near the closing </body> tag.

app/layout.tsx (Additions)
import { CrispChat } from "@/components/shared/crisp-chat";
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {/* ... other layout components ... */}
        <CrispChat />
        {/* ... other scripts ... */}
      </body>
    </html>
  );
}

We conditionally render <CrispChat /> only if CRISP_WEBSITE_ID is set in the environment variables. This prevents attempting to load Crisp if it's not configured.

6. Integrate with Error Handling (Optional)

If you want to provide users with an option to contact support via Crisp directly from an error page (e.g., a client-side error boundary), you can modify the contact button's onClick handler.

Assuming you have a component that shows an error message and a 'Contact Support' button:

components/shared/supportButton.tsx (Example Modification)
import { Crisp } from "crisp-sdk-web";
 
const handleSupportClick = () => {
  // Prioritize Crisp if configured
  if (appConfig.crisp.id) {
    Crisp.chat.show(); // Make sure the chat widget is visible
    Crisp.chat.open(); // Open the chat widget
  } else if (appConfig.resend.supportEmail) {
    // Fallback to email if Crisp is not configured but email is
    window.open(
      `mailto:${appConfig.resend.supportEmail}?subject=Error Report: ${appConfig.appName}&body=Hello, I encountered an error...`,
      "_blank"
    );  
  } else {
    // Optional: Handle cases where neither is configured
    alert("Support configuration not found.");
  }
};

This logic first checks if Crisp is configured (appConfig.crisp.id). If yes, it shows and opens the Crisp chat. If not, it falls back to opening a mailto: link using the configured support email.

With these steps, Crisp live chat should be integrated into your application.

On this page