Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion content/guides/basics/static_file_server.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,28 @@ The function should return an object where keys are header names and values are

:::

:::option{name="prefixes"}

The `prefixes` property limits static file serving to a list of URL paths. By default, the middleware looks for a file in `public/` on every request, including the requests your routes handle. When you define `prefixes`, a request with no matching prefix continues to your routes without a filesystem lookup.

```ts title="config/static.ts"
export default defineConfig({
prefixes: ['/assets', '/favicon.ico', '/robots.txt'],
})
```

A request matches when its path equals a prefix or is nested under it. In the example above, `/assets/app.js` matches `/assets`, and `/assets-old/app.js` does not. The prefixes do not change the file path, so `/assets/app.js` still serves `public/assets/app.js`.

The property is not defined by default, and every request is looked up in the `public` directory.

:::warning
A file in `public/` with no matching prefix is not served, even when it exists. Add an entry for each file or directory you keep outside the listed prefixes, such as `/uploads`.

The middleware compares the prefixes as-is with the request URL. It does not decode the path or ignore the case, so `/Assets/app.js` and `/%61ssets/app.js` do not match `/assets`.
:::

:::

::::

## Serving static files
Expand Down Expand Up @@ -317,7 +339,7 @@ The rule for copying public files is defined in the `adonisrc.ts` file:

The `pattern` property uses glob syntax to match all files inside the `public` directory. The `reloadServer: false` setting indicates that changes to these files during development don't require restarting the development server.

If you add files to the `public` directory while your development server is running, you don't need to restart. The static middleware will serve them immediately. However, if you modify the `config/static.ts` file, you will need to restart the server for the configuration changes to take effect.
If you add files to the `public` directory while your development server is running, you don't need to restart. The static middleware will serve them immediately, provided their URL matches the `prefixes` option when you have defined it. However, if you modify the `config/static.ts` file, you will need to restart the server for the configuration changes to take effect.

## See also

