How I shipped an event registration site in just 1 week with Nuxt, Directus, OpenAI, and TailwindCSS
Tags: openai, directus, nuxt, tailwindcss
Published: 2024-06-07 16:47:03

I recently shipped a event registration site in 1 week that would take some companies 1 year. And I'm definitely not a 10x developer. 

When I was first learning to code, I always appreciated behind-the-curtain looks at how projects were made, soooo… here’s the story and how it’s built.

## The TLDR;

Adding this here, just to get the “What’s your stack?” questions out of the way 🤣

- frontend - [Nuxt](https://nuxt.com) / Vue
- backend / CMS - [Directus](https://directus.io)
- styling - [Nuxt UI](https://ui.nuxt.com) and [Tailwind CSS](https://tailwindcss.com/)
- hosting - Netlify
- avatar generation - OpenAI [Dall•E 3](https://openai.com/index/dall-e-3/)

All the gory details are below.
## The Story **🐰**

### **Meet Leapweek**

LeapWeek.dev is our week long launch celebration for developers at Directus. There are product announcements, workshops, giveaways, and more. 

The live events are typically hosted via the Directus TV website (https://directus.io/tv) but the registration has typically been powered by other tools like [Lu.ma](https://lu.ma/) and a few others.

Using third-party tools obviously meant additional costs which add up, but that wasn’t the main concern. 

**The big headaches to solve were:**

1. a very disconnected experience for users
2. an in-efficient (pronounced “💩-y”) workflow for our team.

So with this third Leap Week, we made the call to build our own “platform” that is tightly integrated with our existing stack and could be re-used for future Leap Week events. 

### **Goals 🥅**

Aside from supporting registration for the event, there were a few important boxes to check.

- “Own” our own event property.
- Build a growth loop to incentivize shares.
- Leverage AI for something “different” to get attention.

## **The Concept 👨‍🚀🚀**

Our previous Leap Week events were spaced themed. We knew we wanted to carry that same theme so we didn’t have to whip up a ton of brand new supporting creative.

Aside from that though, the event registration site was mostly a blank canvas.

We took some inspiration from Vercel Ship and their user registration badge concept. I’d also seen other companies do similar things in the past. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/53pk4hfmu3pdvxfpcblz.png)

I really loved the personalization, but I definitely wanted to take the concept “up a notch”.

**Mission Patch**

The sharing loop was critical for the project and our first idea for it was a mission patch.

It looked nice, dynamically added a name when you filled out the form, and included a parallax effect to make it feel "3D". It would be fine, but it just didn’t feel right. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c83byb4z09kel5dpqs6a.png)


We kept iterating though. At some point fairly early in the process, I had snuck in a rabbit astronaut on the landing page as an accent piece.

My next thought was “ok, let’s use that to make the patch concept even more interesting”. So I added the ability to upload an avatar and drop it into the astronaut suit and that appeared behind the custom patch. This felt a little more interesting.

And then….I wanted to customize the astronaut even further by adding custom patches for the person’s country and company.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3jud6wdc9luavdkt9ezs.png)

A few GPT-fueled jam sessions later and quick demo that left the whole team smiling, the astronaut became the focal point.

**And Rabbitars were born.**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvnkx9j321kejr6dmhus.png)


So basically “Rabbitars” are personalized rabbit astronauts. It’s our version of the registration badge and it’s definitely over the top. They include:

- AI generated rabbit headshot
- Company logo patch
- Name patch
- Country patch

And when you share your unique referral link – we use that avatar to create a personalized social sharing image as well.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hkoc1xsuesmnn84ro54z.png)

## The Backend **🧱**

The backend is powered by [Directus](https://directus.io) - a data platform that is a hybrid of BaaS and CMS. It pairs up with most SQL databases. 

It provides:

- instantly ready-to-go REST APIs (or GraphQL if that’s your thing)
- asset / file storage
- authentication and permissions
- admin interface where you can manage and edit data, build or adjust your data model without writing code
- no-code dashboards
- low code automation tool to build simple or complex flows

Directus runs the whole backend from ticketing and registration to serving data for the landing page.

### **Data Model**

I created the data model via the UI inside Directus. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gv7d6nooj5ci8xtssw1.png)

This means it was easy to see what the editing experience would look like for my team.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wlkc702j3vlfz6rfhdgh.png)

I also put together a nice dashboard for the team to track sign ups and view all the different countries users were from. This is baked into Directus and took me all of like 5 minutes.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7g083l4vnz3lzb0lt7nt.png)



## The Frontend **🧱**

The frontend runs on Nuxt - the popular full stack meta-framework on top of Vue.js. I’m a fan of Nuxt and I’ve been using it for several years in various projects.

### Routes

Nuxt’s file based routing is a helpful pattern to speed along projects. Chuck a Vue component into the `/pages` directory and you get a route.

And there’s really only a handful of routes for this project.

- `/` - the landing page
- `/tickets`  - the registration page
- `/tickets/customize` - the (logged in only)
- `/ticket/[ticket]` - the personalized rabbitar page
- `/auth/login` - login if you switch devices or logout
- `/auth/reset` - if you somehow misplace the initial email with a confirmation code
- `/terms` - terms and conditions for the giveaway


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s54ta66sxtev4cm6ttvm.png)


