SDK icon indicating copy to clipboard operation
SDK copied to clipboard

Throwing "ReferenceError: window is not defined" on Next.js with SSR

Open ivanproskuryakov opened this issue 1 year ago • 13 comments

The library fails to work with the Next.js framework ("next": "^12.1.6") while SSR.

The issue is caused by the missing window object within the file node_modules/@twa-dev/sdk/dist/sdk.js, and it occurs at the time of import. Ref: https://github.com/twa-dev/SDK/blob/master/src/sdk.ts

Transpiled file

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebApp = void 0;
require("./telegram-web-apps");
var telegramWindow = window; // Seems to be the line causing the problem
exports.WebApp = telegramWindow.Telegram.WebApp;
//# sourceMappingURL=sdk.js.map

Demo app:

import WebApp from '@twa-dev/sdk'; // Happens at the time of import

const MiniApp = () => {
  return (
    <div>
      ...
      <button onClick={() => WebApp.showAlert(`Hello World!`)}>
        Show Alert
      </button>
    </div>
  );
};

export default MiniApp;

Console output

Uncaught ReferenceError: window is not defined
    at <unknown> (file:///Users/ivan/code/communa/frontend/node_modules/@twa-dev/sdk/dist/telegram-web-apps.js:248:5)
    at Object.<anonymous> (file:///Users/ivan/code/communa/frontend/node_modules/@twa-dev/sdk/dist/telegram-web-apps.js:274:3)
    at Module._compile (node:internal/modules/cjs/loader:1364:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
    at Module.load (node:internal/modules/cjs/loader:1203:32)
    at Module._load (node:internal/modules/cjs/loader:1019:12)
    at Module.require (node:internal/modules/cjs/loader:1231:19)
    at require (node:internal/modules/helpers:177:18)
    at Object.<anonymous> (file:///Users/ivan/code/communa/frontend/node_modules/@twa-dev/sdk/dist/sdk.js:4:1)
    at Module._compile (node:internal/modules/cjs/loader:1364:14)
Screenshot 2024-05-22 at 17 47 28

ivanproskuryakov avatar May 22 '24 14:05 ivanproskuryakov

@ivanproskuryakov I am experiencing the same, let me know if you find a good fix.

I am able to use Next's Script tag to load the TWA script and then do the JS there, but I would like to use this SDK.

kevcube avatar May 23 '24 01:05 kevcube

@kevcube Sure, I'll open a PR with a patch. Meanwhile, can you post your workaround for this error, please?

ivanproskuryakov avatar May 23 '24 08:05 ivanproskuryakov

@ivanproskuryakov

"use client";
import Script from "next/script";

import { Telegram } from "@twa-dev/types";

declare global {
  interface Window {
    Telegram: Telegram;
  }
}

export default function Page() {
  return (
    <Script
      id="TelegramWebApp"
      src="https://telegram.org/js/telegram-web-app.js"
      onReady={() => {
        window.Telegram.WebApp.MainButton.setParams({
          text: `Hello`,
          is_visible: true,
        });
      }}
    />
  );
}

I'm using Next 14, this can go in layout or other imported files to auto load on all pages of your app.

kevcube avatar May 23 '24 11:05 kevcube

@ivanproskuryakov

"use client";
import Script from "next/script";

import { Telegram } from "@twa-dev/types";

declare global {
  interface Window {
    Telegram: Telegram;
  }
}

export default function Page() {
  return (
    <Script
      id="TelegramWebApp"
      src="https://telegram.org/js/telegram-web-app.js"
      onReady={() => {
        window.Telegram.WebApp.MainButton.setParams({
          text: `Hello`,
          is_visible: true,
        });
      }}
    />
  );
}

I'm using Next 14, this can go in layout or other imported files to auto load on all pages of your app.

It didn't work for me. Anyone faced the issue and find a solution other than this?

EliusHHimel avatar Jul 14 '24 16:07 EliusHHimel

@ivanproskuryakov

"use client";

import Script from "next/script";

import { Telegram } from "@twa-dev/types";

declare global {

interface Window {

Telegram: Telegram;

}

}

export default function Page() {

return (

<Script
  id="TelegramWebApp"
  src="https://telegram.org/js/telegram-web-app.js"
  onReady={() => {
    window.Telegram.WebApp.MainButton.setParams({
      text: `Hello`,
      is_visible: true,
    });
  }}
/>

);

}

I'm using Next 14, this can go in layout or other imported files to auto load on all pages of your app.

It didn't work for me. Anyone faced the issue and find a solution other than this?

Just use telegram-mini-apps/twa.js and join @devs on telegram

kevcube avatar Jul 14 '24 16:07 kevcube

Just check if window is exist, like this:

if (typeof window !== "undefined") {
     // Your code here
}

something like this:

import WebApp from '@twa-dev/sdk'; // Happens at the time of import

const MiniApp = () => {

 const onClick = () => {
     if (typeof window !== "undefined") {
         WebApp.showAlert(`Hello World!`)
    }   
 }

  return (
    <div>
      ...
      <button onClick={onClick}>
        Show Alert
      </button>
    </div>
  );
};

export default MiniApp;

sramezani avatar Aug 10 '24 12:08 sramezani

still the same issue i faced. No PR with fix merged ?

n8m avatar Aug 21 '24 12:08 n8m

The issue is still there!

podrabinek avatar Oct 01 '24 13:10 podrabinek

Still same +

bytemtek avatar Nov 03 '24 09:11 bytemtek

I meet this issue, and currently the workaround I used is to dynamic import the component which use import WebApp from '@twa/sdk'. e.g:

import WebApp from "@twa-dev/sdk";
const Foo = () => {
    const foo = () => {WebApp....}
}
export default Foo;

dynamic import the component:

import dynamic from 'next/dynamic';
import Foo from "@/components/foo";
const Foo = dynamic(() => import("foo"), { ssr: false });
const Page = () => {
    return (
        <div>
            <Foo/>
        </div>
    )
}

joe888777 avatar Nov 30 '24 10:11 joe888777

I meet this issue, and currently the workaround I used is to dynamic import the component which use import WebApp from '@twa/sdk'. e.g:

import WebApp from "@twa-dev/sdk";
const Foo = () => {
    const foo = () => {WebApp....}
}
export default Foo;

dynamic import the component:

import dynamic from 'next/dynamic';
import Foo from "@/components/foo";
const Foo = dynamic(() => import("foo"), { ssr: false });
const Page = () => {
    return (
        <div>
            <Foo/>
        </div>
    )
}

You can use @telegram-apps/sdk-react without any problem.

bytemtek avatar Nov 30 '24 10:11 bytemtek

I meet this issue, and currently the workaround I used is to dynamic import the component which use import WebApp from '@twa/sdk'. e.g:

import WebApp from "@twa-dev/sdk";
const Foo = () => {
    const foo = () => {WebApp....}
}
export default Foo;

dynamic import the component:

import dynamic from 'next/dynamic';
import Foo from "@/components/foo";
const Foo = dynamic(() => import("foo"), { ssr: false });
const Page = () => {
    return (
        <div>
            <Foo/>
        </div>
    )
}

You can use @telegram-apps/sdk-react without any problem.

But some methods do not work with @telegram-apps/sdk-react

GrayJyyyyy avatar Dec 22 '24 05:12 GrayJyyyyy

I was having the same issue but with client component, wich as supposed to work, the problem is that next apparently import the packages as ssr by default as you can see in the base errors in the screenshot "at (ssr)"

Image

then I just imported the library this way, as I noted the issue on the error was that next was rendering the library at the server sidem then i came to the library and it was trying to access window, so it was nextjs fault, and this import worked to me

let WebApp: any;
if (typeof window !== "undefined") {
	WebApp = require("@twa-dev/sdk").default;
}

Full code provider that I was using after the changes

"use client";

import {
	createContext,
	useContext,
	useEffect,
	useMemo,
	useState,
	ReactNode,
} from "react";
import crypto from "crypto";

let WebApp: any;
if (typeof window !== "undefined") {
	WebApp = require("@twa-dev/sdk").default;
}

export interface ITelegramContext {
	isOnTelegram: boolean;
}

const TelegramContext = createContext<ITelegramContext>({
	isOnTelegram: false,
});

export const TelegramProvider: React.FC<{ children: ReactNode }> = ({
	children,
}) => {
	const [isOnTelegram, setIsOnTelegram] = useState(false);
	const [isValid, setIsValid] = useState("invalid");
	const [initDataUnsafe, setInitDataUnsafe] = useState<any>();
	const [initData, setInitData] = useState<any>();

        //Make a api url for this function its not supposed to be here 
	const verifyInitData = (telegramInitData: string): boolean => {
		const urlParams = new URLSearchParams(telegramInitData);
	
		const hash = urlParams.get('hash');
		urlParams.delete('hash');
		urlParams.sort();
	
		let dataCheckString = '';
		for (const [key, value] of urlParams.entries()) {
			dataCheckString += `${key}=${value}\n`;
		}
		dataCheckString = dataCheckString.slice(0, -1);
	
		const secret = crypto.createHmac('sha256', 'WebAppData').update("bot_key");
		const calculatedHash = crypto.createHmac('sha256', secret.digest()).update(dataCheckString).digest('hex');
	
		return calculatedHash === hash;
	}

	useEffect(() => {

		if (WebApp.initDataUnsafe.user) {
			setIsOnTelegram(true);
			setInitDataUnsafe(JSON.stringify(WebApp.initDataUnsafe.user));

			const { hash, ...rest } = WebApp.initDataUnsafe.user;
		}

		if (WebApp?.initData) {
			setIsOnTelegram(true);
			setInitData(JSON.stringify(WebApp.initData));

			
			setIsValid(verifyInitData(WebApp.initData) ? "valid" : "invalid");
		}

	}, []);

	const value = useMemo(
		() => ({ isOnTelegram }),
		[isOnTelegram],
	);

	return (
		<TelegramContext.Provider value={value}>
			IsValid
			{isValid}
			<br />
			Unsafe
			{initDataUnsafe}
			<br />
			Data
			{initData}
			{children}
		</TelegramContext.Provider>
	);
};

export const useTelegram = (): ITelegramContext => useContext(TelegramContext);

LimaTechnologies avatar Jan 22 '25 16:01 LimaTechnologies