Les astuces les plus dingues sur Next.js que tous les devs devraient connaître

June 11, 2025

Les astuces Next.js les plus étranges que tout développeur devrait connaître

Next.js est un framework incroyable qui a révolutionné le développement React, mais comme tout outil complexe, il a ses particularités et ses cas limites. Parfois, les solutions les plus élégantes sont aussi les plus inhabituelles. Voici les astuces Next.js les plus étranges que des développeurs expérimentés ont découvertes à force d'essais, d'erreurs et d'innombrables sessions de débogage nocturnes.

1. Le correctif d'hydratation "Composant Invisible"

Le problème : Des incohérences d'hydratation lorsque le serveur et le client affichent un contenu différent basé sur des données dynamiques.

La solution étrange : Créer un composant invisible qui force la ré-hydratation :

const HydrationFix = ({ children }) => {
  const [isClient, setIsClient] = useState(false)

  useEffect(() => {
    setIsClient(true)
  }, [])

  return isClient ? children : <div style={{opacity: 0}}>{children}</div>
}

Cette technique prévient les erreurs d'hydratation en rendant une version transparente côté serveur et le contenu réel côté client.

2. La promesse Router.push qui n'en est pas une

Le problème : router.push() retourne une promesse, mais ne se comporte pas toujours comme prévu dans les fonctions asynchrones.

La solution étrange : Utiliser un wrap avec un délai (timeout) :

const reliableRouterPush = async (url) => {
  router.push(url)
  await new Promise(resolve => setTimeout(resolve, 0))
  return new Promise(resolve => {
    const checkRoute = () => {
      if (router.asPath === url) {
        resolve()
      } else {
        setTimeout(checkRoute, 50)
      }
    }
    checkRoute()
  })
}

3. L'astuce de chargement d'image "Rafraîchissement Forcé"

Le problème : Le composant Image de Next.js met parfois en cache de manière trop agressive, empêchant l'affichage des images mises à jour.

La solution étrange : Ajouter un paramètre de requête de type horodatage :

const ForceRefreshImage = ({ src, ...props }) => {
  const [timestamp, setTimestamp] = useState(Date.now())

  const refreshImage = () => setTimestamp(Date.now())

  return (
    <Image 
      {...props}
      src={`${src}?t=${timestamp}`}
      onError={refreshImage}
    />
  )
}

4. Le préventif de fuite de mémoire pour les imports dynamiques

Le problème : Les imports dynamiques dans useEffect peuvent causer des fuites de mémoire si les composants sont démontés avant la fin de l'import.

La solution étrange : Utiliser un jeton d'annulation basé sur une référence (ref) :

const useDynamicImport = (importFunc) => {
  const [component, setComponent] = useState(null)
  const cancelRef = useRef(false)

  useEffect(() => {
    cancelRef.current = false
    importFunc().then(module => {
      if (!cancelRef.current) {
        setComponent(() => module.default)
      }
    })

    return () => {
      cancelRef.current = true
    }
  }, [importFunc])

  return component
}

5. La route API "Fantôme" pour les redirections côté client

Le problème : Les redirections côté client ne fonctionnent pas toujours de manière fiable avec les URL externes dans certains navigateurs.

La solution étrange : Créer une route API proxy :

// pages/api/redirect.js
export default function handler(req, res) {
  const { url } = req.query
  if (!url) return res.status(400).json({ error: 'URL required' })

  res.writeHead(302, { Location: url })
  res.end()
}

// Utilisation
router.push(`/api/redirect?url=${encodeURIComponent(externalUrl)}`)

6. Le stabilisateur d'hydratation CSS-in-JS

Le problème : Les bibliothèques CSS-in-JS peuvent causer des incohérences d'hydratation en raison d'une génération de noms de classes différente.

La solution étrange : Utiliser un préfixe de nom de classe déterministe :

const StableStyled = styled.div.withConfig({
  shouldForwardProp: () => true,
  displayName: 'StableStyled',
})`
  /* Vos styles */
`

// Ou avec emotion
const stableClassName = css`
  /* styles */
  label: stable-${typeof window !== 'undefined' ? 'client' : 'server'};
`

7. Le préventif de décalage de mise en page "Invisible"

Le problème : Les états de chargement entraînent des problèmes de décalage de mise en page cumulatif (CLS).

La solution étrange : Pré-allouer de l'espace avec du contenu invisible :