Nuxt Route Rules keep the site speedy by allowing different rendering modes based on specific routes – an uncommon feature for other frameworks. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aretg3e84vknu8ds3m3j.png)

For example, the landing page data is fetched from the Directus backend, but uses a stale while revalidate caching pattern for performance.

I also setup a proxy for the Clearbit Logo API to prevent hounding their server all the time. The site uses their API to fetch the logos for companies based on the website you enter. 

### **Landing Page**

The event landing page uses a “page builder” concept where anyone on our marketing team can update the layout on the page and add new components like card groups or faqs.

On the backend, this is setup using Directus’ [Many-to-Any (M2A) relationships](https://docs.directus.io/app/data-model/relationships.html#many-to-any-m2a). Each block can has different schema.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tjw523z0sd3kt6c1ym88.png)


It all comes together on the Nuxt side. The data is fetched from the Directus backend, and then passed to a `PageBuilder` component that is responsible for looping through an array of blocks and rendering the components dynamically.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l98pgck1a129nln36soy.png)



### **UI**

The site uses the [Nuxt UI](https://ui.nuxt.com/) library for a lot of the basic components like buttons and form inputs. Nuxt UI in turn uses libraries like TailwindCSS and Headless UI. It’s pretty easy to theme and uses tailwind-merge to manage class conflicts. 

It really saved me a lot of time by not to re-create some of the more “rich” components like comboboxes or dropdown menus.

## Generating AI Rabbitars **🧱**

The actual rabbitar images are generated using OpenAI’s Dall•E 3. Currently, the average user generates ~1.52 avatars costing us a total of ~$0.0608 per registrant. We have set a hard limit of 3 generations to prevent any crazy scary OpenAI bills.

There is a Nuxt server route that calls the OpenAI API, saves the generated image to the Directus instance, and updates the avatars generated by the user.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0j6tjmmaewlnz9r0sy48.png)


# The Challenges 🪨

There were more than a few challenges I faced with this thing. 😅

### Referral Tracking

We wanted to offer more chances in the giveaway for referrals so we needed to build a mechanism to control that. 

Once you generate your personalized rabbitar - you can share it to increase your odds of winning. Each person your refer earns you another entry in the giveaway. 

To track this, we tag the visitor with a `referral_ticket_id` cookie whenever they visit a registrant personal url. Whenever a visitor registers for the event, we check for the cookie, and update a `referred_by` field inside our Directus backend.

This is surfaced to the registrant as a “Swag-O-Meter” on their personalized ticket page.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9orpd12patec33e07pw6.png)



### **Function Timeouts**

[Leapweek.dev](http://Leapweek.dev) is hosted on Netlify. We’ve got a number of our other projects hosted there and I’m pretty familiar with the workflow. With Nuxt, there’s not really much configuration to be done, aside from connecting your repo and adding your ENV variables. 

But Dall•E 3 currently takes roughly between ~15-21 seconds to generate a rabbitar for the site. In local development this wasn’t a problem, but once deployed to Netlify, we were getting timeouts on the serverless functions because the default timeout is 10 secs. 

The Netlify support team was right there to help us out. They increased our limit to 26 secs and we’ve not had anymore issues.

### **Long URLs**

Originally we wanted to run this off a subdomain of the site. But [`https://leapweek.directus.io/tickets/bryant-gillespie`](https://leapweek.directus.io/tickets/bryant-gillespie) eats up a lot of characters and shorter urls are better for sharing. We’re really digging Dub.co for sharing our content on socials, but it just wasn’t a fit here for generating links.

So we chose the [`leapweek.dev`](http://leapweek.dev) domain over `leapweek.directus.io`.

But we could do better.

**Nuxt Alias**

The alias property within Nuxt’s definePageMeta makes it super easy to generate aliases for a specific route. So the page at `/tickets/bryant-gillespie` can also be rendered at `/t/bryant-gillespie`.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/452830sy9193dpvako81.png)



Which gives us a final url like:

`https://leapweek.dev/t/bryant-gillespie` 

### **Dynamic OG Images and Caching**

Dynamically generated OG images are really freaking cool, but it’s hard to ensure they render perfectly on different social media platforms. Each platform tends to have it’s own cache for OG images, making it harder to figure out than the Water Temple in Ocarina of Time.

For actually generating the dynamic social share images and caching them, we use the [Nuxt OG-Image module](https://nuxt.com/modules/og-image) by Harlan Wilton. It abstracts away a lot of the complexities of serving up dynamic social images. 

Under the hood, it uses [Satori by Vercel](https://github.com/vercel/satori) to render the images from a Vue component. But because of that there are some caveats about component structure and how you can style your images. 

When someone updates their avatar, we also need to purge the cached image so we don’t show the previous one. That’s handled inside a Nuxt server route as well.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mk8xtnkh4vbd3x1xtmmw.png)



# The Results


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uzw55xo5cqnuc8d28hf9.png)



I'm pretty happy with the results so far.

The site just launched on Monday this week and we already have over 300+ registrants and 475 rabbitars generated.

There's been 0 promotion aside from a few tweets from our team and a single LinkedIn post.

And as far as I know, we now have the world's largest collection of rabbit avatars. 

So if you ever need 100s or 1000s of rabbit headshots, consider me your guy 🤣. 

If you're interested and want to poke around the site and generate your rabbitar - go for it. You can check out the site at https://leapweek.dev