Un progetto Next.js standard segue una specifica struttura di file e directory che facilita le sue funzionalità come il routing, gli endpoint API e la gestione delle risorse statiche. Ecco un layout tipico:
public/: Ospita risorse statiche come immagini, font e altri file. I file qui sono accessibili al percorso radice (/).
app/: Directory centrale per le pagine, layout, componenti e rotte API della tua applicazione. Abbraccia il paradigma App Router, abilitando funzionalità di routing avanzate e segregazione dei componenti server-client.
app/layout.tsx: Definisce il layout radice per la tua applicazione, avvolgendo tutte le pagine e fornendo elementi UI coerenti come intestazioni, piè di pagina e barre di navigazione.
app/page.tsx: Funziona come punto di ingresso per la rotta radice /, rendendo la home page.
app/[route]/page.tsx: Gestisce rotte statiche e dinamiche. Ogni cartella all'interno di app/ rappresenta un segmento di rotta, e page.tsx all'interno di quelle cartelle corrisponde al componente della rotta.
app/api/: Contiene rotte API, consentendo di creare funzioni serverless che gestiscono richieste HTTP. Queste rotte sostituiscono la tradizionale directory pages/api.
app/components/: Contiene componenti React riutilizzabili che possono essere utilizzati in diverse pagine e layout.
app/styles/: Contiene file CSS globali e CSS Modules per lo styling a livello di componente.
app/utils/: Include funzioni di utilità, moduli helper e altra logica non UI che può essere condivisa in tutta l'applicazione.
.env.local: Memorizza variabili di ambiente specifiche per l'ambiente di sviluppo locale. Queste variabili non sono impegnate nel controllo di versione.
next.config.js: Personalizza il comportamento di Next.js, comprese le configurazioni webpack, le variabili di ambiente e le impostazioni di sicurezza.
tsconfig.json: Configura le impostazioni di TypeScript per il progetto, abilitando il controllo dei tipi e altre funzionalità di TypeScript.
package.json: Gestisce le dipendenze del progetto, gli script e i metadati.
README.md: Fornisce documentazione e informazioni sul progetto, comprese istruzioni di configurazione, linee guida per l'uso e altri dettagli rilevanti.
yarn.lock / package-lock.json: Blocca le dipendenze del progetto a versioni specifiche, garantendo installazioni coerenti in diversi ambienti.
Client-Side in Next.js
File-Based Routing in the app Directory
La directory app è la pietra angolare del routing nelle ultime versioni di Next.js. Sfrutta il filesystem per definire le rotte, rendendo la gestione delle rotte intuitiva e scalabile.
// app/about/page.tsxexportdefaultfunctionAboutPage() {return (<div><h1>About Us</h1><p>Learn more about our mission and values.</p></div>);}
Spiegazione:
Definizione della Route: Il file page.tsx all'interno della cartella about corrisponde alla route /about.
Rendering: Questo componente rende il contenuto per la pagina "about".
Route Dinamiche
Le route dinamiche consentono di gestire percorsi con segmenti variabili, permettendo alle applicazioni di visualizzare contenuti basati su parametri come ID, slugs, ecc.
tsxCopy code// app/posts/[id]/page.tsximport { useRouter } from'next/navigation';interfacePostProps {params: { id:string };}exportdefaultfunctionPostPage({ params }:PostProps) {const { id } = params;// Fetch post data based on 'id'return (<div><h1>Post #{id}</h1><p>This is the content of post {id}.</p></div>);}
Spiegazione:
Segmento Dinamico:[id] denota un segmento dinamico nella rotta, catturando il parametro id dall'URL.
Accesso ai Parametri: L'oggetto params contiene i parametri dinamici, accessibili all'interno del componente.
Corrispondenza delle Rotte: Qualsiasi percorso che corrisponde a /posts/*, come /posts/1, /posts/abc, ecc., sarà gestito da questo componente.
Rotte Annidate
Next.js supporta il routing annidato, consentendo strutture di rotta gerarchiche che rispecchiano il layout della directory.
tsxCopy code// app/dashboard/settings/profile/page.tsxexportdefaultfunctionProfileSettingsPage() {return (<div><h1>Profile Settings</h1><p>Manage your profile information here.</p></div>);}
Spiegazione:
Nesting Profondo: Il file page.tsx all'interno di dashboard/settings/profile/ corrisponde al percorso /dashboard/settings/profile.
Riflessione della Gerarchia: La struttura delle directory riflette il percorso URL, migliorando la manutenibilità e la chiarezza.
Route Catch-All
Le route catch-all gestiscono più segmenti nidificati o percorsi sconosciuti, fornendo flessibilità nella gestione delle route.
// app/[...slug]/page.tsxinterfaceCatchAllProps {params: { slug:string[] };}exportdefaultfunctionCatchAllPage({ params }:CatchAllProps) {const { slug } = params;constfullPath=`/${slug.join('/')}`;return (<div><h1>Catch-All Route</h1><p>You have navigated to: {fullPath}</p></div>);}
Spiegazione:
Segmento Catch-All:[...slug] cattura tutti i segmenti di percorso rimanenti come un array.
Utilizzo: Utile per gestire scenari di routing dinamico come percorsi generati dagli utenti, categorie annidate, ecc.
Corrispondenza delle Route: Percorsi come /anything/here, /foo/bar/baz, ecc., sono gestiti da questo componente.
Potenziali Vulnerabilità Lato Client
Sebbene Next.js fornisca una base sicura, pratiche di codifica improprie possono introdurre vulnerabilità. Le principali vulnerabilità lato client includono:
Cross-Site Scripting (XSS)
Gli attacchi XSS si verificano quando script dannosi vengono iniettati in siti web fidati. Gli aggressori possono eseguire script nei browser degli utenti, rubando dati o eseguendo azioni per conto dell'utente.
Esempio di Codice Vulnerabile:
// Dangerous: Injecting user input directly into HTMLfunctionComment({ userInput }) {return <divdangerouslySetInnerHTML={{ __html: userInput }} />;}
Perché è vulnerabile: L'uso di dangerouslySetInnerHTML con input non affidabili consente agli attaccanti di iniettare script dannosi.
Iniezione di Template lato Client
Si verifica quando gli input degli utenti non vengono gestiti correttamente nei template, consentendo agli attaccanti di iniettare ed eseguire template o espressioni.
Esempio di Codice Vulnerabile:
import React from'react';import ejs from'ejs';functionRenderTemplate({ template, data }) {consthtml=ejs.render(template, data);return <divdangerouslySetInnerHTML={{ __html: html }} />;}
Perché è vulnerabile: Se template o data includono contenuti dannosi, può portare all'esecuzione di codice non intenzionato.
Traversata del Percorso Client
È una vulnerabilità che consente agli attaccanti di manipolare i percorsi lato client per eseguire azioni non intenzionate, come il Cross-Site Request Forgery (CSRF). A differenza della traversata del percorso lato server, che prende di mira il filesystem del server, la CSPT si concentra sull'esploitazione dei meccanismi lato client per reindirizzare le richieste API legittime a endpoint dannosi.
Esempio di Codice Vulnerabile:
Un'applicazione Next.js consente agli utenti di caricare e scaricare file. La funzione di download è implementata sul lato client, dove gli utenti possono specificare il percorso del file da scaricare.
Obiettivo dell'Attaccante: Eseguire un attacco CSRF per eliminare un file critico (ad es., admin/config.json) manipolando il filePath.
Sfruttamento di CSPT:
Input Maligno: L'attaccante crea un URL con un filePath manipolato come ../deleteFile/config.json.
Chiamata API Risultante: Il codice lato client effettua una richiesta a /api/files/../deleteFile/config.json.
Gestione del Server: Se il server non convalida il filePath, elabora la richiesta, potenzialmente eliminando o esponendo file sensibili.
Esecuzione di CSRF:
Link Creato: L'attaccante invia alla vittima un link o incorpora uno script maligno che attiva la richiesta di download con il filePath manipolato.
Risultato: La vittima esegue inconsapevolmente l'azione, portando ad accessi o eliminazioni non autorizzate di file.
Perché È Vulnerabile
Mancanza di Convalida dell'Input: Il lato client consente input arbitrari di filePath, abilitando il path traversal.
Fiducia negli Input del Client: L'API lato server si fida e elabora il filePath senza sanitizzazione.
Potenziali Azioni API: Se l'endpoint API esegue azioni che modificano lo stato (ad es., eliminare, modificare file), può essere sfruttato tramite CSPT.
Lato Server in Next.js
Rendering Lato Server (SSR)
Le pagine vengono renderizzate sul server ad ogni richiesta, garantendo che l'utente riceva HTML completamente renderizzato. In questo caso, dovresti creare il tuo server personalizzato per elaborare le richieste.
Casi d'Uso:
Contenuti dinamici che cambiano frequentemente.
Ottimizzazione SEO, poiché i motori di ricerca possono eseguire la scansione della pagina completamente renderizzata.
Implementazione:
// pages/index.jsexportasyncfunctiongetServerSideProps(context) {constres=awaitfetch('https://api.example.com/data');constdata=awaitres.json();return { props: { data } };}functionHomePage({ data }) {return <div>{data.title}</div>;}exportdefault HomePage;
Static Site Generation (SSG)
Le pagine vengono pre-renderizzate al momento della costruzione, risultando in tempi di caricamento più rapidi e un carico ridotto sul server.
Use Cases:
Contenuti che non cambiano frequentemente.
Blog, documentazione, pagine di marketing.
Implementation:
// pages/index.jsexportasyncfunctiongetStaticProps() {constres=awaitfetch('https://api.example.com/data');constdata=awaitres.json();return { props: { data }, revalidate:60 }; // Revalidate every 60 seconds}functionHomePage({ data }) {return <div>{data.title}</div>;}exportdefault HomePage;
Funzioni Serverless (API Routes)
Next.js consente la creazione di endpoint API come funzioni serverless. Queste funzioni vengono eseguite su richiesta senza la necessità di un server dedicato.
Casi d'uso:
Gestione delle sottomissioni di moduli.
Interazione con i database.
Elaborazione dei dati o integrazione con API di terze parti.
Implementazione:
Con l'introduzione della directory app in Next.js 13, il routing e la gestione delle API sono diventati più flessibili e potenti. Questo approccio moderno si allinea strettamente con il sistema di routing basato su file ma introduce capacità migliorate, inclusa la supporto per componenti server e client.
// app/api/hello/route.jsexportasyncfunctionPOST(request) {returnnewResponse(JSON.stringify({ message:'Hello from App Router!' }), {status:200,headers: { 'Content-Type':'application/json' },});}// Client-side fetch to access the API endpointfetch('/api/submit', {method:'POST',headers: { 'Content-Type':'application/json' },body:JSON.stringify({ name:'John Doe' }),}).then((res) =>res.json()).then((data) =>console.log(data));
Spiegazione:
Posizione: Le rotte API si trovano nella directory app/api/.
Nomenclatura dei file: Ogni endpoint API risiede nella propria cartella contenente un file route.js o route.ts.
Funzioni esportate: Invece di un'unica esportazione predefinita, vengono esportate funzioni specifiche per i metodi HTTP (ad es., GET, POST).
Gestione delle risposte: Utilizza il costruttore Response per restituire risposte, consentendo un maggiore controllo su intestazioni e codici di stato.
Come gestire altri percorsi e metodi:
Gestione di metodi HTTP specifici
Next.js 13+ consente di definire gestori per metodi HTTP specifici all'interno dello stesso file route.js o route.ts, promuovendo un codice più chiaro e organizzato.
Esempio:
// app/api/users/[id]/route.jsexportasyncfunctionGET(request, { params }) {const { id } = params;// Fetch user data based on 'id'returnnewResponse(JSON.stringify({ userId: id, name:'Jane Doe' }), {status:200,headers: { 'Content-Type':'application/json' },});}exportasyncfunctionPUT(request, { params }) {const { id } = params;// Update user data based on 'id'returnnewResponse(JSON.stringify({ message:`User ${id} updated.` }), {status:200,headers: { 'Content-Type':'application/json' },});}exportasyncfunctionDELETE(request, { params }) {const { id } = params;// Delete user based on 'id'returnnewResponse(JSON.stringify({ message:`User ${id} deleted.` }), {status:200,headers: { 'Content-Type':'application/json' },});}
Spiegazione:
Esportazioni Multiple: Ogni metodo HTTP (GET, PUT, DELETE) ha la propria funzione esportata.
Parametri: Il secondo argomento fornisce accesso ai parametri della rotta tramite params.
Risposte Migliorate: Maggiore controllo sugli oggetti di risposta, consentendo una gestione precisa degli header e dei codici di stato.
Rotte Catch-All e Annidate
Next.js 13+ supporta funzionalità di routing avanzate come le rotte catch-all e le rotte API annidate, consentendo strutture API più dinamiche e scalabili.
Sintassi:[...] denota un segmento catch-all, catturando tutti i percorsi annidati.
Utilizzo: Utile per le API che devono gestire profondità di percorso variabili o segmenti dinamici.
Esempio di Percorsi Annidati:
// app/api/posts/[postId]/comments/[commentId]/route.jsexportasyncfunctionGET(request, { params }) {const { postId,commentId } = params;// Fetch specific comment for a postreturnnewResponse(JSON.stringify({ postId, commentId, comment:'Great post!' }), {status:200,headers: { 'Content-Type':'application/json' },});}
Spiegazione:
Profondità di Annidamento: Consente strutture API gerarchiche, riflettendo le relazioni tra le risorse.
Accesso ai Parametri: Accesso facile a più parametri di route tramite l'oggetto params.
Gestione delle route API in Next.js 12 e versioni precedenti
Route API nella Directory pages (Next.js 12 e versioni precedenti)
Prima che Next.js 13 introducesse la directory app e migliorasse le capacità di routing, le route API erano principalmente definite all'interno della directory pages. Questo approccio è ancora ampiamente utilizzato e supportato in Next.js 12 e versioni precedenti.
javascriptCopy code// pages/api/users/[id].jsexportdefaultfunctionhandler(req, res) {const {query: { id },method,} = req;switch (method) {case'GET':// Fetch user data based on 'id'res.status(200).json({ userId: id, name:'John Doe' });break;case'PUT':// Update user data based on 'id'res.status(200).json({ message:`User ${id} updated.` });break;case'DELETE':// Delete user based on 'id'res.status(200).json({ message:`User ${id} deleted.` });break;default:res.setHeader('Allow', ['GET','PUT','DELETE']);res.status(405).end(`Method ${method} Not Allowed`);}}
Spiegazione:
Segmenti Dinamici: Le parentesi quadre ([id].js) denotano segmenti di percorso dinamici.
Accesso ai Parametri: Usa req.query.id per accedere al parametro dinamico.
Gestione dei Metodi: Utilizza la logica condizionale per gestire diversi metodi HTTP (GET, PUT, DELETE, ecc.).
Gestione di Diversi Metodi HTTP
Mentre l'esempio di percorso API di base gestisce tutti i metodi HTTP all'interno di una singola funzione, puoi strutturare il tuo codice per gestire ogni metodo esplicitamente per una maggiore chiarezza e manutenibilità.
Esempio:
javascriptCopy code// pages/api/posts.jsexportdefaultasyncfunctionhandler(req, res) {const { method } = req;switch (method) {case'GET':// Handle GET requestres.status(200).json({ message:'Fetching posts.' });break;case'POST':// Handle POST requestres.status(201).json({ message:'Post created.' });break;default:res.setHeader('Allow', ['GET','POST']);res.status(405).end(`Method ${method} Not Allowed`);}}
Best Practices:
Separazione delle preoccupazioni: Separare chiaramente la logica per i diversi metodi HTTP.
Coerenza delle risposte: Garantire strutture di risposta coerenti per facilitare la gestione lato client.
Gestione degli errori: Gestire in modo elegante i metodi non supportati e gli errori imprevisti.
Configurazione CORS
Controlla quali origini possono accedere alle tue rotte API, mitigando le vulnerabilità di Cross-Origin Resource Sharing (CORS).
Esempio di cattiva configurazione:
// app/api/data/route.jsexportasyncfunctionGET(request) {returnnewResponse(JSON.stringify({ data:'Public Data' }), {status:200,headers: {'Access-Control-Allow-Origin':'*',// Allows any origin'Access-Control-Allow-Methods':'GET, POST, PUT, DELETE',},});}
Nota che CORS può essere configurato anche in tutti i percorsi API all'interno del file middleware.ts:
// app/middleware.tsimport { NextResponse } from'next/server';importtype { NextRequest } from'next/server';exportfunctionmiddleware(request:NextRequest) {constallowedOrigins= ['https://yourdomain.com','https://sub.yourdomain.com'];constorigin=request.headers.get('Origin');constresponse=NextResponse.next();if (allowedOrigins.includes(origin ||'')) {response.headers.set('Access-Control-Allow-Origin', origin ||'');response.headers.set('Access-Control-Allow-Methods','GET, POST, PUT, DELETE, OPTIONS');response.headers.set('Access-Control-Allow-Headers','Content-Type, Authorization');// If credentials are needed:// response.headers.set('Access-Control-Allow-Credentials', 'true');}// Handle preflight requestsif (request.method ==='OPTIONS') {returnnewResponse(null, {status:204,headers:response.headers,});}return response;}exportconstconfig= {matcher:'/api/:path*',// Apply to all API routes};
Problema:
Access-Control-Allow-Origin: '*': Permette a qualsiasi sito web di accedere all'API, potenzialmente consentendo a siti dannosi di interagire con la tua API senza restrizioni.
Ampia autorizzazione dei metodi: Consentire tutti i metodi può consentire agli attaccanti di eseguire azioni indesiderate.
Come gli attaccanti lo sfruttano:
Gli attaccanti possono creare siti web dannosi che effettuano richieste alla tua API, potenzialmente abusando di funzionalità come il recupero dei dati, la manipolazione dei dati o l'attivazione di azioni indesiderate per conto di utenti autenticati.
È facile utilizzare il codice usato dal server anche nel codice esposto e utilizzato dal lato client, il modo migliore per garantire che un file di codice non sia mai esposto nel lato client è utilizzare questo import all'inizio del file:
import"server-only";
File Chiave e Loro Ruoli
middleware.ts / middleware.js
Posizione: Radice del progetto o all'interno di src/.
Scopo: Esegue codice nella funzione serverless lato server prima che una richiesta venga elaborata, consentendo operazioni come autenticazione, reindirizzamenti o modifica delle risposte.
Flusso di Esecuzione:
Richiesta in Arrivo: Il middleware intercetta la richiesta.
Elaborazione: Esegue operazioni basate sulla richiesta (ad es., verifica dell'autenticazione).
Modifica della Risposta: Può alterare la risposta o passare il controllo al gestore successivo.
Scopo: Configura il comportamento di Next.js, abilitando o disabilitando funzionalità, personalizzando le configurazioni di webpack, impostando variabili di ambiente e configurando diverse funzionalità di sicurezza.
Configurazioni di Sicurezza Chiave:
Intestazioni di Sicurezza
Le intestazioni di sicurezza migliorano la sicurezza della tua applicazione istruendo i browser su come gestire i contenuti. Aiutano a mitigare vari attacchi come Cross-Site Scripting (XSS), Clickjacking e sniffing del tipo MIME:
Next.js ottimizza le immagini per le prestazioni, ma configurazioni errate possono portare a vulnerabilità di sicurezza, come consentire a fonti non affidabili di iniettare contenuti dannosi.
Esempio di Configurazione Errata:
// next.config.jsmodule.exports= {images: {domains: ['*'],// Allows images from any domain},};
Problema:
'*': Permette il caricamento di immagini da qualsiasi fonte esterna, inclusi domini non affidabili o malevoli. Gli attaccanti possono ospitare immagini contenenti payload o contenuti malevoli che ingannano gli utenti.
Un altro problema potrebbe essere quello di consentire un dominio dove chiunque può caricare un'immagine (come raw.githubusercontent.com)
Come gli attaccanti ne abusano:
Iniettando immagini da fonti malevole, gli attaccanti possono eseguire attacchi di phishing, visualizzare informazioni fuorvianti o sfruttare vulnerabilità nelle librerie di rendering delle immagini.
Esposizione delle Variabili Ambientali
Gestire informazioni sensibili come chiavi API e credenziali del database in modo sicuro senza esporle al client.
a. Esposizione di Variabili Sensibili
Esempio di Configurazione Errata:
// next.config.jsmodule.exports= {env: {SECRET_API_KEY:process.env.SECRET_API_KEY,// Exposed to the clientNEXT_PUBLIC_API_URL:process.env.NEXT_PUBLIC_API_URL,// Correctly prefixed for client},};
Problema:
SECRET_API_KEY: Senza il prefisso NEXT_PUBLIC_, Next.js non espone le variabili al client. Tuttavia, se erroneamente prefissato (ad es., NEXT_PUBLIC_SECRET_API_KEY), diventa accessibile dal lato client.
Come gli attaccanti ne abusano:
Se le variabili sensibili sono esposte al client, gli attaccanti possono recuperarle ispezionando il codice lato client o le richieste di rete, ottenendo accesso non autorizzato a API, database o altri servizi.
Redirects
Gestisci le redirezioni e le riscritture degli URL all'interno della tua applicazione, assicurandoti che gli utenti siano indirizzati in modo appropriato senza introdurre vulnerabilità di redirect aperto.
a. Vulnerabilità di Redirect Aperto
Esempio di Configurazione Errata:
// next.config.jsmodule.exports= {asyncredirects() {return [{source:'/redirect',destination: (req) =>req.query.url,// Dynamically redirects based on query parameterpermanent:false,},];},};
Problema:
Destinazione Dinamica: Consente agli utenti di specificare qualsiasi URL, abilitando attacchi di open redirect.
Fiducia nell'Input dell'Utente: I reindirizzamenti a URL forniti dagli utenti senza validazione possono portare a phishing, distribuzione di malware o furto di credenziali.
Come gli attaccanti lo abusano:
Gli attaccanti possono creare URL che sembrano provenire dal tuo dominio ma reindirizzano gli utenti a siti malevoli. Ad esempio:
Gli utenti che si fidano del dominio originale potrebbero navigare inconsapevolmente verso siti web dannosi.
Configurazione di Webpack
Personalizza le configurazioni di Webpack per la tua applicazione Next.js, che possono involontariamente introdurre vulnerabilità di sicurezza se non gestite con cautela.
Esposizione di Percorsi Sensibili: L'aliasing di directory sensibili e la possibilità di accesso lato client possono leak informazioni riservate.
Bundling di Segreti: Se file sensibili sono inclusi per il client, i loro contenuti diventano accessibili tramite mappe sorgente o ispezionando il codice lato client.
Come gli attaccanti ne abusano:
Gli attaccanti possono accedere o ricostruire la struttura delle directory dell'applicazione, trovando e sfruttando potenzialmente file o dati sensibili.
pages/_app.js e pages/_document.js
pages/_app.js
Scopo: Sovrascrive il componente App predefinito, consentendo stati globali, stili e componenti di layout.
Scopo: Anche se Next.js viene fornito con un server integrato, puoi creare un server personalizzato per casi d'uso avanzati come il routing personalizzato o l'integrazione con servizi backend esistenti.
Nota: L'uso di un server personalizzato può limitare le opzioni di distribuzione, specialmente su piattaforme come Vercel che ottimizzano per il server integrato di Next.js.
Considerazioni Architettoniche e di Sicurezza Aggiuntive
Variabili d'Ambiente e Configurazione
Scopo: Gestire informazioni sensibili e impostazioni di configurazione al di fuori del codice sorgente.
Migliori Pratiche:
Usa File .env: Memorizza variabili come le chiavi API in .env.local (escluse dal controllo di versione).
Accedi alle Variabili in Modo Sicuro: Usa process.env.VARIABLE_NAME per accedere alle variabili d'ambiente.
Non Esporre Mai Segreti sul Client: Assicurati che le variabili sensibili siano utilizzate solo lato server.
Esempio:
// next.config.jsmodule.exports= {env: {API_KEY:process.env.API_KEY,// Accessible on both client and serverSECRET_KEY:process.env.SECRET_KEY,// Be cautious if accessible on the client},};
Nota: Per limitare le variabili solo al server, omettere dal oggetto env o prefissarle con NEXT_PUBLIC_ per l'esposizione al client.
Autenticazione e Autorizzazione
Approccio:
Autenticazione Basata su Sessione: Utilizzare i cookie per gestire le sessioni utente.
Autenticazione Basata su Token: Implementare JWT per un'autenticazione senza stato.
Fornitori di Terze Parti: Integrare con fornitori OAuth (ad es., Google, GitHub) utilizzando librerie come next-auth.
Pratiche di Sicurezza:
Cookie Sicuri: Impostare gli attributi HttpOnly, Secure e SameSite.
Hashing delle Password: Eseguire sempre l'hashing delle password prima di memorizzarle.
Validazione degli Input: Prevenire attacchi di iniezione validando e sanificando gli input.