How can configuration values be stored on an ESP8266? I have a project that uses an MQTT service, and I'm wondering how to best store the configuration settings (server, user, password, port) for the service as right now they are just hard-coded into some constants.

I saw that EEPROM can be used for this but I'm not sure it is the best way. I went to look at the code for WifiManager to see how it accomplishes this task for the SSID and password since it works so well and it removed its EEPROM dependency back in 2015: https://github.com/tzapu/WiFiManager/commit/659df9cb277c5f93946d9d8795e39a5ae5863ab1

I can't figure out how those settings are persisted. 

If EEPROM is the best way for storing something like this, I'd like to write my values without overwriting/corrupting existing values, is that possible? How can I set the initial values of the EEPROM to the constants that I need?

Edit: well after digging into the esp8266/Arduino repository I found that the WiFi.SSID() and WiFi.psk() settings come from wifi_station_get_config and stored with wifi_station_set_config although I haven't found how those work so I think EEPROM might be the way to go here, I just need a bit of guidance and clarification on the right way to use it.
Thanks to Jaromanda X and Juraj for the SPIFFS recommendation. I was able to use this block of SPIFFS code as an example and safely store settings in my project outside of the compiled source.

To upload files to my ESP8266, I followed the installation and usage instructions for the ESP8266 filesystem plugin for the standard Arduino IDE.

First, I created a .gitignore file and added this so that none of the actual values would get added to the repository:

# MQTT config info
data/config.json


Then I created a data folder and added a config.json file that had all the configuration properties that I need to reference:

{
  "cloudmqtt_server": "***.cloudmqtt.com",
  "cloudmqtt_user": "********",
  "cloudmqtt_pass": "************",
  "cloudmqtt_port": 10000 
}


Following the example above, I added #include &lt;FS.h&gt; at line #1 (as well as #include &lt;ArduinoJson.h&gt; for parsing the JSON) and have this block to instantiate the variables with dummy values that will be replaced by the actual values from the JSON file:

// MQTT pub/sub for the servo functions
#define CLOUDMQTT_CONFIG  "/config.json"
// these values are loaded from a config.json file in the data directory
char cloudmqtt_server[18] = "***.cloudmqtt.com";
int  cloudmqtt_port       = 0;
char cloudmqtt_user[9]    = "********";
char cloudmqtt_pass[13]   = "************";


Then in the setup I have this block to load the values from the config:

if (SPIFFS.begin()) {
  Serial.println("mounted file system");

  // parse json config file
  File jsonFile = GetFile(CLOUDMQTT_CONFIG);
  if (jsonFile) {
    // Allocate a buffer to store contents of the file.
    size_t size = jsonFile.size();
    std::unique_ptr&lt;char[]&gt; jsonBuf(new char[size]);
    jsonFile.readBytes(jsonBuf.get(), size);

    DynamicJsonBuffer jsonBuffer;
    JsonObject&amp; json = jsonBuffer.parseObject(jsonBuf.get());
    if (json.success()) {
      strcpy(cloudmqtt_server, json["cloudmqtt_server"]);
      cloudmqtt_port = json["cloudmqtt_port"];
      strcpy(cloudmqtt_user, json["cloudmqtt_user"]);
      strcpy(cloudmqtt_pass, json["cloudmqtt_pass"]);
    } else {
      Serial.println("failed to load json config");
    }
    jsonFile.close();
  }
}


I'm not sure if there's a better way, but this way works for me: 
https://github.com/dieseltravis/esp8266-projects/tree/master/servo-demo