Skip to content
Merged
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
18 changes: 17 additions & 1 deletion packages/redirects/src/lib/rewriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const createRewriter = async function ({
configPath,
configRedirects,
geoCountry,
ignoreSPARedirect = false,
jwtRoleClaim,
jwtSecret,
projectDir,
Expand All @@ -31,6 +32,7 @@ export const createRewriter = async function ({
configPath?: string | undefined
configRedirects: Redirect[]
geoCountry?: string | undefined
ignoreSPARedirect?: boolean
jwtRoleClaim: string
jwtSecret: string
projectDir: string
Expand All @@ -40,7 +42,21 @@ export const createRewriter = async function ({
const redirectsFiles = [
...new Set([path.resolve(publicDir ?? '', REDIRECTS_FILE_NAME), path.resolve(projectDir, REDIRECTS_FILE_NAME)]),
]
const redirects = await parseRedirects({ configRedirects, redirectsFiles, configPath })
let redirects = await parseRedirects({ configRedirects, redirectsFiles, configPath })

// Hacky solution: Filter out the SPA redirect pattern when requested.
// This prevents the redirect from interfering with local dev servers like Vite,
// while still allowing it to work in production.
// See: https://github.com/netlify/primitives/issues/325
if (ignoreSPARedirect) {
redirects = redirects.filter((redirect) => {
// Filter out redirects that match the SPA pattern: from "/*" to "/index.html" with status 200
// See https://docs.netlify.com/manage/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps,
const isSPARedirect = redirect.origin === '/*' && redirect.to === '/index.html' && redirect.status === 200

return !isSPARedirect
})
}

const getMatcher = async (): Promise<RedirectMatcher> => {
if (matcher) return matcher
Expand Down
1 change: 1 addition & 0 deletions packages/redirects/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class RedirectsHandler {
configPath,
configRedirects,
geoCountry,
ignoreSPARedirect: true,
jwtRoleClaim,
jwtSecret,
projectDir,
Expand Down
56 changes: 56 additions & 0 deletions packages/vite-plugin/src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,62 @@ defined on your team and site and much more. Run npx netlify init to get started
await server.close()
await fixture.destroy()
})

test('Ignores SPA redirect in dev mode', async () => {
const fixture = new Fixture()
.withFile(
'netlify.toml',
`[[redirects]]
from = "/*"
to = "/index.html"
status = 200`,
)
.withFile(
'vite.config.js',
`import { defineConfig } from 'vite';
import netlify from '@netlify/vite-plugin';

export default defineConfig({
plugins: [
netlify({
middleware: true,
})
]
});`,
)
.withFile(
'index.html',
`<!DOCTYPE html>
<html>
<head><title>SPA App</title></head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>`,
)
.withFile('src/main.js', `document.getElementById('app').textContent = 'Hello from SPA'`)
const directory = await fixture.create()
await fixture
.withPackages({
vite: viteVersion,
'@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(),
})
.create()

const { server, url } = await startTestServer({
root: directory,
})

// Any route should render the root index.html (Vite handles it) and JS should execute (which
// verifies the SPA redirect isn't interfering with loading the .js module).
await page.goto(`${url}/some-route`)
await page.waitForSelector('#app')
expect(await page.textContent('#app')).toBe('Hello from SPA')

await server.close()
await fixture.destroy()
})
})

describe('With @vitejs/plugin-react', () => {
Expand Down