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.
useInfiniteQuery
, tRPC and the Intersection Observer API.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.
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 |
---|---|
![]() |
![]() |
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.
Another necessary change to counteract cold-starts was to lazy-load all tRPC procedures.
Before | After |
---|---|
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.
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
DATABASE_URL
: Planetscale MySQL database URL.
GOOGLE_CLIENT_ID
, GOOGLE_CLIENT_SECRET
: Required for Google OAuth 2.0. Can be generated on Google API Console.
DISCORD_CLIENT_ID
, DISCORD_CLIENT_SECRET
: Required for Discord OAuth 2.0.
GITHUB_ID
, GITHUB_SECRET
: Required for Github OAuth 2.0.
E-mail sign-in variables:
Read more: https://next-auth.js.org/providers/email
MAILER_PASSWORD=
MAILER_USER=
EMAIL_SERVER_HOST=
EMAIL_SERVER_PORT=
Next Auth variables:
Read more: https://next-auth.js.org/configuration/options#environment-variables
NEXTAUTH_URL=http://localhost:3000/api/auth
NEXTAUTH_SECRET=
AWS attachments and profile avatar buckets' variables:
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_S3_BUCKET_NAME=
AWS_S3_AVATARS_BUCKET_NAME=
NEXT_PUBLIC_AWS_S3_AVATARS_BUCKET_NAME=
NEXT_PUBLIC_AWS_S3_POST_BODY_BUCKET_NAME=
AWS_REGION=
yarn
npx prisma init
npx prisma db push
yarn dev