chapter-three / next-drupal

Next.js for Drupal has everything you need to build a next-generation front-end for your Drupal site: SSG, SSR, and ISR, Multi-site, Authentication, Webforms, Search API, I18n and Preview mode (works with JSON:API and GraphQL).
https://next-drupal.org
MIT License
636 stars 176 forks source link

TypeError: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Object #26

Closed demo-Ghost closed 2 years ago

demo-Ghost commented 3 years ago

Hi! Thanks for all the work you've put into this project. I'm following the quick start tutorial and have managed to set things up, after a some setbacks with simple_oauth library.

But, currently, I'm having an issue viewing my blog posts. I can see them at the home page and can also see that all their content is fetched in home page, as a collection. But once I visit a specific one (blog/first-post, as in blog/[node:title]), I get a 404 in the Network tab in Google Chrome.

And also this error.

 1 of 1 unhandled error

Unhandled Runtime Error
Error: Failed to load script: /_next/static/chunks/pages/404.js

Source
.next\static\chunks\main.js (83:51) @ HTMLScriptElement.script.onerror

  81 | //    executes when `src` is set.
  82 | script.onload = resolve;
> 83 | script.onerror = ()=>reject(markAssetError(new Error(`Failed to load script: ${src}`)))
     |                                           ^
  84 | ;
  85 | // 2. Configure the cross-origin attribute before setting `src` in case the
  86 | //    browser begins to fetch.

I guess the following error is due to not getting any data in the response.

error - TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Object
    at Function.byteLength (buffer.js:726:11)
    at ServerResponse.apiRes.end (C:\Users\trogl\Desktop\next-next-next\node_modules\next\dist\server\api-utils.js:72:41)
    at C:\Users\trogl\Desktop\next-next-next\node_modules\next-drupal\dist\index.js:566:30
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async Object.apiResolver (C:\Users\trogl\Desktop\next-next-next\node_modules\next\dist\server\api-utils.js:101:9)
    at async DevServer.handleApiRequest (C:\Users\trogl\Desktop\next-next-next\node_modules\next\dist\server\next-server.js:770:9)
    at async Object.fn (C:\Users\trogl\Desktop\next-next-next\node_modules\next\dist\server\next-server.js:661:37)
    at async Router.execute (C:\Users\trogl\Desktop\next-next-next\node_modules\next\dist\server\router.js:205:32)
    at async DevServer.run (C:\Users\trogl\Desktop\next-next-next\node_modules\next\dist\server\next-server.js:841:29) {
  code: 'ERR_INVALID_ARG_TYPE',
  page: '/api/preview'
}
shadcn commented 3 years ago

Can you post the code or part of for the blog post page file?

demo-Ghost commented 3 years ago

I haven't changed anything there ([...slug].tsx), but let me get it for you.

import * as React from "react"
import Head from "next/head"
import {
  getPathsFromContext,
  getResourceFromContext,
  getResourceTypeFromContext,
} from "next-drupal"
import { GetStaticPropsContext } from "next"
import { NodeArticle } from "@/nodes/node-article"
import { NodeBasicPage } from "@/components/nodes/node-basic-page"

/* eslint-disable  @typescript-eslint/no-explicit-any */
interface NodePageProps {
  preview: GetStaticPropsContext["preview"]
  node: Record<string, any>
}

export default function NodePage({ node, preview }: NodePageProps) {
  const [showPreviewAlert, setShowPreviewAlert] = React.useState<boolean>(false)

  if (!node) return null

  React.useEffect(() => {
    setShowPreviewAlert(preview && window.top === window.self)
  }, [])

  return (
    <>
      <Head>
        <title>{node.title}</title>
        <meta
          name="description"
          content="A Next.js site powered by a Drupal backend."
        />
      </Head>
      {showPreviewAlert && (
        <div className="fixed top-4 right-4">
          <a
            href="/api/exit-preview"
            className="bg-black text-white rounded-md px-4 py-2 text-sm"
          >
            Exit preview
          </a>
        </div>
      )}
      {node.type === "node--page" && <NodeBasicPage node={node} />}
      {node.type === "node--article" && <NodeArticle node={node} />}
    </>
  )
}

