diff --git a/.changeset/strange-sheep-film.md b/.changeset/strange-sheep-film.md new file mode 100644 index 000000000000..dc55ad03af76 --- /dev/null +++ b/.changeset/strange-sheep-film.md @@ -0,0 +1,40 @@ +--- +'astro': minor +--- + +[Server Islands](https://astro.build/blog/future-of-astro-server-islands/) introduced behind an experimental flag in [v4.12.0](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#4120) is no longer experimental and is available for general use. + +Server islands are Astro's solution for highly cacheable pages of mixed static and dynamic content. They allow you to specify components that should run on the server, allowing the rest of the page to be more aggressively cached, or even generated statically. + +Turn any `.astro` component into a server island by adding the `server:defer` directive and optionally, fallback placeholder content. It will be rendered dynamically at runtime outside the context of the rest of the page, allowing you to add longer cache headers for the pages, or even prerender them. + +```astro +--- +import Avatar from '../components/Avatar.astro'; +import GenericUser from '../components/GenericUser.astro'; +--- +
+

Page Title

+
+ + + +
+
+``` + +If you were previously using this feature, please remove the experimental flag from your Astro config: + +```diff +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + experimental { +- serverIslands: true, + }, +}); +``` + +If you have been waiting for stabilization before using server islands, you can now do so. + +Please see the [server island documentation](https://docs.astro.build/en/guides/server-islands/) for more about this feature. diff --git a/examples/server-islands/astro.config.mjs b/examples/server-islands/astro.config.mjs index c0d6b918a52e..ef62b52077c4 100644 --- a/examples/server-islands/astro.config.mjs +++ b/examples/server-islands/astro.config.mjs @@ -12,7 +12,4 @@ export default defineConfig({ tailwind({ applyBaseStyles: false }) ], devToolbar: { enabled: false }, - experimental: { - serverIslands: true, - } }); diff --git a/packages/astro/e2e/fixtures/server-islands-key/astro.config.mjs b/packages/astro/e2e/fixtures/server-islands-key/astro.config.mjs index db1a7b45243e..b6b0d1b113e5 100644 --- a/packages/astro/e2e/fixtures/server-islands-key/astro.config.mjs +++ b/packages/astro/e2e/fixtures/server-islands-key/astro.config.mjs @@ -6,7 +6,4 @@ export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), integrations: [], - experimental: { - serverIslands: true, - } }); diff --git a/packages/astro/e2e/fixtures/server-islands/astro.config.mjs b/packages/astro/e2e/fixtures/server-islands/astro.config.mjs index 6b3c2a146eac..6b3e596c4c02 100644 --- a/packages/astro/e2e/fixtures/server-islands/astro.config.mjs +++ b/packages/astro/e2e/fixtures/server-islands/astro.config.mjs @@ -10,7 +10,4 @@ export default defineConfig({ adapter: nodejs({ mode: 'standalone' }), integrations: [react(), mdx()], trailingSlash: process.env.TRAILING_SLASH ?? 'always', - experimental: { - serverIslands: true, - } }); diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 440ed6028fb8..c420e85c415d 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -210,6 +210,13 @@ class AstroBuilder { }; const { internals, ssrOutputChunkNames, contentFileNames } = await viteBuild(opts); + + const hasServerIslands = this.settings.serverIslandNameMap.size > 0; + // Error if there are server islands but no adapter provided. + if(hasServerIslands && this.settings.buildOutput !== 'server') { + throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands); + } + await staticBuild(opts, internals, ssrOutputChunkNames, contentFileNames); // Write any additionally generated assets to disk. @@ -230,11 +237,7 @@ class AstroBuilder { routes: Object.values(allPages) .flat() .map((pageData) => pageData.route) - .concat( - this.settings.config.experimental.serverIslands - ? [getServerIslandRouteData(this.settings.config)] - : [], - ), + .concat(hasServerIslands ? getServerIslandRouteData(this.settings.config) : []), logging: this.logger, cacheManifest: internals.cacheManifestUsed, }); diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts index 3ddd1d127424..dab617d82f6a 100644 --- a/packages/astro/src/core/build/plugins/plugin-ssr.ts +++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts @@ -171,13 +171,10 @@ function generateSSRCode(settings: AstroSettings, adapter: AstroAdapter, middlew `import * as serverEntrypointModule from '${ADAPTER_VIRTUAL_MODULE_ID}';`, `import { manifest as defaultManifest } from '${SSR_MANIFEST_VIRTUAL_MODULE_ID}';`, edgeMiddleware ? `` : `import { onRequest as middleware } from '${middlewareId}';`, - settings.config.experimental.serverIslands - ? `import { serverIslandMap } from '${VIRTUAL_ISLAND_MAP_ID}';` - : '', + `import { serverIslandMap } from '${VIRTUAL_ISLAND_MAP_ID}';`, ]; const contents = [ - settings.config.experimental.serverIslands ? '' : `const serverIslandMap = new Map()`, edgeMiddleware ? `const middleware = (_, next) => next()` : '', `const _manifest = Object.assign(defaultManifest, {`, ` pageMap,`, diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 60427a19e8f0..c1300b78bf32 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -91,7 +91,6 @@ export const ASTRO_CONFIG_DEFAULTS = { experimental: { contentCollectionCache: false, clientPrerender: false, - serverIslands: false, contentIntellisense: false, }, } satisfies AstroUserConfig & { server: { open: boolean } }; @@ -521,10 +520,6 @@ export const AstroConfigSchema = z.object({ .boolean() .optional() .default(ASTRO_CONFIG_DEFAULTS.experimental.clientPrerender), - serverIslands: z - .boolean() - .optional() - .default(ASTRO_CONFIG_DEFAULTS.experimental.serverIslands), contentIntellisense: z .boolean() .optional() diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index e8d9edfe3660..eb27657271bb 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -158,7 +158,7 @@ export async function createVite( astroInternationalization({ settings }), vitePluginActions({ fs, settings }), vitePluginUserActions({ settings }), - settings.config.experimental.serverIslands && vitePluginServerIslands({ settings }), + vitePluginServerIslands({ settings }), astroContainer(), ], publicDir: fileURLToPath(settings.config.publicDir), diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index 39aa06417341..fe2a4b2c1d53 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -412,6 +412,19 @@ export const AdapterSupportOutputMismatch = { `The \`${adapterName}\` adapter is configured to output a static website, but the project contains server-rendered pages. Please install and configure the appropriate server adapter for your final deployment.`, } satisfies ErrorData; +/** + * @docs + * @see + * - [Server-side Rendering](https://docs.astro.build/en/guides/server-side-rendering/) + * @description + * To use server islands, the same constraints exist as for sever-side rendering, so an adapter is needed. + */ +export const NoAdapterInstalledServerIslands = { + name: 'NoAdapterInstalledServerIslands', + title: 'Cannot use Server Islands without an adapter.', + message: `Cannot use server islands without an adapter. Please install and configure the appropriate server adapter for your final deployment.`, + hint: 'See https://docs.astro.build/en/guides/server-side-rendering/ for more information.', +} satisfies ErrorData; /** * @docs * @description diff --git a/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs b/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs index b01b674f53de..c6efdc2a23cb 100644 --- a/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs +++ b/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs @@ -6,7 +6,4 @@ export default defineConfig({ integrations: [ svelte() ], - experimental: { - serverIslands: true, - } }); diff --git a/packages/astro/test/fixtures/server-islands/ssr/astro.config.mjs b/packages/astro/test/fixtures/server-islands/ssr/astro.config.mjs index 79ce4c497abb..e0fdc9ef23d0 100644 --- a/packages/astro/test/fixtures/server-islands/ssr/astro.config.mjs +++ b/packages/astro/test/fixtures/server-islands/ssr/astro.config.mjs @@ -8,8 +8,5 @@ export default defineConfig({ integrations: [ svelte() ], - experimental: { - serverIslands: true, - } }); diff --git a/packages/astro/test/server-islands.test.js b/packages/astro/test/server-islands.test.js index 8806f3511545..86956f0d1ae8 100644 --- a/packages/astro/test/server-islands.test.js +++ b/packages/astro/test/server-islands.test.js @@ -63,13 +63,14 @@ describe('Server islands', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/server-islands/hybrid', - adapter: testAdapter(), }); }); describe('build', () => { before(async () => { - await fixture.build(); + await fixture.build({ + adapter: testAdapter() + }); }); it('Omits the island HTML from the static HTML', async () => { @@ -83,5 +84,18 @@ describe('Server islands', () => { assert.equal(serverIslandScript.length, 1, 'has the island script'); }); }); + + describe('build (no adapter)', () => { + it('Errors during the build', async () => { + try { + await fixture.build({ + adapter: undefined + }); + assert.equal(true, false, 'should not have succeeded'); + } catch(err) { + assert.equal(err.title, 'Cannot use Server Islands without an adapter.'); + } + }); + }); }); });