Expand Down
2 changes: 1 addition & 1 deletion content/guides/frontend/api_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export default defineConfig({
plugins: [
react(),
inertia({ ssr: { enabled: false, entrypoint: 'inertia/ssr.tsx' } }),
adonisjs({ entrypoints: ['inertia/app.tsx'], reload: ['resources/views/**/*.edge'] }),
adonisjs({ entryPoints: ['inertia/app.tsx'], reload: ['resources/views/**/*.edge'] }),
],

resolve: {
Expand Down
18 changes: 14 additions & 4 deletions content/guides/frontend/inertia.md
Original file line number Diff line number Diff line change
Expand Up @@ -1603,7 +1603,7 @@ See [history encryption](https://inertiajs.com/history-encryption) on the Inerti

Server-side rendering (SSR) generates the initial HTML on the server, improving perceived performance and SEO.

The starter kit pre-wires everything SSR needs: the `inertia/ssr.tsx` entrypoint exists, `vite.config.ts` declares it under `serverEntrypoints`, and `config/inertia.ts` points at it. Enabling SSR is a single switch.
The starter kit pre-wires everything SSR needs: the `inertia/ssr.tsx` entrypoint exists, `vite.config.ts` declares it under `serverEntryPoints`, and `config/inertia.ts` points at it. Enabling SSR is a single switch.

```ts title="config/inertia.ts"
import { defineConfig } from '@adonisjs/inertia'
Expand All @@ -1618,22 +1618,32 @@ const inertiaConfig = defineConfig({
export default inertiaConfig
```

If you are adding SSR to an existing application, add the same entrypoint to `serverEntrypoints` in `vite.config.ts`.
If you are adding SSR to an existing application, add the same entrypoint to `serverEntryPoints` in `vite.config.ts`.

```ts title="vite.config.ts"
import { defineConfig } from 'vite'
import adonisjs from '@adonisjs/vite/client'
import react from '@vitejs/plugin-react'

export default defineConfig({
plugins: [
adonisjs({
entrypoints: ['inertia/app.tsx'],
serverEntrypoints: ['inertia/ssr.tsx'], // [!code highlight]
entryPoints: ['inertia/app.tsx'],
serverEntryPoints: ['inertia/ssr.tsx'], // [!code highlight]
}),
react(),
],
})
```

### Deploying client assets to a CDN

SSR does not require the client bundles to exist on the application server. The server renders pages from the local SSR bundle and uses the local manifests to resolve client asset URLs. You can upload the hashed client assets to a CDN and omit them from the running application's filesystem.

With the default output directories, keep `public/assets/.vite/manifest.json` and the entire `public/assets/server/` directory in the application artifact. The server directory contains the SSR manifest and bundles. Never upload the manifests or anything under `public/assets/server` to the CDN. The [Vite CDN deployment guide](./vite.md#deploying-assets-to-a-cdn) shows how to configure one URL for both builds and runtime, and how to exclude these files without selecting client filenames manually.

The Inertia SSR renderer loads its local server bundle directly, so it does not need the AdonisJS static middleware to serve `public/assets`. This only applies to Vite assets. Continue to serve other public files, such as `favicon.ico`, uploaded images, or `robots.txt`, with the static middleware, a web server, or separate object storage.

## Understanding the Inertia request lifecycle

Inertia requests follow two flows: the **initial page visit** and **subsequent Inertia visits**. Both flows use the same AdonisJS routes, middleware, controllers, and `inertia.render()` calls. The response format changes based on whether the request includes the `X-Inertia` header.
Expand Down
61 changes: 56 additions & 5 deletions content/guides/frontend/vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ URL prefix for generating asset links in production. Set this to your CDN URL wh

```ts
{
assetsUrl: 'https://cdn.example.com/'
assetsUrl: 'https://cdn.example.com/assets/'
}
```
::::
Expand Down Expand Up @@ -544,7 +544,9 @@ export default defineConfig({

## Deploying assets to a CDN

To serve bundled assets from a CDN in production, configure the `assetsUrl` option in both configuration files. This ensures that URLs in the manifest file and lazy-loaded chunks point to your CDN server.
To serve bundled assets from a CDN in production, use the same CDN URL at build time and at runtime. The build-time value makes lazy-loaded chunks point to the CDN. The runtime value makes helpers such as `@vite` use the same origin.

Read the URL from a `CDN_ASSETS_URL` environment variable in both configuration files, so that a single value drives the build and the running application.

```ts title="vite.config.ts"
import { defineConfig } from 'vite'
Expand All @@ -556,23 +558,72 @@ export default defineConfig({
entryPoints: ['resources/js/app.js'],
reload: ['resources/views/**/*.edge'],
// [!code ++:1]
assetsUrl: 'https://cdn.example.com/',
assetsUrl: process.env.CDN_ASSETS_URL,
}),
]
})
```

```ts title="config/vite.ts"
import env from '#start/env'
import { defineConfig } from '@adonisjs/vite'

export default defineConfig({
buildDirectory: 'public/assets',
// [!code ++:1]
assetsUrl: 'https://cdn.example.com/',
assetsUrl: env.get('CDN_ASSETS_URL'),
})
```

After building your application with `node ace build`, upload the contents of `public/assets` to your CDN.
Add `CDN_ASSETS_URL` to the environment validation in `start/env.ts`. The variable is required, so you must also set `CDN_ASSETS_URL=/assets` in your `.env`, `.env.test`, and `.env.example` files.

```ts title="start/env.ts"
{
CDN_ASSETS_URL: Env.schema.string(),
}
```

Export the CDN URL before you build the application, and set the same value on the production server. The environment schema requires a value, but it cannot check that the build environment and the production server use the same URL.

```sh title="Terminal"
export CDN_ASSETS_URL=https://cdn.example.com/assets/
node ace build
```

:::warning
`CDN_ASSETS_URL` must be defined in every environment that runs `node ace build`, including your CI pipeline. When the variable is missing, the plugin receives `undefined` instead of falling back to `/assets`, and the build fails with a `TypeError`.

Export `CDN_ASSETS_URL=/assets` for builds that do not use a CDN.
:::

Upload from the completed deployment artifact, not from the source checkout. With the default standalone build, `node ace build` copies the Vite output to `build/public/assets`.

The following command uploads every client asset without selecting filenames by hand. It excludes the Vite manifests and the server bundles, because the AdonisJS process reads `.vite/manifest.json`, `server/.vite/manifest.json`, and files such as `server/ssr.js` from the local filesystem and browsers never request them. The default Vite output uses content hashes for client assets, so the command also gives them an immutable cache policy. For an S3-compatible service, add its `--endpoint-url`.

```sh title="Terminal"
aws s3 sync build/public/assets s3://my-assets-bucket/assets/ \
--exclude '.vite/*' \
--exclude 'server/*' \
--cache-control 'public,max-age=31536000,immutable'
```

Upload the assets before switching traffic to the new application release, and do not apply the immutable cache policy to any unhashed files you add to the upload source.

:::warning
Do not add `--delete` to the command. Old hashed files must remain available for browsers with an open page and for application instances that still run the previous release during a rolling deployment. Deleting them breaks lazy-loaded chunks for those users.

Remove old assets with a separate cleanup job or an object lifecycle rule that only targets files older than your longest deployment window.
:::

:::warning
Do not upload the manifests or anything under `server/` to the CDN. The server bundles contain server-side code, not browser assets, and publishing them exposes that code for no benefit.

The static middleware and a reverse proxy that serves the `public` directory also expose everything under `public/assets`. If your server entry points contain code you want to keep private, block the `/assets/server/` path in your reverse proxy.
:::

Configure the CDN or object store to return the correct JavaScript and CSS MIME types and an `Access-Control-Allow-Origin` header for cross-origin module and font requests. Public assets can use `Access-Control-Allow-Origin: *`, and you must allow `GET` and `HEAD` in the object store's CORS configuration. If your Content Security Policy restricts scripts, styles, fonts, or images, add the CDN origin to the relevant directives.

For an Inertia application with SSR, see [Deploying client assets to a CDN](./inertia.md#deploying-client-assets-to-a-cdn).

## Common issues

Expand Down
2 changes: 1 addition & 1 deletion content/reference/edge.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export default defineConfig({
plugins: [
adonisjs({
// highlight-start
entrypoints: ['resources/js/app.js'],
entryPoints: ['resources/js/app.js'],
// highlight-end
}),
]
Expand Down
2 changes: 1 addition & 1 deletion content/start/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ For the best performance, upload your compiled assets to a CDN. This requires up
}
```

After each build, deploy the contents of `build/public` to your CDN. Your CI/CD pipeline can automate this step.
After each build, upload the client assets from `build/public/assets` to your CDN, and keep the Vite manifests and server bundles on the application server. The [Vite CDN deployment guide](../guides/frontend/vite.md#deploying-assets-to-a-cdn) covers the build-time configuration and an upload command your CI/CD pipeline can run.

:::tip
You can combine both approaches. Use a reverse proxy to serve files like `favicon.ico` and `robots.txt` from the `public` directory, and a CDN for Vite-compiled assets (JavaScript, CSS, images) that benefit from global distribution.
Expand Down