nuxt / nuxt

The Intuitive Vue Framework.
https://nuxt.com
MIT License
54.67k stars 5k forks source link

AWS Lambda deployment not serving files from public/_nuxt folder #13478

Closed KaziSadmanAhmed closed 4 months ago

KaziSadmanAhmed commented 2 years ago

Environment


Reproduction

Describe the bug

The deployed function will not serve anything from the /_nuxt path. Either it will return the default welcome page or the 404 error page if you setup pages.

Additional context

No response

Logs

No response

LarryGF commented 2 years ago

I'm experiencing the same problem, from what I can see it also happens for ecerything under the public directory (all the assets)

bros4president commented 2 years ago

We faced the same issue today and after many hours looking for the culprit, we found that the 'aws-lambda' nitro preset does not support serving static files. This is probably by design, because AWS API Gateway does not support serving binary files by default, as far as I know.

We propose two fixes:

One fix is very simple, just need to extend the 'aws-lambda' preset in nuxt.config.js enabling the serveStatic option.

export default {
  nitro: {
    preset: 'aws-lambda',
    serveStatic: true
  }
}

Then, if using API Gateway, you'll likely have to enable binary file serving. Our project is build using Serverless Framework, so we just used serverless-apigw-binary plugin.

We found this option by digging into the source code of the 'node-server' preset. You can also find the preset definition for 'aws-lambda' in case you want to define your own 'aws-lambda-static' or something.

If you don't include this option, Nitro will not include a handler for static file routes, which will be handled by regular Vue Router handler. Which is the reason you get 404 (it is not that the server can't find the files).


A (maybe better) second solution is to not serve static files through lambda, because it's slower and more expensive compared to dedicated static file serving such a CDN. What you do in this case is to not include the serveStatic: true config and define your cndUrl in nuxt.config.js:

export default {
  nitro: {
    preset: 'aws-lambda',
  },
  app: {
    cndUrl: 'https://your-static-host.com/',
  }
}

cndUrl was previously known as publicPath in Nuxt 2

In this case, you have to upload the content of .output/public/ into https://your-static-host.com/. When using Serverless Framework, this would mean a manual process, but you could also automate it with serverless-finch (S3 Bucket) or serverless-lift (S3 + CloudFront Distribution) 👍🏻👍🏻👍🏻


We found the documentation lacking with regards to serving static files through lambda and Nitro config, so if any of this information is of help, we'd be happy to add it to the docs 🙂

pi0 commented 2 years ago

Thanks for very well written insights @bros4president. Certainly contributing to the documentation is more than welcome in order to improve aws docs. Also, I think adding a new preset that enables serve-static would be a good idea 👍🏼

Hyperblaster commented 2 years ago

@bros4president You are a lifesaver. The documentation is very lacking with regards to this, and your solution fixed the problem!!!

jerryisbusy commented 2 years ago

@bros4president Hello, may i know where i can find the details of 'serveStatic: true' ?

Lundi commented 2 years ago

@jerryisbusy There's documentation on the Nitro configuration, like serveStatic, in the Nitro docs here

jerryisbusy commented 2 years ago

@Lundi Thanks!

ffxsam commented 2 years ago

I'm getting stuck on this. I tried the following config:

  nitro: {
    preset: 'aws-lambda',
    serveStatic: true,
  },

but when my client tries to fetch public files, the page shows this:

{
  "url": "/_nuxt/manifest.json",
  "statusCode": 500,
  "statusMessage": "Internal Server Error",
  "message": "The \"path\" argument must be of type string or an instance of URL. Received undefined",
  "description": ""
}

I must be missing something basic here.

jpgupta commented 2 years ago

@ffxsam having the exact sme problem! Have you had any luck?

jpgupta commented 2 years ago

@ffxsam

After a bit of reading on some older posts related to Serverless and nuxt2, it seems like a recommended approach is to serve static assets from a CDN

I installed serverless-finch, configured it to deploy .output/public to an S3 bucket, then configured nuxt to use that S3 bucket as the CDN URL.

Nuxt 2 post (advice seems to broadly hold up for Nuxt 3 in this regard) - https://epndavis.com/blog/nuxtjs-serverless-side-rendering-on-aws-lambda/

Serverless finch plugin: https://github.com/fernando-mc/serverless-finch#readme

Steps:

1) install the serverless-finch package - $ npm i serverless-finch / $ yarn add serverless-finch 2) Update serverless.yml to include the following blocks:

plugins:

custom: serverless-offline: noPrependStageInUrl: true client: bucketName: {{ENTER A BUCKET NAME HERE}} distributionFolder: .output/public

