Skip to content

Upon Astro.rewrite within middleware.js, query string is not available within midlleware.js, although it is available on target page #14733

@fulstadev

Description

@fulstadev

Astro Info

Astro                    v5.14.3
Vite                     v6.3.6
Node                     v24.5.0
System                   macOS (arm64)
Package Manager          npm
Output                   static
Adapter                  none
Integrations             @astrojs/sitemap (v3.6.0)

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

I am using astro as SSG for development only. My deployed stack neither runs node nor astro, it just serves the pages generated via astro during development using custom router and serving logic. The deployed page serving logic mainly consists in the mapping of a URL path string to the serving of a specific html file.

To be able to use astro as seamlessly as possibly during development, I've coded a middleware.js which aims to mimic the routing and serving behaviour of my deployed stack in local development mode via astro dev.

Now imagine that you have to do something based on the query string provided along with the page request in the middleware, before serving the page. E.g. if an incoming page request in astro dev mode is for /path/to/example?hello=world, I rewrite that request to src/pages/de/beispiel.astro, but before serving the src/pages/de/beispiel.astro page, I want to do something in the middleware based on the value of the hello query param.

With the middleware.js below (see it in action in linked stackblitz example):

import {
    defineMiddleware,
    sequence
}                                              from "astro/middleware";
import {middleware}                            from "astro:i18n";
import {ENV}                            from "astro:env/client";

export const codicsRoutingMiddleware = defineMiddleware(
    async (
        ctx,
        next
    ) => {

        const response = await next();

        // To only enable this in astro dev mode, middleware shall not be used during anything else / build
        if (ENV === "dev") {

            const requestedPath = ctx.url.pathname;

            /**
             * This is the routing logic (get to path to the existing astro page for the request path)
             * Just like the router in deployed stack maps a request path to a html file.
             */
            if (!ctx.isPrerendered) {

               // Make sure that ctx.url.search is `?hello=world`
               console.log(ctx.url.search);

                if (requestedPath === `/path/to/example`) {
                    return ctx.rewrite(`/de/beispiel/${ctx.url.search}`);
                }

            } else {

                /**
                 * This is the serving logic (serve the existing astro page, like serving the existing html page in prod)
                 */
                if (requestedPath === '/de/beispiel/') {

                   const html = await response.text();

                   // DO SOMETHING BASED ON QUERY PARAMS
                  // ctx.url.search is ALWAYS EMPTY

                    return new Response(
                        html.replaceAll(
                           "</body>",
                           `${someMarkup}</body>`
                        ),
                        {
                            status:  200,
                            headers: response.headers
                        }
                    );

                }

            }

        }

        return response;

    }
);


export const onRequest = sequence(
    codicsRoutingMiddleware,
    middleware(
        {
            prefixDefaultLocale: true,
            redirectToDefaultLocale: true,
        }
    )
)

With that middleware, when reaching my page, the query string is available in my beispiel.astro script, but it is never avilable within the middleware.js itself.

return ctx.rewrite(/de/beispiel/?hello=world);

... it simply never works.

As a workaround to this, I have defined a singleton helper class e.g.:

class QueryParamsStorageToSurviveDevRewrite {

    static #instance;

    static instance() {

        if (QueryParamsStorageToSurviveDevRewrite.#instance === undefined) {

            QueryParamsStorageToSurviveDevRewrite.#instance = new QueryParamsStorageToSurviveDevRewrite();

        }

        return QueryParamsStorageToSurviveDevRewrite.#instance;

    }

    #queryStringOfLastRequest = '';

    /**
     * To always set the value in a standardized way
     */
    static setQueryStringOfLastRequest(searchString) {
        QueryParamsStorageToSurviveDevRewrite.instance().#queryStringOfLastRequest = searchString.startsWith('?')
                                                                                     ? searchString.substring(1)
                                                                                     : searchString;
    }

    /**
     * Make sure that the value is reset each time it is read
     * @returns {string}
     */
    static getQueryStringOfLastRequest() {
        const currentValue = QueryParamsStorageToSurviveDevRewrite.instance().#queryStringOfLastRequest;
        QueryParamsStorageToSurviveDevRewrite.instance().#queryStringOfLastRequest = '';
        return currentValue;
    }

}

export {QueryParamsStorageToSurviveDevRewrite}

Then, I adapt my middleware slightly, to:

[...]

            if (!ctx.isPrerendered) {

                QueryParamsStorageToSurviveDevRewrite.setQueryStringOfLastRequest(ctx.url.search);

               [...]

            } else {

                if (requestedPath === '/de/beispiel/') {

                   // Act based on QueryParamsStorageToSurviveDevRewrite.getQueryStringOfLastRequest()
[...]

This makes the query string of the last request available in the middleware, but all of this feels like a bug?

Some of the below point into the same direction :

But I feel that my issue is something different?

What's the expected result?

When a rewrite is performed with an explicitly provided query string, the query string should be preserved and survive the rewrite, not only for the end file, but also within the middleware.js itself.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-oiiafger?file=src%2Fmiddleware.js

Participation

  • I am willing to submit a pull request for this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs triageIssue needs to be triaged

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions