serverless-nextjs / serverless-next.js

⚡ Deploy your Next.js apps on AWS Lambda@Edge via Serverless Components
MIT License
4.47k stars 457 forks source link

Next.js 10 localization in pathname #721

Closed katspaugh closed 3 years ago

katspaugh commented 4 years ago

The new built-in localization in Next.js 10 allows to have the locale as a path prefix, e.g.:

/about
/de/about
/es/about

All three routes will open pages/about.tsx. This works fine when I run it locally. When I deploy it using serverless-next.js, it gives a 404 for /de/about and /es/about. Just /about still works.

Is this something that the Serverless Next.js Component doesn't support yet, or am I doing something wrong? Thanks!

dphang commented 4 years ago

Yeah, Next.js 10 just came out two days ago, so it'll take some time to add those new features, since we have custom routing/rendering logic optimized for Lambda@Edge. I've tagged this with enhancement.

But I've tested the existing end-to-end tests with Next.js 10 and nothing broke, so looks like all the existing features work as expected.

LuisFros commented 4 years ago

I am also have the same issue 404 on not root pages.

Lunnatica commented 3 years ago

Hi! I was wondering if there are any updates on this?

dphang commented 3 years ago

Yes, I have not had a chance to look at it yet, been mostly on vacation mode the past few weeks so haven't worked on the code recently. Hoping to get to it once I'm back next week :)

Lunnatica commented 3 years ago

Thanks for the update! :) If it helps, when I tried deploying my site with the i18n solution it worked well on all locales, except for the default one, which returned 404 for all routes unless I manually added the locale prefix to the URL path.

Hope you enjoy the rest of your holiday! :)

Arditc commented 3 years ago

Hi, I was wondering if there was any updates on this issue?

dphang commented 3 years ago

I am planning to continue on this during this week. Apologies since lately I was the only contributor for this, so haven't had enough time to both triage issues and work on the codebase..

From initial looks, Next.js will generate new pages for each locale specified (for SSG). So then we would need to just create new routes in our manifests to direct the locale-prefixed paths to the right SSG pages. And without a local-prefix, it should use specified defaultLocale instead. For SSR pages, there is only a single JS file.

benjipott commented 3 years ago

related to https://github.com/serverless-nextjs/serverless-next.js/issues/830

dphang commented 3 years ago

