오래전에 HTTP 헤더 중 X-Requested-With 에 대해(https://yiunsr.tistory.com/875) 라는 글을 쓴 적있다.
flask 서버에서 어떻게 브라우저 주소창으로 들어 올 때와 ajax 로 요청했을 때의 차이를 구별하는 것에 대해 정리한 글이다. 
그 때에는 이 둘을 본질적으로 구별하기 어렵고 X-Requested-With 을 사용하는 곳에서 설정해서 사용한다고 했다. 

웹브라우저가 발전하게 되면서 이제는 이것을 구별하는 기능이 생겼다. "Sec-Fetch-Dest" 라는 헤더를 이용하면 된다. 

* javascript 요청(ajax)
    * Sec-Fetch-Dest: empty (특정 HTML 태그가 아닌 데이터 자체를 가져온다는 의미)
* html 요청
    *  Sec-Fetch-Dest: document (목적지가 HTML 문서 전체라는 의미)

이런 식으로 웹브라우저가 Sec-Fetch-Dest 키에대해 값을 채워 보내준다.
최소한 Chrome, Edge, firefox, safari, opera 에서는 이 기능을 제공한다. 

내가 잘 사용하는 flask 에서는 

fetch_dest = request.headers.get('Sec-Fetch-Dest')

if fetch_dest == 'document':
    # html 페이지를 보내준다. 
    return render_template('data_page.html')

# 그 외 JavaScript fetch API 등 데이터 요청인 경우 JSON 응답
else:
    # json 으로 응답한다. 
    return jsonify({"status": "success"})


이렇게 header 값에 따라서 내보내는 콘텐츠를 지정해서 보낼 수 있다.