CORS error on Next-XP with port 3000

Enonic version: 7.9.2
OS: Ubuntu 20.04

I’m creating a project using NextXP, where I’m developing a search feature.

On my page search, which is a Layout component, I want to perform a query using the URL param and then render the results in a list.
I’m using the useEffect hook to do this.
First, I tried using the fetchFromApi method:

useEffect(()=>{
  const urlSearchParams = new URLSearchParams(window.location.search);
  
  const searchTerm = urlSearchParams.get('s') || '';
  
  const body = {
              query: `query ($path: String!) {
                  guillotine {
                    query(query: $path) {
                      _path
                      displayName
                      type,
                      dataAsJson
                    }
                  }
              }`,
              variables: {
                  path: `fulltext('displayName, data.*, page.*, x.*', '${searchTerm}', 'AND')`
              },
          }
  
  const response = await fetchFromApi('http://localhost:8080/site/hmdb/master', body)

}, []);

When I execute this on the Content Studio preview, on the localhost:8080, it works well.
But, when I try this on the localhost:3000, I got an CORS error from the Enonic side:

Access to fetch at 'http://localhost:8080/site/hmdb/master' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

I also tried using an Enonic service instead of the fetchFromApi method, but I got the same CORS error (even allowing the CORS headers in the service file).

How can I solve this? I’m doing something wrong on my approach?

Hi Guilherme-Valle.

Could it be that you are trying to do a GET request, whereas in fact you should be using POST?

Hi @tsi, thanks for the response
I think the fetchFromApi method from the @enonic/nextjs-adapter sends a POST request by default.

But I anyway tried using this:

const response = await fetchFromApi('http://localhost:8080/site/hmdb/master', body, {}, 'POST')

And got the same CORS error.

Maybe the idea is always to use the fetchFromApi (or other fetch methods) inside the processor of my component, in the _mapping.ts file, instead of using it inside the component file itself. It works there, e.g:

ComponentRegistry.addPart(`${APP_NAME}:landing-page`, {
    query: getLandingPage,
    view: LandingPage,
    processor: landingPageProcessor
})

export async function landingPageProcessor(common: any, context?: Context, config?: any): Promise<any> {
    const body = {
        query: `query ($path: String!) {
                guillotine {
                  query(query: $path) {
                    _path
                    displayName
                    type,
                    dataAsJson
                  }
                }
            }`,
        variables: {
            path: `fulltext('displayName, data.*, page.*, x.*', 'searchExample', 'AND')`
        },
    }

    const response = await fetchFromApi('http://localhost:8080/site/hmdb/master', body);

    return response;
}

But on the _mapping.ts file, I can’t use the window value to get the URL param that I need to perform the query (in the example above I used a hardcoded search value); it also doesn’t work using the useRouter hook in this file.

Maybe the param should be inside of the context props of the processor? I printed this object and there is nothing there.

Hi @Guilherme-Valle,

Your guessed it right about using it on the server-side.This way you will avoid CORS errors.

To make that I would make next API page under pages/api folder of your project. More here
Make it forward the request to enonic with fetchFromApi call and return the response back.
Then just make a request from you client to that endpoint :wink:

Regarding the empty context, it is possible in case there is no context provided to the fetchContext invocation in you next.js route file:

    const result = await fetchContent(path, context);
2 Likes

Just giving feedback here:

We found the solution for the CORS error by performing the request inside the processor, when registering the component on the _mapping.tsx file.

To get the parameters of the URL in the processor, we have to extract the value from the content.query object.

My processor:


export async function landingPageProcessor(common: any, context?: Context, config?: any): Promise<any> {
    const querySearch = context?.query.s || '';
    const body = {
        query: `query ($path: String!) {
                guillotine {
                  query(query: $path) {
                    _path
                    displayName
                    type,
                    dataAsJson
                  }
                }
            }`,
        variables: {
            path: `fulltext('displayName, data.*, page.*, x.*', '${querySearch}', 'AND')`
        },
    }

    const response = await fetchFromApi('http://localhost:8080/site/hmdb/master', body);

    return response.data;
}

Once we had the data from the API, we were able to access it in the data object in the props of our part component.

2 Likes

Another detail:

We weren’t able to get the URL param on the processor, as I shown in the example above, because the app that we’re working on was using the getStaticProps on the [...contentPath].tsx file; we had to comment the methods related to the static generation and replace them with this:

export async function getServerSideProps(context: Context) {
    const path = context.query.contentPath || [];
    console.info(`Accessing dynamic component at: ${path}`);

    const {
        common = null,
        data = null,
        meta,
        error = null,
        page = null,
    } = await fetchContent(path, context);

    // HTTP 500
    if (error && error.code === '500') {
        throw error
    }

    let catchAllInNextProdMode = meta?.renderMode === RENDER_MODE.NEXT && !IS_DEV_MODE && meta?.catchAll;

    const props = {
        common,
        data,
        meta,
        error,
        page,
    }

    console.log(props)

    const notFound = (error && error.code === '404') || context.res?.statusCode === 404 || catchAllInNextProdMode || undefined;

    return {
        notFound,
        props,
    }
}

Don’t know if this is the correct solution, but only using this we was able to get the query param and use it on the request to the Guillotine API.

Great stuff. We’ll add a chapter to the tutorial and an out-of-the box solution for client-side requests in the upcomong next.XP release.