Telegram Web App with Remix on Vercel? Let’s go!
A story about a project on an unfamiliar (but hyped) stack: Remix, Prisma, Base Web components, and Vercel. Strengths and weaknesses, opportunities and threats, intrigue and betrayal.
From time to time, we all need to step out of our comfort zone and try something new. For my day-to-day work as a pure frontender, I have been using Web Components and Lion Web for almost five years. And for my pet projects, I have been using Create React App with Recoil state management and Evergreen Components for the frontend and Express/Koa for the backend.
However, there is a lot of hype around Remix lately. After looking at their landing page (which looks awesome, by the way), I was pretty impressed with how they handle request waterfalls and jumping interfaces, so I decided to try it.
Coincidentally, my friend wanted to make a Telegram Web App for his business. The idea is quite simple — he needed a UI to manage so-called “checklists” that he could send to his employees at a specific date and time. In other words, this was a ToDo app where he could create multiple ToDo lists and set a schedule on which these lists were sent.

I approached another developer willing to work on the project, and (chanting “Go, Remix!” together) we started.
Early optimism: local development with Remix
First, a development setup (spoiler alert: it will change significantly). We started with a Remix tutorial Jokes app, learning the core concepts. We already knew that we would deploy to Vercel, and Remix quite conveniently had a built-in template for that.
The tutorial went smoothly. Everything was covered step by step, with huge code, for all possible cases in an application.
After this tutorial, we created our model of the application in Prisma with initial data, installed the Base Web components library, and tried to build our first table with data. We used SQLite for the database. This was quite simple and nice.
Day by day, screen by screen, we got closer to the functionality we wanted. Base Web had all the components we needed (actually, even more); working with Remix was rarely a challenge, and only when we wanted to do something we had not yet learned. And, of course, everything was covered in the Remix documentation and was solvable.
We pushed everything to Github. Vercel built and deployed the project automatically, which also worked predictably.

The very last step was to connect our Remix Web app to Telegram. I did not expect any problems with this, as Telegram runs your web app without any other constraints, aside from notifying Telegram when the app is ready to be displayed:
Add this link to a script
<script src="https://telegram.org/js/telegram-web-app.js"></script>And then do
window.Telegram.WebApp.ready();when the app is ready. Exceptionally simple, right?
Later realism: Telegram Web App
The first challenge was local development. How do I get Telegram to access the Remix app?
When one tells Telegram that they want to have a Web App in their bot, Telegram asks for the Web App URL. And the URL can only be HTTPS. So I installed the SSL certificates. But how do you add SSL certificates to Remix? Okay, it was also possible to run Remix through the Express server and add SSL certificates. Alright, a few evenings later, I had a working Remix Web App over HTTPS.

And what about Telegram Web App? It did not work…
That was because of the way Telegram Web Apps work. Telegram does not redirect the user to your website in an iframe, but uses (probably) Web View, and therefore Telegram needs to download all resources from the Web App’s URL. So it’s not enough to just use HTTPS on your localhost. You need to expose your local network.

Fortunately, there was a package Ngrok that I used for another project for this purpose. So I was able to run a ngrok NPM module to expose a local network and specify the public URL, which pointed to my local Web App as my Telegram Web App URL.

And… it worked! But not after I would deploy it…
The second challenge was database access. I mentioned earlier that we were initially working with SQLite. SQLite stores the database as a file in the project.
But what happens during deployment? Each new deployment — a new container with an app. New container — new file with a database. And the previous database is lost. Not cool, right?
I decided to use PostgreSQL. Thanks to Prisma, switching to another database was easy. Two lines in the configuration — done! To get a database, I chose a Heroku free PostgreSQL (which, unfortunately, is discontinued now).

This solved the problem! But by far not the last one…
The third challenge was to get this setup working on the server. So far, we had:
Remix Web App, which was working as a route on the Express server.Telegram bot, which was working as a route on the Express server.PostgreSQL, which existed on Heroku (behind Prisma ORM).Vercel, which did not have a built-in template for such a configuration
The problem was that for Remix, initially, I used Vercel’s built-in template “Remix,” but now I had an Express server. And for a custom build, I could not manage to build the Remix app.
Fortunately, Vercel supports monorepo. So, I decided to extract theExpress server with the Telegram bot into a separate project and leave Remix Web App with the Vercel template intact. Gladly, I have quite some experience of working with monorepo. So, after approximately 20 attempts to build the project without errors (a couple more evenings), I finally got it working.

The bot was responding to users, and the Web App worked! Time to share it with a customer!
The first thing any user should do is create their account through the bot. It can be dony by sending a/start command. This happens almost automatically because when a user installs (opens) a new Telegram Bot, all they see is a description and the button “Start,” which sends this command to the server.
And this command did not work…

