Create a portfolio with Nuxt3 + Tailwind CSS + Three.js and deploy it automatically using GitHub Actions

Naoyuki Hirasawa
11 min readMar 24, 2023


with 3D fractal visual

Portfolio on PC & Smartphone

Hi, I am FTD, an artist & creator working in the digital realm. I renewed my portfolio (also got a domain name). I am trying to create fractal shapes in three dimensions as the main visual.

I have introduced various technologies that I am using for the first time for study purposes, so I am writing this article as a reminder.

When you look for information in Japanese for beginners in web development, you start from “Let’s make a simple website with HTML+CSS+JS”, and when you start digging a little deeper, you suddenly find a lot of unfamiliar words and concepts that you don’t understand well. Also, you will find many articles and suspicious online schools that recommend making money with WordPress. This is not WP’s fault, but the problem is that the search results by Japanese are polluted. How about other areas?

This article is comprehensive from introduction to deployment (in a crude manner), so if you are not a web specialist but would like to create a web site, this may be helpful for you. If you find it useful in any way, I would be happy if you share it on social media.

*The original article is in Japanese, so there may be some discrepancies in the English expressions. Also, many of the referenced articles contain information in Japanese.

Vue2 to Nuxt3

My previous portfolio hosted on GitHub Pages was created with Vue2 (+Vue CLI + Vuetify), but as mentioned in the article above, the UI library Vuetify and I were really incompatible, so I created only a minimal structure and left it alone. I had been doing activities like Creative Coding for a long time, but my motivation disappeared and I was in a sorry state with no playful elements in terms of appearance and interactions. The fact that it has been just over a year or so made me think of renewing it.

Also, around the time of the last portfolio production, Vue3 became the default version, so Iupdated it to match this renewal. I also looked into Nuxt.js, a popular framework for making Vue.js easier to use, and decided to give that a try as well. As a result, I decided to renew our portfolio using Nuxt3.

I referred to the following article when introducing Nuxt3. Compared to Nuxt2, there is less information and I think there were many points that I got stuck in the middle of development, but I will introduce them later or in another article when I feel like it.

Also, not limited to Nuxt, but when you get to the stage of using frameworks to streamline your web production, you will encounter more SPAs, SSGs, and other mysterious strings of text. As in the last time, I had forgotten everything, so I had to look it up again. I’ll also post the article I referred to in that case. By the way, it seems that SPA is easily used to create administrative screens.

Introducing Tailwind CSS

I don’t want to use a UI framework, but I also need to know what’s trendy, so I introduced Tailwind CSS, a CSS framework. It is not a one-shot installation of stylish buttons, but rather the application of utility classes prepared in advance on the framework side to HTML elements to save time and effort in writing (the demo at the official top page is easy to understand).

By the way, I also installed DaisyUI, but did not use it much. It seems that coding by myself is more suitable for me, even if it is a little troublesome.

The official procedure introduces the installation method using Tailwind CLI, but if you are using Nuxt environment, the modular version (Nuxt Tailwind) is available and it seems to be more convenient, so I noticed it later and replaced it. Tailwind tends to be large in size after building, but the modular version includes PurgeCSS from the beginning, so you can reduce the size by deleting unused classes and improve the website speed.

Other references, including those around the introduction, are listed below.

In the process of development, I created various patterns of components for Nuxt, including Tailwind, and uploaded some of them to GitHub as well. However, in order to create a responsive navigation bar with animation and a grid layout, I have ported the old code that used regular CSS, Sass, and jQuery, and the current site is a mess of coding notations. I’ll rearrange it when I feel like it.

Introducing Three.js in Nuxt3 environment

Lee Perry-Smith

Now that the portfolio has been structured to some extent, we will now add in some good visuals. This time, we will introduce Three.js, a library for creating 3D content in JS. It is so famous that even I use it occasionally in my work. The image has nothing to do with it, but for some reason it is one of the most well-received examples.

By the way, if you want to install in Nuxt2 environment, @misaki_mofu’s article and code are very helpful, so you don’t need to look at mine in particular. Here is an excerpt of what I found a little problematic when installing in Nuxt3 environment.

Writing shaders

impot glsl files

You may use raw-loader (read the contents of the file as text) to load shader files (.flag, .vert, .glsl, etc.). However, with Nuxt3, the default build tool is Vite, so you do not need to install a separate loader if you add a special suffix like “hoge.glsl?raw” when importing a shader file. (By the way, you can change back to Webpack from Vite by setting nuxt.config.ts).

Implementing Event Bus

There are times when you want to transform the objects drawn in Three.js in response to changes in the DOM, such as when a page transitions or a button is pressed. In Vue2, it was possible to use the EventBus (also called Hub) mechanism to exchange global events, but in Vue3, the $on, $off, and $once methods seem to have been discontinued, as described in the official documentation. Instead, it is recommended to use external libraries such as mitt, which I have adopted.

The plugin is created in the plugins directory, but unlike Nuxt2, there is no need to add it to nuxt.config.ts. In the Three.js side source, event subscription is done via useNuxtApp(). Also, the following article is helpful.

Drawing 3D fractals (Mandelbulb)

Here I will talk about what to draw in Three.js. You can skip it if you’re not interested (though I’ll miss it).