const AntiCLSWrapper = ({ children, isLoading, height }) => {
  return (
    <div style={{ minHeight: height }}>
      {isLoading ? (
        <div 
          style={{ 
            height, 
            visibility: 'hidden' 
          }} 
          aria-hidden="true"
        >
          {children}
        </div>
      ) : children}
    </div>
  )
}

8. Le synchronisateur d'état "Voyageur Temporel"

Le problème : Les mises à jour d'état provenant de différentes sources (localStorage, API, saisie utilisateur) peuvent entrer en conflit.

La solution étrange : Utiliser des horodatages pour déterminer la précédence de l'état :

const useTimestampedState = (key, initialValue) => {
  const [state, setState] = useState({
    value: initialValue,
    timestamp: Date.now()
  })

  const setTimestampedState = (newValue, force = false) => {
    const timestamp = Date.now()
    setState(current => {
      if (force || timestamp > current.timestamp) {
        return { value: newValue, timestamp }
      }
      return current
    })
  }

  return [state.value, setTimestampedState]
}

9. Le moteur de rendu de composants "Quantique"

Le problème : Certains composants doivent exister dans plusieurs états simultanément pendant les transitions.

La solution étrange : Rendre plusieurs versions et contrôler la visibilité :

const QuantumComponent = ({ state, children }) => {
  const [renderStates, setRenderStates] = useState([state])

  useEffect(() => {
    if (!renderStates.includes(state)) {
      setRenderStates(prev => [...prev, state])
    }

    const timeout = setTimeout(() => {
      setRenderStates([state])
    }, 1000)

    return () => clearTimeout(timeout)
  }, [state])

  return (
    <div>
      {renderStates.map(renderState => (
        <div 
          key={renderState}
          style={{
            position: 'absolute',
            opacity: renderState === state ? 1 : 0,
            pointerEvents: renderState === state ? 'auto' : 'none'
          }}
        >
          {children(renderState)}
        </div>
      ))}
    </div>
  )
}

10. La "trappe d'évacuation" d'urgence pour les limites d'erreur

Le problème : Les limites d'erreur peuvent piéger les erreurs de manière trop agressive, empêchant le débogage en développement.

La solution étrange : Créer une limite d'erreur consciente de l'environnement de développement :

class WeirdErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false, errorCount: 0 }
  }

  static getDerivedStateFromError(error) {
    return { hasError: true }
  }

  componentDidCatch(error, errorInfo) {
    this.setState(prev => ({ errorCount: prev.errorCount + 1 }))

    // En développement, laisser passer les erreurs après 3 tentatives
    if (process.env.NODE_ENV === 'development' && this.state.errorCount > 3) {
      throw error
    }
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>Un problème est survenu.</h2>
          <button onClick={() => this.setState({ hasError: false })}>
            Réessayer
          </button>
        </div>
      )
    }

    return this.props.children
  }
}

Pourquoi ces astuces fonctionnent-elles ?

Ces solutions non conventionnelles répondent à des problèmes concrets qui découlent de :

  1. Incohérences des navigateurs : Chaque navigateur gère certaines opérations différemment.
  2. Cycle de vie de React : Comprendre quand et comment React met à jour les composants.
  3. Architecture de Next.js : Travailler avec le rendu côté serveur et les systèmes de routage du framework.
  4. Optimisation des performances : Prévenir les pièges courants liés aux performances.
  5. Expérience utilisateur : Maintenir des interactions fluides même lorsque les choses tournent mal.

Quand utiliser ces techniques ?

  • En dernier recours : Essayez d'abord les solutions conventionnelles.
  • Problèmes avérés : Utilisez-les lorsque vous avez identifié un problème spécifique.
  • Tests requis : Testez toujours minutieusement sur différents navigateurs et appareils.
  • Documentation : Commentez votre code de manière approfondie lorsque vous utilisez ces techniques.
  • Accord de l'équipe : Assurez-vous que votre équipe comprend et approuve les approches non conventionnelles.

Conclusion

Bien que ces astuces puissent sembler bizarres, elles ont été éprouvées dans des applications en production. Elles représentent la sagesse collective des développeurs qui ont rencontré des cas limites et trouvé des solutions créatives. N'oubliez pas que le meilleur code n'est souvent pas le plus élégant, c'est celui qui résout des problèmes réels de manière fiable.

Gardez ces techniques dans votre boîte à outils lorsque les approches conventionnelles échouent. N'oubliez pas de bien les documenter et de les utiliser avec discernement. Parfois, la solution la plus étrange est exactement ce dont vous avez besoin pour livrer votre application avec succès.

Bon code, et que votre hydratation soit toujours parfaite !

Partager cet article