Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serverless Functions returning as Invalid URL #2441

Closed
marcdstudio opened this issue Jun 16, 2022 · 11 comments
Closed

Serverless Functions returning as Invalid URL #2441

marcdstudio opened this issue Jun 16, 2022 · 11 comments
Labels

Comments

@marcdstudio
Copy link

marcdstudio commented Jun 16, 2022

Describe the bug
Hi, I am attempting to configure serverless functions with Sanity to create a live content preview within the Sanity dashboard as seen here. I have setup the serverless function in the .eleventy.js file and am getting the correct output in the ./netlify/functions/serverless folder, but it seems the url that the template is attempting to use for the preview slug is returning as invalid which in turn blocks the preview pages from rendering. The error returned on the page is:

TypeError: Invalid URL: undefined
  new NodeError (internal/errors.js:322:7)
  onParseError (internal/url.js:270:9)
  new URL (internal/url.js:346:5)
  Object.handler (/Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/index.js:9:11)
  Object._executeSync (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:286:47)
  /Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:95:26
  new Promise (<anonymous>)
  Object.execute (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:87:16)
  Object.invokeFunction (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/runtimes/js/index.js:57:36)
  NetlifyFunction.invoke (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/netlify-function.js:85:41)

The error returned in the console is:

Request from ::1: GET /.netlify/functions/serverless
{"level":"error","message":"End - Error:"}
{"errorMessage":"Invalid URL: undefined","errorType":"TypeError","stackTrace":["new NodeError (internal/errors.js:322:7)","onParseError (internal/url.js:270:9)","new URL (internal/url.js:346:5)","Object.handler (/Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/index.js:9:11)","Object._executeSync (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:286:47)","/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:95:26","new Promise (<anonymous>)","Object.execute (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:87:16)","Object.invokeFunction (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/runtimes/js/index.js:57:36)","NetlifyFunction.invoke (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/netlify-function.js:85:41)"],"level":"error"}
Response with status 500 in 215 ms.

To Reproduce
Steps to reproduce the behavior:

  • Add EleventyServerlessBundlerPlugin Plugin to .eleventy.js:

config.addPlugin(EleventyServerlessBundlerPlugin, {
    name: "serverless",
    functionsDir: "./netlify/functions/",
 });
  • create pagination of posts:
---
layout: base.njk
pagination:
  alias: post
  data: post
  size: 1
  serverless: eleventy.serverless.path.slug
permalink: 
  build: /{{ post.slug }}/
  serverless: /preview/:slug/
renderData:
  title: "{{ post.title }}"
---
  • Run netlify dev
  • netlify/functions/serverless is automatically built
  • Encounter Error

Expected behavior
A new preview page would be built e.g. /preview/dynamic-slug with the live content from the sanity dashboard along with the original static page

Environment:

  • OS and Version: macOS Monterey 12.4
  • Eleventy Version: 1.0.1

Additional context
I reviewed multiple setups of serverless functions within the eleventy templates, and ultimately tried to follow the sanity walkthrough above as close as possible though I understand it was an older case and things may have changed since then. For additional context, I also tried different versions of the eleventy 1.0 e.g. beta versions as well as older versions of the @netlify/functions package but those unfortunately threw other errors. Any references for a working setup similar to this configuration or tips to debug this issue would be appreciated!

@zachleat
Copy link
Member

I’m curious if your serverless event is reporting a rawUrl value.

Can you log it out in ./netlify/functions/serverless/index.js? Should be something like:

