Serving .well-known/apple-app-site-association (extensionless file) with correct Content-Type
Anonymous
FREEOP

2 months ago

Hi!

I'm deploying a Vite SPA to Railway and need to serve the Apple App Site Association file at /.well-known/apple-app-site-association. This file:

- Has no file extension (Apple's requirement)

- Must be served with Content-Type: application/json

Using npx serve with the -s flag for SPA routing, the file returns index.html instead of the actual file contents. This is a https://github.com/vercel/serve-handler/issues/142 - it doesn't properly handle extensionless files in .well-known.

What I've tried:

1. serve.json with rewrites (renaming to .json and rewriting) - rewrites don't seem to apply

2. serve.json with cleanUrls: false - no effect

3. Running serve from inside the dist directory with -c flag - .well-known returns 404

Is there a recommended way to serve extensionless files with custom headers on Railway without a custom server? Perhaps a built-in nginx config option or another static file server that handles this well?

Solved$10 Bounty

Pinned Solution

domehane
FREE

2 months ago

okay , you can create a lightweight Express server that properly handles the file

server.js :

import express from 'express';

import path from 'path';

import { fileURLToPath } from 'url';

import fs from 'fs';

const __filename = fileURLToPath(import.meta.url);

const dirname = path.dirname(filename);

const app = express();

const PORT = process.env.PORT || 3000;

const distPath = path.join(__dirname, 'dist');

app.get('/.well-known/apple-app-site-association', (req, res) => {

const filePath = path.join(distPath, '.well-known', 'apple-app-site-association');

if (fs.existsSync(filePath)) {

res.setHeader('Content-Type', 'application/json');

res.sendFile(filePath);

} else {

res.status(404).send('Not found');

}

});

app.use(express.static(distPath));

app.get('*', (req, res) => {

res.sendFile(path.join(distPath, 'index.html'));

});

app.listen(PORT, () => {

console.logServer running on port ${PORT});

});

and package.json for Express setup :

{

"name": "your-vite-app",

"type": "module",

"scripts": {

"dev": "vite",

"build": "vite build",

"start": "node server.js"

},

"dependencies": {

"express": "^4.18.2"

},

"devDependencies": {

"vite": "^5.0.0"

}

}

the railway setup for Express is a build command: npm install && npm run build and a Start Command: npm start no nixpacks.toml needed

5 Replies

Railway
BOT

2 months ago

Hey there! We've found the following might help you get unblocked faster:

If you find the answer from one of these, please let us know by solving the thread!


domehane
FREE

2 months ago

add this Dockerfile at the root of your project :

# Multi-stage build

FROM node:20-alpine AS builder

WORKDIR /app

# Copy package files

COPY package*.json ./

# Install dependencies

RUN npm ci

# Copy source files

COPY . .

# Build the app

RUN npm run build

# Production stage

FROM nginx:alpine

# Copy built app from builder stage

COPY --from=builder /app/dist /usr/share/nginx/html

# Copy custom nginx configuration

COPY nginx.conf /etc/nginx/conf.d/default.conf

# Expose port 80

EXPOSE 80

# Start nginx

CMD ["nginx", "-g", "daemon off;"]


Anonymous
FREEOP

2 months ago

Hi! Thanks, @domehane.

Is there a solution no involving setting up a Dockerfile? This app is inside a monorepo, and I'd rather avoid the additional complexity of directly maintaining a Dockerfile.


domehane
FREE

2 months ago

okay , you can create a lightweight Express server that properly handles the file

server.js :

import express from 'express';

import path from 'path';

import { fileURLToPath } from 'url';

import fs from 'fs';

const __filename = fileURLToPath(import.meta.url);

const dirname = path.dirname(filename);

const app = express();

const PORT = process.env.PORT || 3000;

const distPath = path.join(__dirname, 'dist');

app.get('/.well-known/apple-app-site-association', (req, res) => {

const filePath = path.join(distPath, '.well-known', 'apple-app-site-association');

if (fs.existsSync(filePath)) {

res.setHeader('Content-Type', 'application/json');

res.sendFile(filePath);

} else {

res.status(404).send('Not found');

}

});

app.use(express.static(distPath));

app.get('*', (req, res) => {

res.sendFile(path.join(distPath, 'index.html'));

});

app.listen(PORT, () => {

console.logServer running on port ${PORT});

});

and package.json for Express setup :

{

"name": "your-vite-app",

"type": "module",

"scripts": {

"dev": "vite",

"build": "vite build",

"start": "node server.js"

},

"dependencies": {

"express": "^4.18.2"

},

"devDependencies": {

"vite": "^5.0.0"

}

}

the railway setup for Express is a build command: npm install && npm run build and a Start Command: npm start no nixpacks.toml needed


Anonymous
FREEOP

2 months ago

Okay great, I ended up doing that! Thanks!


Status changed to Solved ray-chen 3 months ago


Loading...