Have published @sls-next/serverless-component@1.19.0-alpha.29 which should have locale subpath routing working. Note that you do need to add Accept-Language header for the root-level locale redirect (it's added by default in default behavior, but if you override it, you will have to add it yourself).

I haven't extensively tested it nor added complete tests for it yet, so please treat it as a preview for now and report any issues by opening a bug report. Thanks!

Note: domain-based routing will be a separate issue

testreen commented 3 years ago

Thanks for the update, everything is working as expected for us, except for the index.js page when using getStaticProps.

For all locales, the base path returns an AccessDenied error due to index.html not being exported properly to the static-pages folder in S3. All other static routes are being exported properly. Using the trailingSlash config does not solve this issue, and changing getStaticProps to getServerSideProps renders the index path as expected. Let me know if you need more information!

dphang commented 3 years ago

@testreen thanks! Let me take a look and fix that today. I might have missed testing getStaticProps on index page (only with getServerSideProps.

Arditc commented 3 years ago

Hey, many thanks for your update!

I've bumped my app to the latest serverless version, and I seem to experience these issues:

Any ideas what could be causing this?

How to possibly reproduce this: my current next.config.js

module.exports = {
    i18n: {
        locales: ['en', 'es'],
        defaultLocale: 'en',
      },
}

this is my serverless config:

allthemeasurements:
  component: "@sls-next/serverless-component@1.19.0-alpha.29"
  inputs:
    bucketName: xxx
    domain: "xxx.xxx" # sub-domain defaults to www
    roleArn: arn:aws:iam::xxxx
    name:
      defaultLambda: UI
    cloudfront:
      distributionId: xxxx
      defaults:
        forward:
          cookies: "none"

image image

dphang commented 3 years ago

Yea, I will rework some of the changes today. Basically with locales then data files and pages would be put into locale-prefixed paths e.g /en/, however for locale index pages it is stored in root as en.html or en.json if you have made it statically generated, instead of index.json or index.html as expected. So it is why there is a 403 access denied.

As fix we need to copy en.html en.json files correctly and reference those instead. Probably easier to rewrite / -> /en internally so that is can reference the en.html page.

Also if using locales, we can just have locale directories e.g /en, /nl and then just rewrite all non-locale-prefixed URLs to add the default locale prefix. E.g / -> /en, /another -> /en/another, so we don't have to maintain default locale pages at the root level too. And also makes fallback page generation easier, since Next.js requests data file using locale path, e.g _next/data/build-id/en/not-yet-generated.json and we just need to generate a page /en/not-yet-generate.html.

benjipott commented 3 years ago

@dphang all's good for me in production , but i have the same issue mentioned here https://github.com/serverless-nextjs/serverless-next.js/issues/646 all SSG routes with getStaticPaths` fail with 503

all i18n sub routes with SSG are not uploaded to S3 with local path like /_next/data/buildID/fr/auth/signin.json

probably a problem with dataRoutes and 'dataRoutes` in routes-manifest

dphang commented 3 years ago

Thanks @benjipott for reporting, for that issue #646 I didn't have luck making progress on it. But will look at the other issue.

benjipott commented 3 years ago

@dphang with my latest pr approved and merged, i test only the upload to s3 but i think an other method filter this file because /fr/path/file.json is correctly present in next folder ... no idea why. I can check it in your related issue, thks @dphang

dphang commented 3 years ago

@johnaxe not sure, I did not change anything client-side, so perhaps it might be Next.js converting the link component to wrong href? Is that the same behavior on the local dev server?

krish-dev commented 3 years ago

I have noticed one use case that SSG with nextjs i18n only working from the root page (index.jsx). I put the same code on all pages for testing.

/              => working
/en            => working
/ssg-home.     => not working
/en/ssg-home   => not working

Here is my serverless.yml configuration

pocOptim:
    component: '@sls-next/serverless-component@1.19.0-alpha.31'
    inputs:
        memory: 512
        useServerlessTraceTarget: true

This is my i18n config for next.config.js

i18n: {
     localeDetection: false,
     locales: ['en', 'de'],
     defaultLocale: 'en'
},
dphang commented 3 years ago

@krish-dev thanks, I will take a look, not sure if it's due to serverless trace target (I have only tested without it)

krish-dev commented 3 years ago

You are correct @dphang, I did some testing without useServerlessTraceTarget: true then it works, but with useServerlessTraceTarget the behavior is the same as I described. However, useServerlessTraceTarget: true is very crucial for the application which has a very long list of SSR pages.

I have noticed one use case that SSG with nextjs i18n only working from the root page (index.jsx). I put the same code on all pages for testing.

/              => working
/en            => working
/ssg-home.     => not working
/en/ssg-home   => not working
krish-dev commented 3 years ago

@dphang any luck for useServerlessTraceTarget: true issue?

dphang commented 3 years ago

Haven't finished looking at it yet, I was fixing some other locale bugs and e2e tests. I need to check the .next output structure for serverless-trace target and see if files are being copied correctly. Planning to wrap up this feature this week.

peniakoff commented 3 years ago

Hi, @dphang ! What is the status for this fix? I noted that I have issue related to localization stuff. It is an error new Error('next-i18next was unable to find a user config') thrown from default-lambda/pages/index.js, function serverSideTranslations. It looks like I have no defined userConfig or something? I tried to fix it by manual copying next-i18next.config.js to default-lambda main folder, but after that I reached only another one error: ERROR Unhandled Promise Rejection { "errorType": "Runtime.UnhandledPromiseRejection", "errorMessage": "Error: Cannot find module '/var/task/next-i18next.config.js'", "reason": { "errorType": "Error", "errorMessage": "Cannot find module '/var/task/next-i18next.config.js'", "code": "MODULE_NOT_FOUND", "stack": [ "Error: Cannot find module '/var/task/next-i18next.config.js'", " at webpackEmptyContext (/var/task/pages/index.js:91038:10)", " at /var/task/pages/index.js:1552:90" ] }, "promise": {}, "stack": [ "Runtime.UnhandledPromiseRejection: Error: Cannot find module '/var/task/next-i18next.config.js'", " at process.<anonymous> (/var/runtime/index.js:35:15)", " at process.emit (events.js:314:20)", " at processPromiseRejections (internal/process/promises.js:209:33)", " at processTicksAndRejections (internal/process/task_queues.js:98:32)" ] }

poppein commented 3 years ago

I am having the same issues as @peniakoff . The problem is that the next-i18next.config.js file does not get bundled. You can pass the configuration directly to the method but then it complains that the locales files are missing. Any way to get them bundled ?

dphang commented 3 years ago

@poppein @peniakoff Sorry, I am not too sure how it works as I don't use next-i18next yet. You may need to use postBuildCommands to run a script to manually copy any needed files into the Lambda folder before the upload. As this component does not know about those additional plugins. Feel free to share the script and/or integrate the copying for next-18next.

dphang commented 3 years ago

@krish-dev sorry for delay, been busy last couple of weeks with moving. I did test the e2e locales test app (next-app-with-locales) in different ways but still could not reproduce any issue that you saw. SSG assets are copied correctly and am able to view ssg page under /fr/ssg-page and also /en, /fr etc. For example: https://d1f9sulga5gpon.cloudfront.net/fr/ssg-page.

Here is how assets looks like, all data files and pages are copied correctly:

Screen Shot 2021-03-24 at 1 41 28 AM

In default lambda pages are also copied correctly (actually no difference from non-locale app since these are only the SSR pages), they are liightweight pages + node_modules folder:

Screen Shot 2021-03-24 at 1 50 15 AM

If possible could you please try to modify the e2e-tests/next-app-with-locales app to reproduce the issue. Maybe there is some other config that is different?

rmulder commented 3 years ago

Getting the following deployment error now, which did not exist in previous versions: error: RequestEntityTooLargeException: Request must be smaller than 69905067 bytes for the UpdateFunctionCode operation at Object.extractError (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/protocol/json.js:52:27) at Request.extractError (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/protocol/rest_json.js:55:8) at Request.callListeners (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/sequential_executor.js:106:20) at Request.emit (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/sequential_executor.js:78:10) at Request.emit (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/request.js:688:14) at Request.transition (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/request.js:22:10) at AcceptorStateMachine.runTo (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/state_machine.js:14:12) at /Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/state_machine.js:26:10 at Request. (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/request.js:38:9) at Request. (/Users/rammarketinginternational/.serverless/components/registry/npm/@sls-next/serverless-component@1.19.0-alpha.41/node_modules/aws-sdk/lib/request.js:690:12) { message: 'Request must be smaller than 69905067 bytes for the UpdateFunctionCode operation', code: 'RequestEntityTooLargeException', time: 2021-04-02T02:29:38.673Z, requestId: '6d575d0d-6130-4d86-934b-6782f7cbc410', statusCode: 413, retryable: false, retryDelay: 91.18280823581092 }

dphang commented 3 years ago

@rmulder I believe your issue is this: https://github.com/serverless-nextjs/serverless-next.js/issues/979 please follow or reply to that issue instead of here, since it should not be related to Next.js 10 localization

rmulder commented 3 years ago

@dphang - You are absolutely correct. I re-ran the deploy with alpha.42 version (which is now out) - and that problem has been resolved. My code deploys, and thus far the i18n issues I had been experiencing have been resolved. Thanks!

royporter7 commented 3 years ago

@poppein @dphang I'm currently experiencing the next-i18next.config.js non-bundling error as well. Error: Cannot find module '/builds/<project>/next-i18next.config.js' at webpackEmptyContext (/builds/<project>/.next/serverless/pages/index.js:45452:10) at /builds/<project>/.next/serverless/pages/index.js:803:90

Can either of you share a solution which might help address this? I tried adding a postBuildScipts which looks like this fs.copySync('./next-i18next.config.js', '/builds/<project>/next-i18next.config.js', { recursive: true }); but no dice.

Any suggestions?

poppein commented 3 years ago

@rhoiyds I unfortunately revert back to using plain react i18next and not next-18next as i couldn't get it to bundle properly

royporter7 commented 3 years ago

@poppein Thank you for letting me know - however that is sad to hear. I'm afraid I might have to do the same, unless @dphang has found a solution.

gouroujo commented 3 years ago

@rhoiyds I ve managed to make it work by copying my next-i18next.config.js in both the root of my project (alongside next-config) and in my /src folder. I hope it will work for you.

pawelphilipczyk commented 3 years ago

Great to hear that @gouroujo! Did you manually copied this or do you have some task that you could share?

royporter7 commented 3 years ago

@gouroujo Thanks for sharing your solution! I had really high hopes for it, however, unfortunately still no dice. The same error as always. If there's anything else you can share that might help, it would be greatly appreciated.

royporter7 commented 3 years ago

Hi all, after days of headaches I've managed to find a workaround which seems to work for me. Here's some notable configs that managed to work for me, no guarantees for anyone else I suppose.

Removed target: 'serverless' from next.config.js. Also added i18n configs manually.

module.exports = {
  i18n: {
    defaultLocale: 'ja',
    locales: ['en', 'ja']
  }
};

Changed the next-i18next.config file from a .js to a .ts Updated the contents of the next-i18next.config.ts file to a duplication of what is defined in the next.config.js i18n field.

export const i18n = {
    defaultLocale: 'ja',
    locales: ['en', 'ja']
}

In tsconfig.json (root of the project) I included the newly created next-i18next.config.ts file.

{
  "compilerOptions": {
    "rootDirs": ["src/", "types/", "pages/", "lib/", "components/"],
    "target": "esnext",
    "module": "esnext",
    "jsx": "preserve",
    "lib": ["dom", "es2017"],
    "moduleResolution": "node",
    "allowJs": true,
    "noEmit": true,
    "strict": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "removeComments": false,
    "preserveConstEnums": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  },
  "exclude": ["node_modules"],
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next-i18next.config.ts"]
}

In _app.tsx, when wrapping the application with the appWithTranslation function, I imported the variable from previously made next-i18next.config.ts config, and passed it as a second parameter to the appWithTranslation function call.

import React, {FC} from 'react';
import {AppProps} from 'next/app';
import { appWithTranslation } from 'next-i18next'
import i18n from '../next-i18next.config'

const WrappedApp: FC<AppProps> = ({ Component, pageProps }) => {

  return (
        <Component {...pageProps} />
  )
}

export default appWithTranslation(WrappedApp, {i18n})

And did so similarly with any page that needs getStaticProps, example page below:

import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next'
import i18n from '../next-i18next.config'

const TestPage = ({}: Props) => {

const { t } = useTranslation('common')

  return (
      <>
        {t('test')}
        </>
  )
}

export const getStaticProps = async ({ locale } : any) => ({
    props: {
      ...await serverSideTranslations(locale, ['common'], {i18n}),
    }
  })

export default TestPage

What an absolute pain that was. I hope this helps anyone out there with the same issue. Also hope this gets resolved and that serverless next.js can properly locate the module without needing to duplicate the configs.

Just some musings: It seems when you run serverless it duplicates the next-i18next.config.js file and creates another file like next-i18next.config.original<hash>.js not sure if that's the issue or not...

Can provide more details of my projects configs if necessary. Good luck all.

dphang commented 3 years ago

Thanks all for the help, let me look into it next. Unfortunately I am not using next-i18next myself yet and don't have enough time to look at various integrations (being the primary contributor to this) so I will take a look and try to repro & fix for the next minor version

thisismydesign commented 3 years ago

@rhoiyds' approach worked. You can see the same conclusion here: https://github.com/isaachinman/next-i18next/issues/990#issuecomment-795398532

ignaciolarranaga commented 3 years ago

Guys, is the automatic locale selection working for you guys in AWS/LambdaEdge? I can access the routes by directly pointing to them but the locale selection/redirection is not working (i.e. it just stays in english, the default).

dphang commented 3 years ago

Guys, is the automatic locale selection working for you guys in AWS/LambdaEdge?

I can access the routes by directly pointing to them but the locale selection/redirection is not working (i.e. it just stays in english, the default).

Are you forwarding the Accept-Language header in CloudFront configuration? This is needed to detect the user's locale

ignaciolarranaga commented 3 years ago

Yes, that seems to be the problem, I'm checking how to change the Cloudfront behavior (I thought it was done :) )

Arditc commented 3 years ago

Guys, is the automatic locale selection working for you guys in AWS/LambdaEdge? I can access the routes by directly pointing to them but the locale selection/redirection is not working (i.e. it just stays in english, the default).

Are you forwarding the Accept-Language header in CloudFront configuration? This is needed to detect the user's locale

So I tried this, and things sadly didn't work as expected.

I added Accept-Language to the nextjs serverless configuration. When my Language is 'es' and I land on mywebsite.com/ I get redirected to mywebsite.com/es and then I notice an infinite 307 redirects to /es after this

After I removed the Accept-Language Header in the serverless configuration, I still see it in Cloudfront (see screenshot) for the Path Pattern Default (*). (Not sure what I need to add in the serverless config so I stop seeing it appear in Cloudfront😞 )

Screenshot 2021-05-15 at 19 45 10

Update: I ended up putting headers: "none" for the time being.

ignaciolarranaga commented 3 years ago

On my case it is working as expected, weird.

Leaving the CDK code just in case it is useful for someone:

    const cachePolicy = new CachePolicy(this, `DefaultCachePolicy${capitalize(env)}`, {
      cachePolicyName: `DefaultCachePolicy${capitalize(env)}`,
      comment: 'The default policy to be used in the cache',
      cookieBehavior: CacheCookieBehavior.all(),
      headerBehavior: CacheHeaderBehavior.allowList('Accept-Language'),
      queryStringBehavior: CacheQueryStringBehavior.all(),
      enableAcceptEncodingGzip: true,
      enableAcceptEncodingBrotli: true, // spellchecker: disable-line
    });

    const nextLambdaEdge = new NextJSLambdaEdge(this, `Cloudfront${capitalize(env)}`, {
      ...
      defaultBehavior: {
        cachePolicy
      }
    });
Lunnatica commented 3 years ago

Guys, is the automatic locale selection working for you guys in AWS/LambdaEdge? I can access the routes by directly pointing to them but the locale selection/redirection is not working (i.e. it just stays in english, the default).

Are you forwarding the Accept-Language header in CloudFront configuration? This is needed to detect the user's locale

So I tried this, and things sadly didn't work as expected.

I added Accept-Language to the nextjs serverless configuration. When my Language is 'es' and I land on mywebsite.com/ I get redirected to mywebsite.com/es and then I notice an infinite 307 redirects to /es after this

After I removed the Accept-Language Header in the serverless configuration, I still see it in Cloudfront (see screenshot) for the Path Pattern Default (*). (Not sure what I need to add in the serverless config so I stop seeing it appear in Cloudfront😞 )

Screenshot 2021-05-15 at 19 45 10

Update: I ended up putting headers: "none" for the time being.

It was giving me this error too when using the latest version @sls-next/serverless-component@1.19.1-alpha.7 - but I managed to fix it by downgrading to the latest stable version (@sls-next/serverless-component@1.19.0) and now it works fine.

ignaciolarranaga commented 3 years ago

By the way, somebody found how to fallback to the most approximate locale?

Example:

// next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'es''],
    defaultLocale: 'en',
  },
}