export async function getStaticPaths(context) {
  const output = {
    paths: await getPathsFromContext(["node--article", "node--page"], context),
    fallback: true,
  };

  return output; 
}

export async function getStaticProps(context) {
  const type = await getResourceTypeFromContext(context);

  if (!type) {
    return {
      notFound: true,
    }
  }

  let params = {}
  if (type === "node--article") {
    params = {
      include: "field_image,uid",
    }
  }

  const node = await getResourceFromContext(type, context, {
    params,
  })

  if (!node?.status) {
    return {
      notFound: true,
    }
  }

  return {
    props: {
      preview: context.preview || false,
      node,
    },
    revalidate: 60,
  }
}

node

shadcn commented 3 years ago

Is this happening in the Drupal preview only? What happens if you visit the post directly?

demo-Ghost commented 3 years ago

You mean from Next.js or in Drupal?

If I go to my Drupal site at localhost and click on the post on Homepage, I get this

image

shadcn commented 3 years ago

I mean if you visit the page on the Next site

demo-Ghost commented 3 years ago

On the Next site, homepage for the default-starter works great. Plus, I see all the data of the collection. image

but if I click on one of the posts, I get this image

demo-Ghost commented 3 years ago

Just to add to this, although it might not be relevant, but my env variable for DRUPAL_FRONT_PAGE(which I really don't know what it does exactly) is /node.

shadcn commented 3 years ago

I'm trying to reproduce this. Can you delete the .next directory and try restarting the server again yarn dev?

Let's also add a console.log(node) in getStaticProps to confirm if Next is able to pull the blog node:

export async function getStaticProps(context) {
  const type = await getResourceTypeFromContext(context);

  if (!type) {
    return {
      notFound: true,
    }
  }

  let params = {}
  if (type === "node--article") {
    params = {
      include: "field_image,uid",
    }
  }

  const node = await getResourceFromContext(type, context, {
    params,
  })

  console.log(node) // <----------- πŸ‘ˆ

  if (!node?.status) {
    return {
      notFound: true,
    }
  }

  return {
    props: {
      preview: context.preview || false,
      node,
    },
    revalidate: 60,
  }
}
demo-Ghost commented 3 years ago

I deleted .next and restarted. The node is always null in the console and problem still persists.

I also deleted the next.js sites and pathauto patterns in Drupal yesterday and re-created them in case something was wrong there, but I think the peoblem is on the Next side of things.

Two more things to mention:

demo-Ghost commented 3 years ago

Do you think this could be an issue related to decoupled routing?

demo-Ghost commented 3 years ago

I did some debugging in your source code, adding some console logs in buildUrl and getResourceByPath functions, because I think this has to do with the Url Aliases and Routing. As I mentioned before, the issue is not when fetching a collection of posts, but an individual post, at [...slug].jsx (check basic-starter source code).

What I get is this (sorry for this awful spaghetti sequence :) )

