Skip to content

leojuriolli7/t3-blog

Repository files navigation

- T3 Blog

Deployed to Vercel on https://t3-blog-pi.vercel.app/

This is a fullstack blog application made with the T3 Stack.

Aditionally, I used AWS S3 Buckets for file uploads, with presigned urls for securely accessing the stored files, ensuring they are safe and private.

Features

  • Login with Google, Github or discord, or with a Magic link to your e-mail.
  • Create & edit posts.
  • Receive notifications for new posts, replies, comments on your posts, new followers, and more.
  • Tag your posts.
  • Intercepted routes like reddit and instagram (Read a post with a modal, without having to go to a new page)
  • Upload files to your posts as attachments: You can upload videos, images, audio or documents (.pdf, .msword, etc.)
  • Upload images to your posts' body by dragging and dropping (or file system) like on Github!
  • Every file uploaded can be interacted - You can listen to the audios using a custom-built audio-player, watch the videos, download any of the files, etc.)
  • Like or dislike posts.
  • Create a poll on your post and let users vote on it.
  • Add a link to your post with link scraping using metascraper.
  • Comment on posts.
  • Use Markdown to write your posts & comments.
  • Reply to comments on posts (Infinitely nested comment replies like reddit)
  • Favorite posts and browse through all your favorited posts.
  • Follow users and be followed by other users, see their and your followers/following.
  • See a personalized "Following" timeline with posts from your following.
  • Search posts, tags, users & comments with full-text search.
  • Access yours or other users' profiles and see their posts.
  • Personalize your profile - Upload an avatar, change your username, add a bio and a profile link.
  • Search for posts using different filters and by their tags.
  • Role-based API with users and admins.
  • Search engine optimizations through meta-tags, server-side-rendering and semantic HTML.

The stack

Improving the user experience

  • I used TanStack Query (formerly React Query) to implement optimistic updates, allowing the UI to update without having to wait for a backend response, making for a blazing fast experience. - You can like/dislike posts and see the feedback immediately, or edit a post and see the changes applied instantaneously.
  • Implemented infinite scrolling on multiple screens with TanStack Query's useInfiniteQuery, tRPC and the Intersection Observer API.
  • I used Lodash debounce to debounce search results and improve the search input performance while keeping a good UX. (Typing and automatically receiving results)

Performance increases

Various performance optimizations were implemented on this website, such as code splitting, data-fetching in parallel, optimizing fonts, dynamic importing, reducing the first load JS and leveraging the Next.JS server-side for expensive calculations instead of doing them on the client-side.

By data fetching in parallel, all requests go out at the same time and we await them together. If we await each one individually, each request will only fire when the previous one has been resolved, causing potentially huge performance hits.

By dynamic importing and code-splitting, the initial JS loaded on page-load was drastically reduced on every page. For instance, instead of loading the JS for the edit post form on the post page load, it is loaded on-demand when the user clicks to edit a post.

I also leveraged the Next.JS node server for processsing markdown and formatting all dates on the server-side, instead of doing it on the client. I also truncate the HTML (Limit it to 250 characters) on the server to reduce the size of the DOM tree and avoid sending unnecessary HTML to the client. It is also parsed before being sent to the client, adjusting for Search Engine Optimizations. Read more here.

Processing markdown on the server

Another important UX improvement was when I switched from converting markdown to HTML on the client-side with React Markdown to converting MD to HTML on the server-side, and returning simple HTML to the client.

I could notice whenever I first loaded the site, or scrolled down to load new posts, the page would slow down/crash for a bit, presumably to process & convert the markdown text it was receiving. This poor UX was fixed with this change. This contributed to a huge boost in the performance scores of the website:

Parsing markdown on the client After parsing markdown on the server
Screenshot from 2023-03-29 23-46-02 Screenshot from 2023-03-29 23-47-33

Overcoming T3 Stack limitations

Cold starts

This project is deployed on Vercel with Serverless Functions. This means every time someone opens the website, there is a waiting period while the function is initialized.

As the website grew, and so did the amount of tRPC routers and dependencies, the cold-starts got to 7-10 seconds. To solve this, I split all the tRPC routers into their own serverless functions, meaning they could load up individually and on-demand (and in parallel), instead of loading everything at once.

Here is the Pull Request.

Another necessary change to counteract cold-starts was to lazy-load all tRPC procedures.

Before After
Screenshot from 2023-06-24 21-43-30 Screenshot from 2023-06-24 21-42-57

File uploads

tRPC does not support multipart/form-data, so file uploads could not be done reliably inside the tRPC router. For that reason, I decided to use the AWS SDK, S3 buckets and presigned URLs, a very safe and reliable method of uploading files.

In this case, the tRPC router is only responsible with creating presigned URLs for uploads on the client and getting/parsing the AWS objects before sending the JSON to the client on any queries.

Image caching

Next.js has a very effective and powerful image caching when using their next/image component. However, this can be an issue when updating an image without changing its url, like for example, updating a user's profile picture, the url is the same, but the content has changed. Next.js is not able to identify the change.

To counteract this, every time an image is updated, a timestamp is appended to its url, to ensure the path will be different and the cache will be invalidated immediately. Eg: bucket-url/user-id/avatar?1231903109

See more on the commit.

Run the project

Environment variables:

Commands

  1. Install all dependencies with yarn
  2. Init prisma with npx prisma init
  3. Push prisma schema to database with npx prisma db push
  4. Start the application with yarn dev