When I hit with Accept-Language: 'es-ES' instead of falling back to 'es' it falls back to 'en' (which is a little bit awkward since locale='es' would be much more appropriate). Does it needs to be custom made?, or is there some trick we can use?

Lunnatica commented 3 years ago

@ignaciolarranaga I'm having the same problem. It works fine locally, but not after deploying to production.

Update: it seems to be an issue with Next.js https://github.com/vercel/next.js/issues/20488

ignaciolarranaga commented 3 years ago

Hey, just migrated to 1.9.0-alpha.4 and keeps in loop when not correctly recognize the locale. For example, My browser by default sends: accept-language: es-419,es;q=0.9, this forces the jump to /es but later keeps looping over that.

dphang commented 3 years ago

I think most of the major issues (locale redirection, precedence etc.) should be fixed now in the latest versions, so I'll close this and please open a new issue so it's easier to track specific issues. Thanks!

royporter7 commented 3 years ago

Sorry to continue the discussion, but I reverted all of my previously mentioned changes, updated to the latest version of serverless component as well as nexti18next and tested a serverless deployment. It successfully deploys, however all I get is 404's on all of my routes.

dphang commented 3 years ago

Sorry to continue the discussion, but I reverted all of my previously mentioned changes, updated to the latest version of serverless component as well as nexti18next and tested a serverless deployment. It successfully deploys, however all I get is 404's on all of my routes.

Can you post any more details of .serverless_nextjs directory / manifests and also if there is any reproduction you can share. Might be good to open a new issue so it's easier to isolate the problem?