async function handler(event) {
  console.log(event.rawUrl);

  let elev = new EleventyServerless("serverless", {
    path: new URL(event.rawUrl).pathname,
    query: event.queryStringParameters,
    functionsDir: "./netlify/functions/",
  });
  // …

event.path might work better for you there, here’s what it used to look like before we fixed #2221
image

@zachleat zachleat added the feature: 🏙 serverless Eleventy Serverless label Jun 17, 2022
@marcdstudio
Copy link
Author

Hey @zachleat, appreciate the quick response!

The event.rawUrl returns undefined while the event.path returns the correct url e.g. /preview/path. I tried replacing the url variable to be event.path instead of event.rawUrl in the ./netlify/functions/serverless/index.js:

  let elev = new EleventyServerless("serverless", {
    path: new URL(event.path).pathname,
    query: event.queryStringParameters,
    functionsDir: "./netlify/functions/",
  });

This however still returns the same invalid URL error as before. You can see the full log below with the event.rawUrl, event.path, and then the error:

event.rawUrl: undefined
event.path: /preview/test/
{"level":"error","message":"End - Error:"}
{"errorMessage":"Invalid URL: /preview/test/","errorType":"TypeError","stackTrace":["new NodeError (internal/errors.js:322:7)","onParseError (internal/url.js:270:9)","new URL (internal/url.js:346:5)","Object.handler (/Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/index.js:11:11)","Object._executeSync (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:286:47)","/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:95:26","new Promise (<anonymous>)","Object.execute (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:87:16)","Object.invokeFunction (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/runtimes/js/index.js:57:36)","NetlifyFunction.invoke (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/netlify-function.js:85:41)"],"level":"error"}
Response with status 500 in 141 ms.

I also tried to replace the entire variable with event.path seen here:

let elev = new EleventyServerless("serverless", {
    path: event.path,
    query: event.queryStringParameters,
    functionsDir: "./netlify/functions/",
  });

Which then resulted in the following Serverless Error:

Serverless Error: EleventyConfigError: Error in your Eleventy config file '/Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/eleventy.config.js'. You may need to run `npm install`.
    at TemplateConfig.mergeConfig (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/TemplateConfig.js:261:15)
    at TemplateConfig.getConfig (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/TemplateConfig.js:122:26)
    at new Eleventy (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/Eleventy.js:74:39)
    at Serverless.getOutput (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/Serverless.js:192:16)
    at Object.handler (/Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/index.js:17:29)
    at Object._executeSync (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:286:47)
    at /Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:95:26
    at new Promise (<anonymous>)
    at Object.execute (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js:87:16)
    at Object.invokeFunction (/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/runtimes/js/index.js:57:36) {
  originalError: Error: Cannot find module './lib/serializers'
  Require stack:
  - /Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/eleventy.config.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/TemplateConfig.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/TemplateRender.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/TemplateData.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/Eleventy.js
  - /Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/index.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/runtimes/js/index.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/runtimes/index.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/registry.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/server.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/commands/dev/index.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/@oclif/config/lib/plugin.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/@oclif/config/lib/config.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/@oclif/config/lib/index.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/node_modules/@oclif/command/lib/command.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/node_modules/@oclif/command/lib/index.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/index.js
  - /Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/bin/run
      at Function.Module._resolveFilename (internal/modules/cjs/loader.js:902:15)
      at Function.Module._load (internal/modules/cjs/loader.js:746:27)
      at Module.require (internal/modules/cjs/loader.js:974:19)
      at require (internal/modules/cjs/helpers.js:93:18)
      at Object.<anonymous> (/Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/eleventy.config.js:4:24)
      at Module._compile (internal/modules/cjs/loader.js:1085:14)
      at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
      at Module.load (internal/modules/cjs/loader.js:950:32)
      at Function.Module._load (internal/modules/cjs/loader.js:790:12)
      at Module.require (internal/modules/cjs/loader.js:974:19) {
    code: 'MODULE_NOT_FOUND',
    requireStack: [
      '/Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/eleventy.config.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/TemplateConfig.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/TemplateRender.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/TemplateData.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@11ty/eleventy/src/Eleventy.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/netlify/functions/serverless/index.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/lambda-local/build/lambdalocal.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/runtimes/js/index.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/runtimes/index.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/registry.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/lib/functions/server.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/commands/dev/index.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@oclif/config/lib/plugin.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@oclif/config/lib/config.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/@oclif/config/lib/index.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/node_modules/@oclif/command/lib/command.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/node_modules/@oclif/command/lib/index.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/src/index.js',
      '/Users/jakepfahl/Documents/Web/lecolonial/node_modules/netlify-cli/bin/run'
    ]
  }
}
Response with status 500 in 163 ms.

@marcdstudio
Copy link
Author

Hey @zachleat, following up here. Any ideas how I could further debug the issue to get the functions working?

@zachleat
Copy link
Member

Hey, I think there are probably a few issues to go through here. If you’re not seeing rawUrl in your event object I’d start with the assumption that your netlify CLI dependency is outdated? I’d try to update it first.

Next it seems like you’re missing the ./lib/serializers dependency in your serverless bundle. If you require it in your eleventy config it should be bundled for you automatically but if the require is nested deeply inside of a function or class it may fail? You can move the require or you can use the copy option in the serverless bundle to copy your lib folder manually to the bundle https://www.11ty.dev/docs/plugins/serverless/#bundler-options

@marcdstudio
Copy link
Author

Hey @zachleat that worked! Thanks for clarifying. Although I'm now running into the same issue that Knut was having in the video linked above where all pagination pages are returning the data from the first document in the array of documents.

For example, /preview/page-a with a title of 'Page A' in the data will return the correct title of 'Page A'. But /preview/page-b with the title of 'Page B' in the data will return 'Page A'. I attempted to add the serverless: eleventy.serverless.path.slug to the pagination to try and sync the data, but that returned an error of "error": "json.filter is not a function" : Could not find pagination data. Is there something I'm missing here?

---
layout: base.njk
pagination:
  alias: post
  data: post
  size: 1
  serverless: eleventy.serverless.path.slug
permalink: 
  build: "/{{ post.slug }}/"
  serverless: "/preview/:slug/"
---

@marcdstudio
Copy link
Author

marcdstudio commented Jul 1, 2022

Hi @zachleat wanted to follow up with the solution I came up with to assign the correct data for each paginated page, but would be interested to hear if there's a better way to do it:

async function handler(event) {
  const targetUrl = event.path.replace('/preview', '')

  let elev = new EleventyServerless("serverless", {
    path: new URL(event.rawUrl).pathname,
    functionsDir: "./netlify/functions/",
  });

  try {
    let allPages = await elev.getOutput();
    let targetPage = allPages.find((p) => p.url == targetUrl)    

    return {
      statusCode: 200,
      headers: {
        "Content-Type": "text/html; charset=UTF-8",
      },
      body: targetPage.content,
    };
  } catch (error) {
    if (elev.isServerlessUrl(event.path)) {
      console.log("Serverless Error:", error);
    }

    return {
      statusCode: error.httpStatusCode || 500,
      body: JSON.stringify(
        {
          error: error.message,
        },
        null,
        2
      ),
    };
  }
}

It seems that the default index.js script assumes that the page.content assigned to the body should always come from the first object returned from elev.getOutpout e.g. let [page] = await elev.getOutput when instead it needs to match the event path with the corresponding page url value returned in the array. Maybe this is what the serverless option is for in the pagination options, but I couldn't get it to work when I added it as shown above in the previous reply.

@marcdstudio
Copy link
Author

hey @zachleat could you clarify why elev.getOutput returns an array of all documents within the paginated template even after specifying the path in the EleventyServerless options? I got the above solution to work by filtering through these documents, but on some of the sites with high volumes of documents within a paginated template the netlify function times out.

Ideally the path specified in the options Is the only document fetched during elev.getOutput. Then the default let [path] = elev.getOutput would work because there would always only be one document returned. Currently, in an array of ['article-a', 'article-b', 'article-c'] all articles are fetched during elev.getOutput, so when article-b triggers the function article-a is being returned no matter which path is specified. Am I missing something in the options?

@treb0r
Copy link

treb0r commented Sep 1, 2022

Same issue here. The serverless function always returns the first item in the pagination array. Using 2.0.0-canary.15.

@ArmandoAmador
Copy link

Hi @zachleat wanted to follow up with the solution I came up with to assign the correct data for each paginated page, but would be interested to hear if there's a better way to do it:

async function handler(event) {
  const targetUrl = event.path.replace('/preview', '')

  let elev = new EleventyServerless("serverless", {
    path: new URL(event.rawUrl).pathname,
    functionsDir: "./netlify/functions/",
  });

  try {
    let allPages = await elev.getOutput();
    let targetPage = allPages.find((p) => p.url == targetUrl)    

    return {
      statusCode: 200,
      headers: {
        "Content-Type": "text/html; charset=UTF-8",
      },
      body: targetPage.content,
    };
  } catch (error) {
    if (elev.isServerlessUrl(event.path)) {
      console.log("Serverless Error:", error);
    }

    return {
      statusCode: error.httpStatusCode || 500,
      body: JSON.stringify(
        {
          error: error.message,
        },
        null,
        2
      ),
    };
  }
}

It seems that the default index.js script assumes that the page.content assigned to the body should always come from the first object returned from elev.getOutpout e.g. let [page] = await elev.getOutput when instead it needs to match the event path with the corresponding page url value returned in the array. Maybe this is what the serverless option is for in the pagination options, but I couldn't get it to work when I added it as shown above in the previous reply.

Thank you for this.

@ArmandoAmador
Copy link

Hi @zachleat wanted to follow up with the solution I came up with to assign the correct data for each paginated page, but would be interested to hear if there's a better way to do it:

async function handler(event) {
  const targetUrl = event.path.replace('/preview', '')

  let elev = new EleventyServerless("serverless", {
    path: new URL(event.rawUrl).pathname,
    functionsDir: "./netlify/functions/",
  });

  try {
    let allPages = await elev.getOutput();
    let targetPage = allPages.find((p) => p.url == targetUrl)    

    return {
      statusCode: 200,
      headers: {
        "Content-Type": "text/html; charset=UTF-8",
      },
      body: targetPage.content,
    };
  } catch (error) {
    if (elev.isServerlessUrl(event.path)) {
      console.log("Serverless Error:", error);
    }

    return {
      statusCode: error.httpStatusCode || 500,
      body: JSON.stringify(
        {
          error: error.message,
        },
        null,
        2
      ),
    };
  }
}

It seems that the default index.js script assumes that the page.content assigned to the body should always come from the first object returned from elev.getOutpout e.g. let [page] = await elev.getOutput when instead it needs to match the event path with the corresponding page url value returned in the array. Maybe this is what the serverless option is for in the pagination options, but I couldn't get it to work when I added it as shown above in the previous reply.

Were you able to handle the scenario where you need preview/page-a/nested-page?

Eleventy tells me Cannot GET /preview/page-a/nested-page/index.htm

I can't seem to figure out if it's a eleventy issue or if I need to add an extra route to my netlify.toml file. Something that can handle a wildcard instead of

[[redirects]]
from = "/preview/:slug/"
to = "/.netlify/functions/preview"
status = 200
force = true
_generated_by_eleventy_serverless = "preview"

[[redirects]]
from = "/preview/"
to = "/.netlify/functions/preview"
status = 200
force = true
_generated_by_eleventy_serverless = "preview"

@zachleat
Copy link
Member

zachleat commented Apr 9, 2024

Stale per project slipstream changes in #3074.

@zachleat zachleat closed this as not planned Won't fix, can't repro, duplicate, stale Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants