Serverless express with handlebars

I had hbs loaded in the main package.json.
However I ended up using my own minimalistic template using plain JS template literal, since it seems clear Netlify functions can’t really load/find non-JS files.
Maybe that’s obvious to other people, but the documentation is in general lacking of specifying what is the expected file structure in the production environment.
Also I believe the result was different when running the netlify dev command, so I did not find that really helpful.

So, one of the things I’ve recently tested was accessing an arbitrary file in a deployed function.

First, you’ll need to zip your function yourself to allow you be sure those non-imported files are included in the function zip (example).

The next thing to note is that your lambda function is not executed inside your function’s folder. The execution path is store in the env var LAMBDA_TASK_ROOT (this is set by AWS directly). However, you function folder is under LAMBDA_TASK_ROOT/{function_name}. If your function name is here_is_my_function, the path will be LAMBDA_TASK_ROOT/here_is_my_function. You’ll need to make sure that the paths in your function reflect that (example).

If you see that the behavior is a bit different, feel free to file an issue here: https://github.com/netlify/cli/issues. Doing so would be greatly appreciated!

In any case, I hope that helps clarify how AWS executes lambda functions (which is what we deploy our functions to).

Thanks @Dennis, this is really useful info, I wish the documentation was as clear as you have been.
However, when you say “you’ll need to zip your function” you mean basically copy (not compressing) the files, right? I can do that with webpack for example, not necessarily using zip, can’t I?

And btw, do you have any idea why express would not find hbs among the installed npm modules when using app.set('view engine', 'hbs');? Is the set method maybe referencing the engine module not from the default node_modules folder?

When I mention ‘zip your function’, I do mean that you use zip to create a real zip file containing your function folder.

As far as the set method in express, I’m not sure but I think it does its own look up in your node_modules folder and doesn’t rely on any imports you define. Like I said, manually zipping your function folder with all the necessary files (node_modules, etc.) would probably be the way.

Why is zipping the content necessary, and why just copying the files would not work?
I don’t think I ever found any mention of this practice in the documentation and to me it just seems counterintuitive.

If you copy your files over, our buildbot will use zip-it-and-ship-it to zip your function up. If you don’t import or require the file, it will not be included in the function deploy. This is described here: https://docs.netlify.com/cli/get-started/#unbundled-javascript-function-deploys. If you want to include artibrary files that you do not import in your function, then you will need to zip your function yourself. Our buildbot will then know to just go ahead and deploy the zip file without further processing.

I think using templating engines with netlify is not a good option, like vercel.
They have clearly mentioned the limitations in their docs that it is not a good option for many reasons.

Read this https://vercel.com/guides/using-express-with-vercel#templating-and-view-engines

Hello everyone!

Long time no see on this post.
Thanks for the info @zippytyro, I was not aware of that.

These days I use some sort of mixed solution, which I believe is suitable for my simple email template needs. The solution goes something like this:

  1. export the email template from a pure js file with placeholders
module.exports = `
  Hello {{ name }}. Thanks for reading my newsletter
`;
  1. import it when needed and compile it in place with handlebars
const loadTemplate = (type) => {
  // load file and return it as a string
};

const compileTemplate = (data, type) => {
  // replace placeholders with handlebars and return the final string

  const template = handlebars.compile(loadTemplate(type));
  return template(data);
}

What do you think about this appoach? I would love to hear your opinion.
All the best,
Victor

1 Like

That’s an interesting approach :+1: . Have you tested it to see if it works? It’s a bit outside the scope of Support at this point but I’d be interested to see how it works out for you. Mostly, I’m trying to see what the advantages are over a static site generator. :thinking:

Hi @victorocna I’m very curious to see if you have a sample repo with your newly revised method. I’m trying to do the same thing and could use a little guidance.

hey there, @jhsu :wave:

Sorry to hear you are encountering obstacles. Can you share a little bit more about what you are trying to do? Additionally, could you share a link to your site with us? This will help us provide the appropriate support to get you on track.

Thanks!

Hi @hillary sorry for the delayed response. I made some progress but am a stuck at trying to load a handlebars template. Here’s my repo and the deployed site.

If you look at ./functions/render/index.js I have the handlebars hardcoded to const data, but I’d like to separate the template into its own file. I tried to do so and received an ENOENT error.

Hey @jhsu

The issue with the template is you are using readFielSync to read a file that does not exist at runtime when it appears this file isn’t in an [included_files] list.

I have a very minimal Markdown renderer demo coelmay/ntl-ssr which is deployed to markdown–ntl-ssr.netlify.app.

1 Like

Ah okay so in your netlify.toml you specify the functions directory and then for specifically the function named posts to include all files in the posts directory that have a markdown extension. Am I interpreting that correctly?

That is correct.

If you use netlify/functions there is no need to specify the functions directory as this is the default. In this instance you would have

[functions]
  [functions.post] # assuming the functions is called post.js
    included_files = ["posts/*.md"] # assuming this is where include files are

You can specific the functions directory in the UI also. If you add the directory to the netlify.toml this will override the value set in the UI.

1 Like

@coelmay this is exactly what I’m looking for, but I’m still running into issues with the ENOENT error. I’m pretty sure it’s something silly, but I’m new to this so I’m at a bit of a loss for troubleshooting.

Try changing

 const data = fs.readFileSync('./template.hbs', 'utf8');

to

 const data = fs.readFileSync('./templates/template.hbs', 'utf8');

I suggest leaving the styles.css file in the site root, and linking to it instead e.g.

<link rel="stylesheet" href="/styles.css" />
1 Like

You completely rock!! That did the trick. I’m curious about the css file being in the site root… so eventually I need to have 3 functions (1 per template). My original plan was to structure it like this:

site/
  index.html
functions/
  template1/
    template1.js
  template2/
    template2.js
templates/
  template1/
    template1.hbs
    template1.css
  template2/
    template2.hbs
    template2.css

What you’re recommending would be to put the 3 css files under site/ but why is that?

The templates directory is not accessible outside the function, so any files referenced in it (e.g. via <link href="">, or <img src="">) aren’t accessible once the function finished rendering and returns the content.

Your directory structure is a little overly complex. Simplify

.
├── functions
│   └── template.js
├── templates
│   ├── template1.hbs
│   ├── template2.hbs
│   └── template3.hbs
└── site
    ├── template1.css
    ├── template2.css
    └── template3.css

With the included_files use a wildcard e.g.

included_files = ["templates/*.hbs"]

You could have three separate functions and use static routing to specific paths to a function, or have a single function that determines which template to load based on the path.

1 Like

All that makes sense, thanks again for the help!