I thought it would be more modern and stylish to draw a flat object that moves smoothly (and would be evaluated in competitions), but I decided to draw a fractal shape because it is my portfolio and I want to include something I like. The following is an excerpt from my thesis. (I doubt if it is correctly translated into English.)

A fractal is a geometric concept introduced by French mathematician Benoît B. Mandelbrot, and refers to a figure whose parts and whole are self-similar. Self-similarity refers to the fact that when one part of a figure is taken, the shape of that part is similar to the shape of the whole figure.

This time, I will draw the Mandelbrot set, one of the most famous fractals. In addition, I will also take advantage of Three.js to create a 3-dimensional version of the set. It was very difficult to adjust the colors to give it a certain degree of presence without being too assertive.

Although there is no exact 3-dimensional Mandelbrot set, by extending the complex numbers used in the calculation from polar to spherical coordinates, it is possible to plot a 3D fractal by plotting points that do not diverge infinitely. It is difficult for me to explain in detail due to my lack of comprehension, but I will post the wiki of Mandelbulb, which I referred to once upon a time. And thank you, Prof. Yamagishi, who helped me when I was a student.

Below is an old video I made with openFrameworks.

Fractal Audio Visualizer by oF

Creating a contact form

using Google Forms

When implementing an inquiry form, it is not easy to think about the server-side processing. A simple solution would be to post a link to a mail address and have the user’s mailer start up when clicks on the link.

This time, however, I wanted to prepare a proper input form screen, so we implemented a customized design while using the embedding function of Google Forms. The default embedding method makes the form look disappointing, but with this method, it is possible to create a contact form without spoiling the overall atmosphere of the site. The following article was very helpful. Incidentally, I heard that Netlify’s Forms function can also be easily implemented (I did not adopt it this time so that it can be used with other hosting services).

Configure OGP

OGP settings are also performed (the card that appears when the URL is shared on SNS, etc.), although it is always easy to forget to do so. If you do not need to change the content displayed on each page or set it dynamically, Nuxt will set the prefix and meta tags in nuxt.config.ts.

I was very confused when the cards did not show up on Twitter and note (even though the settings should be correct). I think the cache from when I tried to share before setting OGP might still be there. By the way, it took a little over a day to show up, though some pages still don’t show up on Twitter.

Not really related, but I asked ChatGPT and they kept giving me the old way of clearing Twitter cache, which was very stressful. I may put this story in a separate post since it seems to be in demand.

Build with SSG and upload files to Netlify

The article has been long, but the site is now complete in one step. I will try to build it with SSG (Static Site Generation) configuration. nuxt.config.ts is not particularly tweaked, and the “npx nuxi generate” command will output static files under .output/public (maybe even under dist (It would probably be OK to put the file under dist, but we are following the official documentation). Please also refer to the following.

Now that the set of files is ready, upload it to the hosting service. This time, I chose Netlify because it has a free plan and many people seem to be using it. I also recommend GitHub Pages, which I used in my previous portfolio (I changed it this time for study purposes).

Uploading is not particularly difficult as all you have to do is drag files. You can also link your repository to GitHub and have it build and deploy at the timing of the push, but after reading the following article, I decided against it (although I’m not likely to run out of my free allowance). By the way, if you are operating Netlify without thinking, it will prompt you to connect to GitHub, so be careful if you are concerned about it.

Get your own domain

Since I went through all the trouble of creating a portfolio, I decided to get my own domain name as well. I got mine from Google Domains, and since I usually only shop at the local supermarket, it was fun to choose a domain name. I used the following article as a reference for linking to Netlify.

Incidentally, when setting up DNS, there are many unfamiliar words that appear on the Google Domains setup screen, and ChatGPT was helpful here. When there are clear specifications and definitions, I found it easier and more convenient to use ChatGPT than search engines to find out the meaning.

Use GitHub Actions to automatically build and deploy when pushing to repository

I was able to get the portfolio published, but it is a pain to build and upload the files every time I make changes to the website. As mentioned above, we don’t want to have Netlify build the site, so we will use GitHub Actions to build the site and deploy it to Netlify. GitHub Actions is free for public repositories, and you can use up to 2,000 minutes of build time per month for private repositories. I prepared an automated build and deploy environment based on the following article.

Struggles on the path

I’ll also leave a note on a modest snag that I may be the only one. I specified .output/public as the input path for actions-netlify after the build, but it told me in error that it could not find the file. In fact, I just forgot to type a period just before output, but I spent a lot of time on unnecessary modifications such as “I found duplicated repository names in the path when debugging, so I tried specifying one level higher” and “I tried using an escape sequence for the period in vain”. As a result, I just wrote the path as usual and it worked. If I had specified ./dist, I wouldn’t have gotten into so much trouble. Also, ChatGPT pointed out the version difference of actions-netlify and I was confused.


This is a long story, but I wrote about the process from renewal of the portfolios to automatic deployment. The repository is also open to the public (as of the writing of this article). Web engineers, I would be happy to hear your advice if you like. If you find the article useful in any way, please share it. I am also looking forward to receiving your job offers.

Thank you for reading this far.



Naoyuki Hirasawa

FollowTheDarkside (FTD) - creative coder / visual artist / writer / bboy - Portfolio: