diff --git a/.changeset/thin-deers-sin.md b/.changeset/thin-deers-sin.md new file mode 100644 index 000000000000..25cb79b2bbdc --- /dev/null +++ b/.changeset/thin-deers-sin.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fixes an issue where error pages did not have access to the `Astro.locals` fields provided by the adapter. diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts index 0e2d745ef57c..5c163005d9a1 100644 --- a/packages/astro/src/core/app/index.ts +++ b/packages/astro/src/core/app/index.ts @@ -66,6 +66,7 @@ export interface RenderOptions { } export interface RenderErrorOptions { + locals?: App.Locals, routeData?: RouteData; response?: Response; status: 404 | 500; @@ -295,7 +296,7 @@ export class App { routeData = this.match(request); } if (!routeData) { - return this.#renderError(request, { status: 404 }); + return this.#renderError(request, { locals, status: 404 }); } const pathname = this.#getPathnameFromRequest(request); const defaultStatus = this.#getDefaultStatusCode(routeData, pathname); @@ -314,7 +315,7 @@ export class App { response = await renderContext.render(await mod.page()); } catch (err: any) { this.#logger.error(null, err.stack || err.message || String(err)); - return this.#renderError(request, { status: 500 }); + return this.#renderError(request, { locals, status: 500 }); } if ( @@ -322,6 +323,7 @@ export class App { response.headers.get(REROUTE_DIRECTIVE_HEADER) !== 'no' ) { return this.#renderError(request, { + locals, response, status: response.status as 404 | 500, }); @@ -374,7 +376,7 @@ export class App { */ async #renderError( request: Request, - { status, response: originalResponse, skipMiddleware = false }: RenderErrorOptions + { locals, status, response: originalResponse, skipMiddleware = false }: RenderErrorOptions ): Promise { const errorRoutePath = `/${status}${this.#manifest.trailingSlash === 'always' ? '/' : ''}`; const errorRouteData = matchRoute(errorRoutePath, this.#manifestData); @@ -397,6 +399,7 @@ export class App { const mod = await this.#getModuleForRoute(errorRouteData); try { const renderContext = RenderContext.create({ + locals, pipeline: this.#pipeline, middleware: skipMiddleware ? (_, next) => next() : undefined, pathname: this.#getPathnameFromRequest(request), @@ -410,6 +413,7 @@ export class App { // Middleware may be the cause of the error, so we try rendering 404/500.astro without it. if (skipMiddleware === false) { return this.#renderError(request, { + locals, status, response: originalResponse, skipMiddleware: true, diff --git a/packages/astro/test/fixtures/ssr-locals/src/pages/404.astro b/packages/astro/test/fixtures/ssr-locals/src/pages/404.astro new file mode 100644 index 000000000000..66b1f7a045b1 --- /dev/null +++ b/packages/astro/test/fixtures/ssr-locals/src/pages/404.astro @@ -0,0 +1,4 @@ +--- +const { foo } = Astro.locals; +--- +

{ foo }

diff --git a/packages/astro/test/fixtures/ssr-locals/src/pages/500.astro b/packages/astro/test/fixtures/ssr-locals/src/pages/500.astro new file mode 100644 index 000000000000..66b1f7a045b1 --- /dev/null +++ b/packages/astro/test/fixtures/ssr-locals/src/pages/500.astro @@ -0,0 +1,4 @@ +--- +const { foo } = Astro.locals; +--- +

{ foo }

diff --git a/packages/astro/test/fixtures/ssr-locals/src/pages/go-to-error-page.astro b/packages/astro/test/fixtures/ssr-locals/src/pages/go-to-error-page.astro new file mode 100644 index 000000000000..ee962857d4d0 --- /dev/null +++ b/packages/astro/test/fixtures/ssr-locals/src/pages/go-to-error-page.astro @@ -0,0 +1,3 @@ +--- +throw new Error +--- \ No newline at end of file diff --git a/packages/astro/test/ssr-locals.test.js b/packages/astro/test/ssr-locals.test.js index 9fea1fed9bd6..e4dee50252d1 100644 --- a/packages/astro/test/ssr-locals.test.js +++ b/packages/astro/test/ssr-locals.test.js @@ -7,6 +7,8 @@ import { loadFixture } from './test-utils.js'; describe('SSR Astro.locals from server', () => { /** @type {import('./test-utils').Fixture} */ let fixture; + /** @type {import('./test-utils.js').App} */ + let app; before(async () => { fixture = await loadFixture({ @@ -15,10 +17,10 @@ describe('SSR Astro.locals from server', () => { adapter: testAdapter(), }); await fixture.build(); + app = await fixture.loadTestAdapterApp(); }); it('Can access Astro.locals in page', async () => { - const app = await fixture.loadTestAdapterApp(); const request = new Request('http://example.com/foo'); const locals = { foo: 'bar' }; const response = await app.render(request, { locals }); @@ -29,7 +31,6 @@ describe('SSR Astro.locals from server', () => { }); it('Can access Astro.locals in api context', async () => { - const app = await fixture.loadTestAdapterApp(); const request = new Request('http://example.com/api'); const locals = { foo: 'bar' }; const response = await app.render(request, undefined, locals); @@ -38,4 +39,26 @@ describe('SSR Astro.locals from server', () => { assert.equal(body.foo, 'bar'); }); + + it('404.astro can access locals provided to app.render()', async () => { + const request = new Request('http://example.com/slkfnasf'); + const locals = { foo: 'par' }; + const response = await app.render(request, { locals }); + assert.equal(response.status, 404); + + const html = await response.text(); + const $ = cheerio.load(html); + assert.equal($('#foo').text(), 'par'); + }); + + it('500.astro can access locals provided to app.render()', async () => { + const request = new Request('http://example.com/go-to-error-page'); + const locals = { foo: 'par' }; + const response = await app.render(request, { locals }); + assert.equal(response.status, 500); + + const html = await response.text(); + const $ = cheerio.load(html); + assert.equal($('#foo').text(), 'par'); + }); });