Since NextJS handles both backend and frontend seamlessly, emailing things now become another quest to finish, especially when you need to use template. This blog will be filled with my rants and lessons about this quest of creating my contact page. You can also check my logs for this in issue chez14/christianto.net#13.
Previously, in a world where backend handles everything (data flow, frontend codes, etc), emailing something in html format is just another common task to do. All we need to do is just make a template, make sure everything works and compatible with intended receiver, call the template engine to render the template to a variable, and then just send the captured html content, with your library of choice.
In NextJS however, it’s not that easy. React handles things for browser and not email clients, and we can’t just export email-compatible static html element from it, because all styling are not included in one giant string! In addition to that, React might include JS dependencies too, in which, its certainly won’t run in email clients. So the solution is to add our choice of html templating engine.
That being said, adding them isn’t just adding dependencies for the engine and done. The engine need to be able to read the template file, and render things properly too. Since the template file isn’t related with the main site, this is the main challenge in my NextJS project to solve.
The Quest
Let’s start with simple template. This is my email template for Contact Page:
The template contains simple stylings in the head
, and a body content in pug. In normal backend stuff, we can just refer this by following code:
Notice how the template is loaded? I’m just using the old-plain path.join
, because I thought it can just be loaded like that, and the file will be transported alongside everything, but apparently, not! Both NextJS and Vercel only cares what Webpack says.
Because of this, there are several options we can try:
- Use
copy-webpack-plugin
to manually copy the file (or folder). - Use
file-loader
, and load it from there. - Use
raw-loader
, and load the string directly.
So let’s start the journey with one that are the most effortless first: loading them with copy-webpack-plugin
.
copy-webpack-plugin
Originally, I thought this plugin will also add some kind of entries on the NFTs since it actually copy files directly from the normal path to build path, but apparently it is not. The copied file do get copied to the build folder (in this case, .next
folder), but it does not get collected to .vercel/output
folder, because the plugin doesn’t add something to the NFTs. This means loading on prod is not possible.
file-loader
Then continue with file-loader
. We want the pug themselves decide how to load template file. If the template file quite big, they might have mechanism to load them chunk by chunk, though, it might be rare occasion for the template to be more than few MBs big. So this is my reason why I want to try this option.
After configuring (commit e7d23403
), immediately when we test it on local, it can’t even find the location mentioned by the file-loader
. The file is renamed, and the folder isn’t original path anymore, as expected. The unexpected part of this are, NextJS dev server’s current working directory is still in the project root, not the .next
folder. Webpack’s loader’s root directory are set to the .next
folder, so upon loaded, the file’s path is actually set to that path, not project path.
If I’m not lazy, I might need to create a new library to handle cases like this, but since one of my requirement of my own website is “be as effortless as you can be”, making another library isn’t one of them. In addition to that, I actually kind of afraid that adding extra loaders as dependencies will actually be a not maintainable solution. When making this blog, I just realized that file-loader
is actually have been abandoned, and users informed to use it’s predecessor instead: asset-loader
, in which, I actually haven’t tried it yet.
To summarize, with file-loader
it can be imported via Webpack, but the path informed by the loader is sort of unusable. So, because of this bummer, I don’t want to continue to explore this option any further.
raw-loader
raw-loader
will actually load the file to memory—well, in this case, to variable— every time we import the thing in the script. As mentioned earlier, this is not an ideal solution, because if the file is really big, it will just eats a lot of memory! But well, since it’s a rare occasion for this case, and are I don’t have any other way, this is the last resort I should try.
This is how the code looked like after we configure the module declaration for Typescript (commit 98b99e3b
):
So that’s how I did it for now. It works, at least for now, and I don’t have to maintain another library for Webpack/NextJS.
Another Potential Solution
Putting the file to public path of NextJS project will actually bring the file to .vercel/output
folder. This… might works, but it violates my personal standards:
- Never serve any files that doesn’t meant to be accessible from public.
Learning from earlier experience with managing one of my client website, you never know what files in thenode_modules
orvendor
folder. There might be vuln or backdoor from dependencies poisoning that you’re unaware of. - If possible, never put said file into folder that are served by the web server, even if you can block the access to those files/folders.
For example, you putnode_modules
orvendors
folder inpublic_html
, then you block them in.htaccess
. This might work for now, but when your infra team decide to try experimenting to use Nginx, the folder is accessible again because Nginx ignore your.htaccess
file. Your Infra team now have to add another config for those.
Oh anyway, there’s another problem with this too: getting the correct path! As mentioned in file-loader
section earlier, loading file from fixed folder like this is very tricky! I have to dig deep to NextJS code to see which variable or config classes that provides the necessary information to the path I’m interested with.
So with those potential problems, I decided that this wont be a good fit for this exploration.
Conclusion
So, after testing all those options we can choose, I settled with using raw-loader
due to properly obtaining the path to the file is tricky. Especially because NextJS Dev Server current working directory expectedly runs on project root instead of build folder. There are another solution: put them in public path, but putting the file on this folder is actually makes me uncomfy because it violates my personal standards. That being said, the feature works pretty well, for now.