Not only did it not work, but there were no errors in Vercel’s logs. Okay, time to cover our code with good old console.log.
It turned out that the bot could not connect to the database to create a user. How could the same Prisma configuration work in one place but not in another? Time to find out!
After trying to explicitly connect to the database using db.$connect, changing the monorepo structure, and even copying the same Prisma database model and configuration into the bot, nothing worked. Perhaps, because this is a serverless function, the function is terminated before the connection is made. After spending some time in the documentation of Prisma, I found out that I can change the timeouts. Let us try playing with them!
First, I disabled the timeouts completely, but that did not change anything. Second, I set all the timeouts to 5 seconds. And YES, after that, the error appeared in the logs, and the hypothesis was correct. The connection could not be established within the given timeouts. However, five seconds is way too long to connect to the database, anyway. So a higher timeout was not a good choice.
Okay, time to get even crazier. What if we did not run the database queries in the bot, but sent them to the Remix app?
Tried. Failed…

So, it seemed that all the external requests from the bot were failing. I tried changing the data center location, but still no luck.
I still do not quite understand the reason, but I suspect it has something to do with the firewall or traffic routing rules.
Dead end
At this moment, it was confirmed that I could not connect to external resources. They could not be established, whether it was a database or HTTPS connection. However, it was still a mystery how the Remix app could work with the database without any problems. Also, the bot was able to send messages to Telegram. Some requests went through, but not to the Remix app or database.
Thinking outside the box
Since we are already in the realm of wild ideas, how about we try something completely different? Essentially, the Telegram bot on Webhooks is a POST endpoint. You tell Telegram which URL to send messages to, and it calls that webhook on every message from users who interact with your bot. So, in theory, we can tell Telegram to send the messages to our Remix app and process them there.

And… it worked!
After all the umpteen attempts to figure out how to make a Telegram Web App with Remix on Vercel, it finally worked. I adjusted the local development to the new configuration and continued to tweak the Remix app.
P.S. Did I mention that Remix’s documentation is fantastic? After completing the project and consulting the documentation another time, I found that they suggested the implementation of Remix with Webhooks. What is “Outside box,” with me, is a “Fully covered topic in the documentation” with them 😊
Following are a few notes on the technologies I tried in this project.
Remix
- The old way of working with forms is fine for me. This way is based on the native form tag. I like that a lot, and I have been looking for something like that, but maybe this does not fit modern FE development. In the last ten years, FE developers used custom methods to collect the data and send it to the server via AJAX. Yes, that has made us dependent on JavaScript on the client side, and the fact that Remix supports JavaScript-free applications is fantastic. But I am a little doubtful that this is a killer feature.
- Routing is good.
- Convention of not loading
*.server.tsxfiles on the client is good. Catchboundaries anderrorboundaries are good.
While writing this post, I was looking at the demo of the Remix. And, you know, what they promised — they delivered. I liked what I felt during the development, and I’m glad these are the points mentioned in the landing, like routing or error boundaries. Unfortunately, I did not get a chance to work with many nested routes to avoid waterfall rendering, but I bet that’s cool too.

Base Web
Selectcomponent nativenameproperty is missing. The forms are sent natively in Remix, and the inputs should have native attributes like aname. But to my surprise,Selectcomponent does not support this. So I ended up creating a custom component that renders hidden inputs with the required name along withSelect.- Fewer available icons compared to the other components libraries (for example, Evergreen).
- A bit challenging server-side rendering of Styletron (a styling system from Uber used in Baseweb). I could not figure out Styletron server-side rendering for development for a long time. This led to Flash of unstyled content and even post-build production issues. After studying the Nextjs with Styletron example, I had to include the server-side generated style, which was a bit of a challenge in Remix.
Here is a screenshot of the application with the components used to give you a feeling of Base Web.

Overall, the experience is very positive. The data tables exceed expectations with built-in filters and such. The only downside is that the row's changing height (expanding) is missing, which limits the usage of these tables.

Prisma
- Models look nice and simple.
- API seems quite natural.
findUnique/findFirstare a bit confusing, though.
I also liked it and will try to use it more in the future.
Telegram Web App
After understanding how these apps work, it became easier to integrate my Remix app into it. The documentation is clear but sometimes a little hard to find. For example, if you google “Telegram Web App”, you get the links to a regular Telegram. Therefore, I had to add the documentation to my bookmarks.
But in general, it’s nice too. Telegram provides a way to run Devtools for the Telegram Web App, but that’s not strictly necessary either. You can run Web App in normal Chrome and test it in the actual Telegram Web App after the deployment to Production.

Vercel
Vercel is Great! Even though I struggled a lot with the external connections and the Telegram bot, the simplicity of everything is outstanding. To start the project, all you have to do is create a GitHub repository, push some code there and connect it to Vercel. After that, it will take care of the rest. Monorepo support is there, the logs are excellent, the pipeline is straightforward, the settings are clear, there is a lot of documentation and examples. Very happy with it.

Final optimism
The whole project took longer than expected because of these issues with the bot, but I’m glad I tried it. Nowadays, it’s possible to build a complete app with front and back within a few days. This relieves developers from the burden of infrastructure and leaves them time for what they love to do —programming.