3) build project etc (whatever steps you're using)

ffxsam commented 2 years ago

Yeah, I realized it can just be hosted on the CDN. I've created the following deploy script to handle this for me:

#!/bin/sh
set -ex

source .env

npx nuxi build nuxt
aws s3 sync --delete nuxt/.output/public $CDN_S3_URL
yarn sst deploy $*
uncle-samm commented 2 years ago

Don't want to be a pain, but could anybody create an example repo until the documentation is better? It would be greatly appreciated!

ffxsam commented 2 years ago

@uncle-samm https://github.com/ffxsam/nuxt-sst

uncle-samm commented 2 years ago

@uncle-samm https://github.com/ffxsam/nuxt-sst

My hero!

ffxsam commented 2 years ago

@uncle-samm It may not necessarily be exactly what everyone needs (as it's highly opinionated by using SST to build the infrastructure), but it's a 100% working-out-of-the-box repo.. to the best of my knowledge. 🙂

uncle-samm commented 2 years ago

@uncle-samm It may not necessarily be exactly what everyone needs (as it's highly opinionated by using SST to build the infrastructure), but it's a 100% working-out-of-the-box repo.. to the best of my knowledge. 🙂

Yes, I'll have to look into that but I do see a lot of advantages using SST. It's still relatively new for me, am used to Docker. However this seems like a logical next step for me

steffenstolze commented 2 years ago

I'm not even able to reproduce this manually.

I have a Nuxt v3 project with a /server/api/hello.ts file with some basic content

export default defineEventHandler((event) => {
  return {
    message: 'this endpoint works',
  };
});

this is my nuxt.config.ts

import { defineNuxtConfig } from 'nuxt';

// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
  nitro: {
    preset: 'aws-lambda'
  },
  ssr: false,
  typescript: {
    shim: false,
  },
  build: {
    postcss: {
      postcssOptions: require('./postcss.config.js'),
    },
  },
});

after running NITRO_PRESET=aws-lambda yarn build (which does a nuxt build) an .output folder is created, containing

image

I immediately ask myself why there is a renderer.mjs handler in the server sub-folder since I configured ssr: false in the Nuxt config.

In the AWS console (for testing purposes), I created a Lambda proxy integration:

image

Regarding the target Lambda, I've tried everything from the whole .output folder to only the content of the .output/server folder.

I've not been able to access my API at https://xxx.execute-api.eu-central-1.amazonaws.com/dev/api/hello

Any hints would be welcome!

Ideally I want to host the static parts (the SPA) on S3+CloudFront and provide the API part via APIGW+Lambda. But one step after another..

bros4president commented 2 years ago

I haven't built with ssr: false, but with ssr enabled, you map the lambda handler to .output/server/index.handler.

steffenstolze commented 2 years ago

I've checked the method execution in the AWS console for APIGW and the response gives a hint:

Tue Sep 13 23:26:58 UTC 2022 : Sending request to https://lambda.eu-central-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:eu-central-1:xxxxxxxxxx:function:nuxt-test/invocations
Tue Sep 13 23:26:58 UTC 2022 : Received response. Status: 200, Integration latency: 25 ms
Tue Sep 13 23:26:58 UTC 2022 : Endpoint response headers: {Date=Tue, 13 Sep 2022 23:26:58 GMT, Content-Type=application/json, Content-Length=168, Connection=keep-alive, x-amzn-RequestId=d167144e-5c2e-48fb-af46-ed0xxxxxxxxxx, x-amzn-Remapped-Content-Length=0, X-Amz-Executed-Version=$LATEST, X-Amzn-Trace-Id=root=1-632111c2-8502d608c69e6xxxxxxxxxx;sampled=0}
Tue Sep 13 23:26:58 UTC 2022 : Endpoint response body before transformations: {"cookies":[],"statusCode":200,"headers":{"content-type":"application/json","server-timing":"-;dur=0;desc=\"Generate\""},"body":"{\"message\":\"this endpoint works\"}"}
Tue Sep 13 23:26:58 UTC 2022 : Execution failed due to configuration error: Malformed Lambda proxy response
Tue Sep 13 23:26:58 UTC 2022 : Method completed with status: 502

it says Malformed Lambda proxy response what I can't understand yet, because the response seems to be fine:

{
  "cookies": [],
  "statusCode": 200,
  "headers": {
    "content-type": "application/json",
    "server-timing": "-;dur=0;desc=\"Generate\""
  },
  "body": "{\"message\":\"this endpoint works\"}"
}

This means the endpoint is called, forwarded to Lambda, which executes the right Nuxt server endpoint GET /api/hello and then has problems with the integration / method response.

The actual response is created by Nuxt / Nitro from my hello.ts endpoint

export default defineEventHandler((event) => {
  return {
    message: 'this endpoint works',
  };
});

it works locally though..

Any idea appreciated. And the Nuxt docs really lack some detail in this regard. I'm happy to document it once I figured this out

danielroe commented 2 years ago

@steffenstolze Your issue is likely the same as https://github.com/unjs/nitro/issues/504.

kublasean commented 1 year ago

Don't want to be a pain, but could anybody create an example repo until the documentation is better? It would be greatly appreciated!

For future readers in addition to the example from @ffxsam here is an example using AWS CDK to deploy to Lambda and S3. I also was not able to get it working until using a CDN for assets.

https://github.com/kublasean/nuxt3-cdk-lambda-hello-world

danielroe commented 4 months ago

If anyone is still encountering this, please open an issue in the nitro repository? 🙏