┌─────────────────────────────────────────────────────────────────┐
│ TaskStars App │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────┐ ┌─────────────────────┐ │
│ │ Client (Next.js) │◄────────────►│ Server (Express) │ │
│ │ │ │ │ │
│ │ Uses: │ │ Uses: │ │
│ │ • .env.local │ │ • .env.local │ │
│ │ • .env.production │ │ • .env.production │ │
│ └────────────────────┘ └─────────────────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────┐ ┌─────────────────────┐ │
│ │ config/api.js │ │ Environment Vars │ │
│ │ │ │ │ │
│ │ API_URL │ │ CLIENT_URL │ │
│ │ SOCKET_URL │ │ SERVER_URL │ │
│ │ BACKEND_URL │ │ MONGO_URI │ │
│ └────────────────────┘ │ JWT_SECRET │ │
│ │ GOOGLE_CLIENT_* │ │
│ │ GITHUB_CLIENT_* │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
if (process.env.NODE_ENV === "production") {
dotenv.config({ path: ".env.production" }); // ← Loads production config
} else {
dotenv.config({ path: ".env.local" }); // ← Loads development config
}Next.js automatically loads environment files based on NODE_ENV:
NODE_ENV=development→ Loads.env.localNODE_ENV=production→ Loads.env.production
1. You run: npm run dev
↓
2. Sets: NODE_ENV=development
↓
3. Server loads: server/.env.local
4. Client loads: client/.env.local
↓
5. All API calls use: http://localhost:8080
6. All CORS allows: http://localhost:3000
7. OAuth redirects to: http://localhost:8080/api/auth/*/callback
1. You run: npm start
↓
2. Sets: NODE_ENV=production
↓
3. Server loads: server/.env.production
4. Client loads: client/.env.production
↓
5. All API calls use: https://taskstars.onrender.com
6. All CORS allows: https://taskstars.onrender.com
7. OAuth redirects to: https://taskstars.onrender.com/api/auth/*/callback
// Reads from environment variable set in .env.local or .env.production
export const API_URL =
process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080";
export const SOCKET_URL =
process.env.NEXT_PUBLIC_SOCKET_URL || "http://localhost:8080";import { API_URL } from "@/config/api";
// Now all API calls use the configured URL
fetch(`${API_URL}/api/tasks/readtasks`);// CORS Configuration
app.use(
cors({
origin: process.env.CLIENT_URL || "http://localhost:3000",
credentials: true,
})
);
// OAuth Callbacks
callbackURL: `${
process.env.SERVER_URL || "http://localhost:8080"
}/api/auth/google/callback`;TaskStars/
├── server/
│ ├── env.template ← Template showing all variables
│ ├── .env.local ← Your dev config (gitignored)
│ ├── .env.production ← Your prod config (gitignored)
│ ├── server.js ← Loads correct .env file
│ ├── config/
│ │ └── passportConfig.js ← Uses SERVER_URL for OAuth
│ └── routes/
│ └── authRoutes.js ← Uses CLIENT_URL for redirects
│
├── client/
│ ├── env.template ← Template showing all variables
│ ├── .env.local ← Your dev config (gitignored)
│ ├── .env.production ← Your prod config (gitignored)
│ ├── config/
│ │ └── api.js ← Central config (NEW!)
│ ├── app/
│ │ ├── login/page.js ← Imports from config/api.js
│ │ └── signup/page.js ← Imports from config/api.js
│ └── components/
│ ├── TaskList.js ← Imports from config/api.js
│ ├── TaskManager.js ← Imports from config/api.js
│ └── ... ← All import from config/api.js
│
└── package.json ← Root scripts for easy switching
// Hardcoded in every file
const socket = io("https://taskstars.onrender.com");
fetch("https://taskstars.onrender.com/api/tasks");
cors({ origin: "https://taskstars.onrender.com" });// Dynamic based on environment
import { SOCKET_URL, API_URL } from "@/config/api";
const socket = io(SOCKET_URL);
fetch(`${API_URL}/api/tasks`);
cors({ origin: process.env.CLIENT_URL });- One command:
npm run dev - Automatically uses local URLs
- No code changes needed
- One command:
npm start - Automatically uses production URLs
- No code changes needed
- All secrets in
.envfiles - Never committed to git
- Different credentials for dev/prod
- Each developer can have their own
.env.local - Templates provided for easy setup
- Clear documentation
- Works with any hosting platform
- Set environment variables in platform dashboard
- No code changes for different environments
Client side:
import { API_URL } from "@/config/api";
const response = await fetch(`${API_URL}/api/your-new-endpoint`);Server side:
- Add to
server/env.template - Add to your
server/.env.local - Add to your
server/.env.production - Use with
process.env.YOUR_VARIABLE
Client side:
- Add to
client/env.template(withNEXT_PUBLIC_prefix) - Add to your
client/.env.local - Add to your
client/.env.production - Use with
process.env.NEXT_PUBLIC_YOUR_VARIABLE
npm run dev
# Visit http://localhost:3000
# Should connect to local backendnpm run build
npm start
# Should use production URLs# Server: Check console on startup
# Should print: "Server running on port 8080"
# Should print: "MongoDB Connected: [your-mongo-host]"
# Client: Check browser console
# API calls should go to the correct URL| Problem | Solution |
|---|---|
| API calls go to wrong URL | Check NEXT_PUBLIC_API_URL in client .env file |
| CORS errors | Check CLIENT_URL in server .env file |
| OAuth fails | Check SERVER_URL in server .env file |
| Changes not working | Restart both server and client |
| Variables undefined | Restart and check variable names |
The key changes enable environment-based configuration:
- Centralized Config (
client/config/api.js) - Environment Variables (
.env.localand.env.production) - Automatic Selection (Based on
NODE_ENV) - No More Hardcoding (Dynamic URLs everywhere)
- Easy Switching (Simple commands)
You can now develop locally and deploy to production without changing any code! 🎉