event - build page: /[...slug]
wait  - compiling...
event - compiled successfully
-
url in buildUrl URL {
  href: 'http://localhost/drupal-next/web/jsonapi',
  origin: 'http://localhost',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/drupal-next/web/jsonapi',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}
-
url in buildUrl URL {
  href: 'http://localhost/drupal-next/web/jsonapi',
  origin: 'http://localhost',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/drupal-next/web/jsonapi',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}
-
url in buildUrl URL {
  href: 'http://localhost/drupal-next/web/jsonapi/node/page?fields%5Bnode--page%5D=path&fields%5Bnode--article%5D=path',
  origin: 'http://localhost',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/drupal-next/web/jsonapi/node/page',
  search: '?fields%5Bnode--page%5D=path&fields%5Bnode--article%5D=path',
  searchParams: URLSearchParams { 'fields[node--page]' => 'path', 'fields[node--article]' => 'path' },
  hash: ''
}
-
url in buildUrl URL {
  href: 'http://localhost/drupal-next/web/jsonapi/node/article?fields%5Bnode--article%5D=path',
  origin: 'http://localhost',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/drupal-next/web/jsonapi/node/article',
  search: '?fields%5Bnode--article%5D=path',
  searchParams: URLSearchParams { 'fields[node--article]' => 'path' },
  hash: ''
}
-
url in buildUrl URL {
  href: 'http://localhost/drupal-next/web/router/translate-path?path=blog%2Fsecond-article',
  origin: 'http://localhost',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/drupal-next/web/router/translate-path',
  search: '?path=blog%2Fsecond-article',
  searchParams: URLSearchParams { 'path' => 'blog/second-article' },
  hash: ''
}
-
url in buildUrl URL {
  href: 'http://localhost/drupal-next/web/subrequests?_format=json',
  origin: 'http://localhost',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/drupal-next/web/subrequests',
  search: '?_format=json',
  searchParams: URLSearchParams { '_format' => 'json' },
  hash: ''
}
-
url in getResourceByPath http://localhost/drupal-next/web/subrequests?_format=json
-
response data in getResourceByPath {
  resolved: 'http://localhost/blog/second-article',
  isHomePath: false,
  entity: {
    canonical: 'http://localhost/blog/second-article',
    type: 'node',
    bundle: 'article',
    id: '2',
    uuid: 'afe65deb-6e3c-48a4-9b2b-fc807caf7857'
  },
  label: 'Second Article',
  jsonapi: {
    individual: 'http://localhost/jsonapi/node/article/afe65deb-6e3c-48a4-9b2b-fc807caf7857',
    resourceName: 'node--article',
    pathPrefix: 'jsonapi',
    basePath: '/jsonapi',
    entryPoint: 'http://localhost/jsonapi'
  },
  meta: {
    deprecated: {
      'jsonapi.pathPrefix': 'This property has been deprecated and will be removed in the next version of Decoupled Router. Use basePath instead.'
    }
  }
}

Everything looks good, until you check the last console output

response data in getResourceByPath {
  resolved: 'http://localhost/blog/second-article',
  isHomePath: false,
  entity: {
    canonical: 'http://localhost/blog/second-article',
    type: 'node',
    bundle: 'article',
    id: '2',
    uuid: 'afe65deb-6e3c-48a4-9b2b-fc807caf7857'
  },
  label: 'Second Article',
  jsonapi: {
    individual: 'http://localhost/jsonapi/node/article/afe65deb-6e3c-48a4-9b2b-fc807caf7857',
    resourceName: 'node--article',
    pathPrefix: 'jsonapi',
    basePath: '/jsonapi',
    entryPoint: 'http://localhost/jsonapi'
  },
  meta: {
    deprecated: {
      'jsonapi.pathPrefix': 'This property has been deprecated and will be removed in the next version of Decoupled Router. Use basePath instead.'
    }
  }
}

The URL is wrong there for jsonapi.individual, it shouldn't be http://localhost/jsonapi/node/article/afe65deb-6e3c-48a4-9b2b-fc807caf7857.

I'm not sure if this is some config thing in Drupal, or some config in Next.js. Plus, I also configured my url to be localhost/drupal-next/web after suspecting that it could be an issue with path aliases and also did a clean Drupal install from scratch. Initially, I had Drupal configured at plain http://localhost with some Apache settings (no drupal-next/web), but removed those settings, just to make sure I didn't mess with the paths.

The error in this Github Issue title, TypeError: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Object comes from giving the wrong input to a function, it expects a string or buffer, but instead gets an object { message: "Invalid slug" } from within next-drupal source code.

rilrom commented 3 years ago

Few things I've found that may help guide you @shadcn.

I managed to reproduce the issue of a 404 appearing on individual slugs. I was able to rectify the issue by adding 'deserialize: false' to the options parameter of 'getResourceFromContext'.

In the getResourceByPath function, if data is serialised it will return undefined. I have not been able to determine why.

image-1

This then returns the data back to the getStaticProps function, however the data does not contain a node.status (edit: I imagine this is likely due to deserialize not being run), so it will return notFound: true

image-2

Removing this check will create a successful page load.

Obviously this is not ideal and the checks are there for a reason but hopefully this will provide some context in order for you to further investigate.

Edit: Apologies for all the edits.

Seems there's one more thing I forgot to mention. Setting deserialize to false is not enough, this line here also seems to cause null to be returned.

image-3

shadcn commented 3 years ago

@demo-Ghost @rilrom Thanks for the detailed info. Is there anyway you can help me reproduce this (minimal reproducible repo)?

demo-Ghost commented 3 years ago

@shadcn In my case, this is a local environment in Windows, with Xampp. Other than that, just follow the exact instructions on Quick Start guide https://next-drupal.org/docs/quick-start. That's about it I think, the repo is the same as the basic starter.

taku3202 commented 3 years ago

I am experiencing the same phenomenon.

TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Object
    at new NodeError (node:internal/errors:371:5)
    at Function.byteLength (node:buffer:733:11)
    at ServerResponse.apiRes.end (/opt/preview/.docker/preview/node_modules/next/dist/server/api-utils.js:72:41)
    at /opt/preview/.docker/preview/node_modules/next-drupal/dist/index.js:593:30
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async Object.apiResolver (/opt/preview/.docker/preview/node_modules/next/dist/server/api-utils.js:101:9)
    at async Server.handleApiRequest (/opt/preview/.docker/preview/node_modules/next/dist/server/next-server.js:770:9)
    at async Object.fn (/opt/preview/.docker/preview/node_modules/next/dist/server/next-server.js:661:37)
    at async Router.execute (/opt/preview/.docker/preview/node_modules/next/dist/server/router.js:205:32)
    at async Server.run (/opt/preview/.docker/preview/node_modules/next/dist/server/next-server.js:841:29) {
  code: 'ERR_INVALID_ARG_TYPE'

The version I am using is as follows.

I refer to this code, On the Drupal side, the page loaded successfully by applying the following patch to the subrequests module.

        "patches": {
+       "drupal/subrequests": {
+                "Subrequest failed validation": "https://www.drupal.org/files/issues/2019-05-27/3029570-array-not-object.patch",
+                "Get same results on different request": "https://www.drupal.org/files/issues/2019-07-18/change_request_type-63049395-09.patch"
+            }
        },
shadcn commented 3 years ago

Just thinking out loud here.

It looks like buildUrl is correctly returning the url but the response from decoupled router has the wrong base url.

url in buildUrl URL {
  href: 'http://localhost/drupal-next/web/subrequests?_format=json', <----------------- βœ…
  origin: 'http://localhost',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/drupal-next/web/subrequests',
  search: '?_format=json',
  searchParams: URLSearchParams { '_format' => 'json' },
  hash: ''
}
-
url in getResourceByPath http://localhost/drupal-next/web/subrequests?_format=json
-
response data in getResourceByPath {
  resolved: 'http://localhost/blog/second-article',  <----------------- ❌
  isHomePath: false,
  entity: {
    canonical: 'http://localhost/blog/second-article',  <----------------- ❌
    type: 'node',
    bundle: 'article',
    id: '2',
    uuid: 'afe65deb-6e3c-48a4-9b2b-fc807caf7857'
  },
  label: 'Second Article',
  jsonapi: {
    individual: 'http://localhost/jsonapi/node/article/afe65deb-6e3c-48a4-9b2b-fc807caf7857',
    resourceName: 'node--article',
    pathPrefix: 'jsonapi',
    basePath: '/jsonapi',
    entryPoint: 'http://localhost/jsonapi'  <----------------- ❌
  },
  meta: {
    deprecated: {
      'jsonapi.pathPrefix': 'This property has been deprecated and will be removed in the next version of Decoupled Router. Use basePath instead.'
    }
  }
}

I'll looking into the Drupal/Decoupled Router end.

shadcn commented 3 years ago

Aha

From \Drupal\decoupled_router\EventSubscriber\RouterPathTranslatorSubscriber::onPathTranslation:

$path = $event->getPath();
$path = $this->cleanSubdirInPath($path, $event->getRequest());
/**
   * Removes the subdir prefix from the path.
   *
   * @param string $path
   *   The path that can contain the subdir prefix.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request to extract the path prefix from.
   *
   * @return string
   *   The clean path.
   */
  protected function cleanSubdirInPath($path, Request $request) {
    // Remove any possible leading subdir information in case Drupal is
    // installed under http://example.com/d8/index.php
    $regexp = preg_quote($request->getBasePath(), '/');
    return preg_replace(sprintf('/^%s/', $regexp), '', $path);
  }

Trying to figure out why we're doing this now.

demo-Ghost commented 3 years ago

Hello again @shadcn . I'm trying to think why you could have done this. Something that seemed weird to me, is that URL Aliases at '/admin/config/search/path', image would always show the full url path, regardless of where Drupal is served from. Even if served directly at http://localhost, the URL Aliases would include /drupal/web in the url. image

shadcn commented 3 years ago

This is coming from Drupal. Been a while since I used Drupal in a subdir path. I can't remember if there was a config somewhere to overwrite this.

You're saying that you're running the site at http://localhost and Drupal is still showing http://localhost/drupal/web in the admin?

demo-Ghost commented 3 years ago

No, it shows http://localhost/drupal/web only in the URL aliases screen, the one in the screenshot. The url in the browser is correct, http://localhost.

My Drupal installation is in a subfolder ([web server root]/drupal/web) and then I configure Apache .htaccess files to serve it from http://localhost, using this doc https://www.drupal.org/forum/support/post-installation/2016-06-22/d8-how-to-use-drupal-8-in-a-subfolder (check IntraFusion's comment).

KojoEnch commented 3 years ago

I faced the same issue (Drupal 9.2). As per @taku3202 comment, applying Drupal Subrequests patches solved it :

shadcn commented 2 years ago

@demo-Ghost Did you figure this out?

claudiu-cristea commented 2 years ago

I'm getting the same error in preview. My Drupal site base URL is http://localhost:8080/blog/drupal/web

EDIT: I've followed exactly the quick start https://next-drupal.org/learn/quick-start path

claudiu-cristea commented 2 years ago

Also, applying the patches from https://github.com/chapter-three/next-drupal/issues/26#issuecomment-945509653 didn't help

claudiu-cristea commented 2 years ago

Just for debugging I've replaced the RouterPathTranslatorSubscriber::cleanSubdirInPath() method (see https://github.com/chapter-three/next-drupal/issues/26#issuecomment-950711871) with:

  protected function cleanSubdirInPath($path, Request $request) {
    // Remove any possible leading subdir information in case Drupal is
    // installed under http://example.com/d8/index.php
    if (strpos($path, '/blog/drupal/web/') === 0) {
      $path = substr($path, strlen('/blog/drupal/web/'));
    }
    return $path;
  }

...and it fixes the issue.

Note that I've hardcoded my base path (/blog/drupal/web/) because it seems that I couldn't get it from request. What comes from $request->getBasePath() is an empty string. Same is stored in $GLOBALS['base_path'].

However, we need a permanent fix. I see there is https://www.drupal.org/project/decoupled_router/issues/3133873 but the patch over there is not resolving the issue.

claudiu-cristea commented 2 years ago

So this works but don't ask my why :) The Symfony request stored in $GLOBALS['request'] contains the correct base path information. But I have no idea why they are different...

  protected function cleanSubdirInPath($path, Request $request) {
    // Remove any possible leading subdir information in case Drupal is
    // installed under http://example.com/d8/index.php
    $regexp = preg_quote($GLOBALS['request']->getBasePath(), '/');
    return preg_replace(sprintf('/^%s/', $regexp), '', $path);
  }
claudiu-cristea commented 2 years ago

Ok, so I've reworked https://www.drupal.org/project/decoupled_router/issues/3133873. @demo-Ghost could you try to apply also the https://git.drupalcode.org/project/decoupled_router/-/merge_requests/3.diff patch to decoupled_router module and see if fixes the issue? It does for me.

(However, still don't understand why, in cleanSubdirInPath(), $request and $GLOBALS['request'] are diverged)

demo-Ghost commented 2 years ago

I'll give it a shot @claudiu-cristea and I'll let you know.

vermario commented 2 years ago

@claudiu-cristea hi! I had the same issue described here, and your last patch fixed it!